5 ms·
I like using this little trick to change the directory to the one in which the script resides... #!/bin/bash cd "$(dirname "$0")"
by ray_v 3y ago
I like using this little trick to change the directory to the one in which the script resides...
#!/bin/bash
cd "$(dirname "$0")"
- saurik 3y ago#!/bin/bash cd "${0%/*}/"
- heavyheavy 3y agohuge fan
- kazinator 3y agoReplacing the POSIX dirname utility with a silly hack is inexcusable. If $0 happens to be just "foo", you calculate "foo/" which is incorrect. $(dirname "$0") calculates ".".
- saurik 3y agoOk, so I am pretty sure that, if $0 is just "foo", something else is broken/weird? Like, first off, let's analyze why that would ever happen: it isn't because the script is in the current directory--which you imply by saying the correct answer is "."--as then the $0 should be "./foo", not "foo" (unless "." is on your path, which is the next paragraph). 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.)
- akkartik 3y ago> if you run "foo", then $0 is an absolute path to the script. 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.
- saurik 3y agoOk, I see the case: it is if you actually literally have "." on your PATH. I never ever do this--and consider it super super dangerous--and so it never occurred to me to test that case. So like, if I have "~/bin" on my path, it is converted to an absolute path, but if I have "." on my path and I am in that folder it gives me just foo. Thanks for helping me understand that case...
- kazinator 3y ago> bash fixes that 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 directory That 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.
- saurik 3y agoOk, so I see what is going on with PATH, and it was fascinating (at least to me) and I am really glad I asked: it never ever occurred to me to literally put "." or (apparently even more critically) even "" on a PATH, due to the ever-present danger of entering a folder with a trojan horse called "ls" and getting toasted. When I was talking about "." being on my PATH I meant like, running the program from inside /usr/bin. If I put "~/bin" on my path and run foo it gets converted to an absolute /home/saurik/bin/foo, but if I put "." on my PATH and go into the folder... yeah: you managed to pull off leaving me with just "foo" in $0. I still can't use dirname for most of my scripts, though :(. (If it makes you feel any better, I do often use dirname if it's there.)
- KolenCh 3y agoThis would have a problem if the script is symlinked.
- michaelcampbell 3y agocd $(dirname $(readlink -f "$0")) # ?
- ray_v 3y agoThanks! that's good to keep in mind for sure. I use this mainly for running something out of cron where i want to make sure it gets placed into that directory since many of the scripts i have are written to expect that.