#!/bin/bash
cd "${0%/*}/" #!/bin/bash
cd "${0%/*}/"If $0 happens to be just "foo", you calculate "foo/" which is incorrect.
$(dirname "$0") calculates ".".
Where it might make sense is if "foo" were on your path (which might include "."): one might then expect $0 to be "foo" as that's what you typed; but, if so, "." is the incorrect directory for all of the non-"." entries in your path! Thankfully, this isn't the case, though: if you run "foo", then $0 is an absolute path to the script.
So like, the next thing which could come up is "the caller did something crazy with $0 that we didn't expect", as, normally, argv[0] can be set to anything at all by the person who runs a program, no matter where the program happens to be located... but, then, if we aren't going to trust $0 as a useful "path" (lol) to getting the correct path at all, what are we even doing here? :(
But, thankfully-again, bash doesn't let that happen: if you write a runner program that calls execl and you override argv0 to just be "foo", bash fixes that; I am honestly not 100% sure of what the exact behavior here is to make that be the result (as there are multiple "obvious" ways to code that mechanism, or even which layer is in charge of it), but the reality is you don't seem to get to control $0 willy-nilly. (edit: in my 4th reading of this comment I realize this is actually the same behavior as the earlier paragraph involving the path and is almost certainly the same mechanism, whatever it happens to be precisely.)
So, I guess I am confused: do you have a concrete reproduction of how you actually got "$0" set to "foo" in the first place? I would honestly love to know and would thank you dearly, as I am sure that there will be some corner case that triggers in a lot of other software and I'll have another crazy/fun thing to look for when I do security audits ;P.
Regardless, the reason I do not use dirname is because I feel the actual reason why my code is a "silly hack" is because I am assuming that / is the directory separator; but, in my personal experience, systems (including embedded devices, restricted containers, Linux derivatives, etc.) that have a shell but do not have dirname--despite it being "POSIX"--are somewhat common whereas systems that have a shell capable of running my script which do not enforce use of / as a directory separator on $0 has maybe at most come up once or twice in my entire life (and it caused other things to break anyway ;P)... and, come to think of it, would also fail to be POSIX compliant anyway, just in a way that is much less likely than missing dirname.
(I will also point out that the behavior of dirname is inconsistent with respect to the root directory: my hack always trails the path with / and never leads to a doubled-up /--which, sure, is often allowable... but is a non-canonical path which I have seen cause security issues before and personally believe should be disallowed--while dirname eats the trailing / everywhere except for / itself, which sucks and is asking for pain.)
That doesn't seem to be the case.
$ cat foo
echo $0
$ ./foo
./foo
$ export PATH=.:$PATH
$ foo
foo
I'm usually on zsh, but I get the same result even varying the shebang inside foo.Does every shell that is currently in use? I don't know. I have access to some embedded systems with BusyBox; let's try that:
# cat foo
#!/bin/sh
printf '$0 is %s\n' "$0"
# ./foo
$0 is ./foo
# PATH=. foo
$0 is ./foo
# PATH= foo
$0 is foo
# /bin/sh --help
BusyBox v1.22.1 (2017-03-02 15:41:43 CST) multi-call binary.
Next, on Ubuntu: $ PATH= foo
$0 is foo
$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Apr 18 2020 /bin/sh -> dash
Next. invoking a script by passing it as an argument to a shell: $ /bin/bash foo
$0 is foo
$ /bin/bash ./foo
$0 is ./foo
$ /bin/bash ././foo
$0 is ././foo
> the behavior of dirname is inconsistent with respect to the root directoryThat is true. The result of dirname cannot be combined with a name without a slash, but the slash is already there in the root case, an annoying special case. Scripts often take the directory of $0 in order to combine it with relative paths.