zsh: no such file or directory: - but file exists

1.7k Views Asked by At

Background: I already have a working alias my-tool as follows:

alias my-tool='~/path/to/src/my-tool.py'

I want another alias that depends on that alias' path (so I don't write the path in two places):

alias other-tool=$'$(dirname $(dirname $(which my-tool | awk \'{ print($NF) }\')))/script/other-tool.sh'

which outputs the error

zsh: no such file or directory: ~/path/to/script/other-tool.sh

but it exists!

Strangely, if I replace the alias with

alias other-tool=$'$(dirname $(dirname ~/path/to/src/my-tool.py))/script/other-tool.sh'

it works, but again I want to avoid entering the ~/path/to/.. twice

Clearly there's unexpected behavior in either awk, dirname or which, can anyone explain why the error?

3

There are 3 best solutions below

0
RARE Kpop Manifesto On

the way i deal with tilde (~) is simply expand it out once instead of hard-quoting them with single quotes (')- this way i wouldn't have to go manually replace it with "${HOME}" :

testfn="$( grealpath -ePq ~/.zshenv )"   # g- for gnu

echo "${testfn}"
ls -AlF "${testfn}"

/Users/user123/.bash_profile
-rw-r--r--  1 user123  staff  1746187 Aug 27 23:54 /Users/user123/.bash_profile

And that's actually a correct output, since my zsh environment file is actually a symlink :

lr--r--r--  1 user123  staff  15 Dec 31  2019 /Users/user123/.zshenv -> ./.bash_profile
1
Shawn On

There are definitely better ways to do this (I like the path prefix variable mentioned by @Luuk in a comment), but a working equivalent to what you're trying is:

alias other-tool=$'loc=($(type my-tool)); ${~loc[-1]:h:h}/script/other-tool.sh'

Instead of running awk and a couple of dirname processes each time you run the alias, this uses zsh parameter substutitions intead - first it sets the loc array variable to the result of running type my-tool, with each word its own element, then for the last element (The path): The :h modifier acts like dirname, done twice, and then the ~ turns on the GLOB_SUBST option for that expansion, which among other things does filename expansion, catching the ~.

0
Gilles 'SO- stop being evil' On

There's a tilde character at the beginning of the alias. This works when the alias is invoked as a command, because the result of the alias expansion undergoes word expansion. But then you attempt to do some processing on the text of the alias, which results in a string that you want to use as a path. That string still contains a tilde character, so you're attempting to use a directory called ~.

If you really want to use the my-tool alias as a basis for defining the other-tool alias, do the string processing at the time you define the alias. Don't use type or which: they're end-user commands that display additional messages. Reach directly for the text of the alias, using the aliases associative array. Use zsh's built-in constructs for manipulating strings (parameter expansion) or paths (history modifiers): they're easier to get right than using external tools.

alias other-tool=$aliases[my-tool]:h:h/scripts/other-tool.sh

or

alias other-tool=${aliases[my-tool]%/*/*}/scripts/other-tool.sh

But it would be both conceptually simpler and more reliable to instead define a variable with the root path, and use that variable in both aliases.

tool_root=~/path/to
alias my-tool='$tool_root/src/my-tool.py'
alias other-tool='$tool_root/scripts/other-tool.py'