2

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?

Oliver Williams
  • 5,966
  • 7
  • 36
  • 78
  • The output of this piece is different from what you expect: `which my-tool | awk \'{ print($NF) }\'` ? – Luuk Aug 27 '22 at 11:04
  • No, it is as expected, and the path or file it says doesn't exist in the error output certainly does. That's the part that's the mystery – Oliver Williams Aug 27 '22 at 11:09
  • 1
    I would create an environment variable like: `export MYDIR=~/path/to/src`, and use `$MYDIR` in all scripts that need it. (But, of course, with a better name than `MYDIR` ) – Luuk Aug 27 '22 at 11:12
  • That works and I'm implementing it, thanks. This is just a mystery/annoyance – Oliver Williams Aug 27 '22 at 11:22
  • 1
    I would expect that you do not have a directory named `~`. In some contexts, `~` is expanded to the path of your home directory, but in others it is not. Always replace `~` with `$HOME`. Tilde should only be used interactively, so you do not have to worry about the details of when tilde expansions are applied. – William Pursell Aug 27 '22 at 13:10
  • @OliverWilliams Regarding the first 2 comments above, given the example value of `my-tool` in your question, `'~/path/to/src/my-tool.py'`, if `~` expands to a path that includes spaces the output of `which my-tool | awk '{ print($NF) }'` would be everything between the 1st 2 spaces, otherwise it'd be a null string. Either way, it's hard to believe that'd be the expected output. – Ed Morton Aug 27 '22 at 15:11

3 Answers3

3

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'
Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
1

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 ~.

Shawn
  • 47,241
  • 3
  • 26
  • 60
  • If you have spaces in the path, this will break, but there's ways to work around it if needed. – Shawn Aug 28 '22 at 14:00
-1

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
RARE Kpop Manifesto
  • 2,453
  • 3
  • 11