Skip to content

Fix fzf preview under non-POSIX login shells (e.g. fish) - #3

Merged
jghub merged 3 commits into
jghub:masterfrom
johnstegeman:fix-fzf-preview-shell
Sep 30, 2026
Merged

jghub merged 3 commits into
jghub:masterfrom
johnstegeman:fix-fzf-preview-shell

Conversation

@johnstegeman

@johnstegeman johnstegeman commented Sep 29, 2026 •

Copy link
Copy Markdown

Problem

With a non-POSIX login shell (e.g. fish), the fzf preview pane fails immediately:

fish: Unsupported use of '='. In fish, please use 'set pathname '/path/to/dir''.
pathname='/path/to/dir/'; LC_ALL=C ls -AC --color=always "$pathname"

Cause

_ze_find and _ze_dig build the fzf --preview command in POSIX shell syntax (pathname={2..}; ...). fzf executes preview commands with $SHELL -c (fzf docs: "the default value is $SHELL -c if $SHELL is set, otherwise sh -c"). When $SHELL is fish, fish cannot parse a bare VAR=value assignment, so the preview dies before the listing runs. The preview is spawned by fzf itself, so running ze.sh's logic under bash (as the fish wrapper does) does not help.

Fix

Reworked per @jghub's feedback — two independent changes:

  1. Interpolate the placeholder directly ({2..}) instead of first assigning it to a shell variable. fzf already single-quotes placeholder expansions (this is the same quoting the old pathname={2..} relied on), so the result is equivalent, but the preview command no longer contains any shell-specific assignment syntax. This keeps it working on any shell and on fzf versions older than 0.48 (Debian bookworm ships 0.38, Ubuntu 24.04 ships 0.44.1), where --with-shell does not exist.

  2. Pin SHELL=/bin/sh on both fzf calls, so the preview is always spawned by a POSIX shell regardless of the user's login shell. This also prevents the interactive shell's functions/aliases from leaking into the preview — e.g. a fish ls function wrapping eza, which rejects -C and would otherwise break the preview even after the parse error is gone. It also future-proofs other wrappers (nushell, …).

-    typeset metric header mode=${3:-dirs} preview='pathname={2..}'
+    typeset metric header mode=${3:-dirs}
 ...
-        files) preview+='; head -256 -- "$pathname"'; zopts+=(-o);;
-        *)     preview+='; LC_ALL=C ls -AC --color=always "$pathname"';;
+        files) preview='head -256 -- {2..}'; zopts+=(-o);;
+        *)     preview='LC_ALL=C ls -AC --color=always {2..}';;
 ...
-            … | fzf "${fzfopts[@]}" | cut -f2-)
+            … | SHELL=/bin/sh fzf "${fzfopts[@]}" | cut -f2-)

(and the analogous edits in _ze_dig). The --with-shell approach from the first revision is dropped, since pinning SHELL covers it without the fzf >= 0.48 requirement.

Verification

  • bash -n / zsh -n / ksh -n: OK
  • fzf driven in a pty with the login shell set to fish and a path containing a space and a single quote: preview runs under /bin/sh and lists the directory correctly ({2..} quoting intact).
  • End-to-end fish -c 'ze -f iris' and fish -c 'ze -d': no Unsupported use of '=' and no eza -C errors.
  • Non-fzf fallbacks (_ZE_NO_FZF) unaffected.

@jghub

jghub commented Sep 29, 2026

Copy link
Copy Markdown
Owner

Thanks for tracking this down. I am not an active fish user (and I did not think of the fish-as-login-shell scenario), so the report is appreciated.

Direct {2..} interpolation is a good fix and I'll merge it once you've updated the PR. I think that it is principally preferable to keep the preview command free of shell-specific assignment syntax. E.g. debian/ubuntu still ships fzf 0.44 AFAIK so the --with-shell fix would not suffice for those OSes.

Separately/alternatively, I'd still like to pin /bin/sh for the fzf call, since a future wrapper (nushell, for example) could hit the same problem (this also should resolve the environment leakage issue you mention). Proposed change:

--- a/ze.sh
+++ b/ze.sh
@@ -189,7 +189,7 @@
         header="${mode%s} stack ranked by $metric"
         fzfopts=( -0 -e --no-sort --preview-window='top,19%' --header="$header" --color='header:bright-red' --preview "$preview" )
         (set -o pipefail; _ze "${zopts[@]}" -- "$1" | cut -f2- |
-            awk '{ buf[NR] = $0 } END { offs = NR+1; while (NR) print offs-NR "\t" buf[NR--] }' | fzf "${fzfopts[@]}" | cut -f2-)
+            awk '{ buf[NR] = $0 } END { offs = NR+1; while (NR) print offs-NR "\t" buf[NR--] }' | SHELL=/bin/sh fzf "${fzfopts[@]}" | cut -f2-)
     fi
 }

I tested it in fish with

begin; set -lx SHELL (command -v fish); ze -f; end

so that ze.sh then sees SHELL as fish on startup. so this approach also woul fix the issue (but your 'direct interpolation' variant still should be adopted in the first place).

fzf runs --preview commands with $SHELL -c. ze.sh built the preview in
POSIX shell syntax (pathname={2..}; ...), so with fish as the login shell
the preview died before running:

  fish: Unsupported use of '='. In fish, please use 'set pathname ...'

Two independent changes, per maintainer feedback:

* Interpolate the field placeholder directly ({2..}) instead of first
  assigning it to a shell variable. fzf already single-quotes placeholder
  expansions, so this is equivalent and keeps the preview free of any
  shell-specific assignment syntax (works on any fzf, incl. Debian/Ubuntu
  versions older than 0.48 that lack --with-shell).

* Pin SHELL=/bin/sh on both fzf calls, so the preview is spawned by a
  POSIX shell regardless of the user's login shell. This also stops the
  interactive shell's functions/aliases from leaking into the preview
  (e.g. a fish 'ls' function wrapping eza, which rejects -C).

@jghub jghub left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'preview' still needs to be declared to keep it local to the respective function.

Comment thread ze.sh Outdated
Comment thread ze.sh Outdated
johnstegeman and others added 2 commits September 30, 2026 09:41
Co-authored-by: jghub <jghub@users.noreply.github.com>
Co-authored-by: jghub <jghub@users.noreply.github.com>
@jghub
jghub merged commit 26c1de1 into jghub:master Sep 30, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants