I have a script below, which stores the absolute path of FZF
in a variable named fzf_cmd
. I want to execute FZF
using this variable. It works in Bash but not in Zsh.
f1.sh
fun() {
local fzf_cmd
[[ -n "${BASH_VERSION}" ]] && fzf_cmd="$(type -P fzf)"
[[ -n "${ZSH_VERSION}" ]] && fzf_cmd="$(type -p fzf)"
"${fzf_cmd}" --height 40%
}
fun
I get the following error message in ZSH.
fun:4: no such file or directory: fzf is /home/rishi/commandline-plugins/fzf/bin/fzf
However, when I run fzf
explicitly using its absolute path (/home/rishi/commandline-plugins/fzf/bin/fzf
), it works as expected in Zsh.
Am I missing anything here? How can I get it to work in both of the shells?
EDIT:
The command type -p fzf
in Zsh doesn't only print the absolute path of fzf. It prints fzf is /home/rishi/commandline-plugins/fzf/bin/fzf
. That was the cause of the issue. I think using whence
would be better here!
You got the zsh variant wrong. This should work:
fun() {
local fzf_cmd
if [[ -n "${BASH_VERSION}" ]]; then
fzf_cmd="$(type -P fzf)"
elif [[ -n "${ZSH_VERSION}" ]]; then
fzf_cmd==fzf
fi
"${fzf_cmd}" --height 40%
}
type -p fzf
provides too much noise in zsh. While you could use ksh-style whence -p fzf
, using the builtin =cmd
filename expansion mechanism makes it shorter and more efficient as it avoids forking a process to get the output of a command through a pipe.
UPDATE
Detailed explanation, as requested by the OP:
In the original approach, the use of type -p fzf
would have worked, if this command would output only the absolute path of fzf
and nothing else, which it unfortunately does not.
Therefore I used the trick with filename expansion: For a word w
, the term =w
expands to the first absolute path of w
, found in the variable path
(respectively $PATH
).
BTW, we could also write it without testing the shell version, using the command which
:
fun() {
"$(/usr/bin/which fzf)" --height 40%
}
This ensures that always an external program named fzf
is invoked, bypassing a possible function (or alias) of the same name. The disadvantage compared to the approach using =
-expansion is, that with which
we always create a child process to calculate the path to fzf
, while =fzf
runs inside the parent shell; no forking needed.
UPDATE
It just occurred to me that an even simpler solution would be to use the command
command, which works in bash and in zsh:
fun() {
/usr/bin/command fzf --height 40%
}
This would also bypass a function or alias named fzf
and ensure that only an executable (external command) named fzf gets invoked.
UPDATE
Using absolute path to which
and command
for compatibility with bash and zsh.