Home > Enterprise >  The results of running `which $0` with bash and ./ are different, what causes this difference?
The results of running `which $0` with bash and ./ are different, what causes this difference?

Time:02-14

I have the following bash file (test_para.sh):

#!/bin/bash
echo $(dirname `which $0`)

When I run this file with bash test_para.sh, the terminal outputs the following errors:

dirname: missing operand
Try 'dirname --help' for more information.

However, when I run this bash scripts wiht ./test_para.sh, there is no error, and the output is .

As we all know, bash command and ./ command are both running in a child shell, why the running abovementioned scritp shows different results in these two ways?

CodePudding user response:

When you're trying to understand what's happening in a shell script, it's often helpful to put the command set -x before the problem section to see what's actually happening as the script runs. If you add that to your script (right after the shebang), and run it both ways, you'll see something like this:

$ ./test_para.sh 
    which ./test_para.sh
   dirname ./test_para.sh
  echo .
.

$ bash test_para.sh 
    which test_para.sh
   dirname
dirname: missing operand
Try 'dirname --help' for more information.
  echo

The first run does pretty much what you probably expect -- $0 is "./test_para.sh", so which just prints that, dirname splits off the ".", and echo prints that (BTW, the echo $( ) thing is unnecessary and irrelevant -- I'll get back to this).

In the second run, $0 is just "test_para.sh", because that's the name you gave bash. But what which does is search the directories in $PATH for something by that name, and of course it's not in any of the PATH directories, it's in the current working directory. So which can't find it, and therefore doesn't print anything. dirname then complains that it wasn't given anything to get the directory part of, and you get that error (and no output).

The basic problem here is that which is not an all-purpose find-the-program tool, it specifically searches $PATH directories. Here's what its man page says:

which returns the pathnames of the files (or links) which would be executed in the current environment, had its arguments been given as commands in a strictly POSIX-conformant shell. It does this by searching the PATH for executable files matching the names of the arguments.

..so if you execute a script in some other way (like with the bash command), it's not too surprising that it doesn't work.

If you're trying to find a general solution to locating the script's directory, it's actually surprisingly hard, and sometimes impossible (or even meaningless). See "How can I get the source directory of a Bash script from within the script itself?" and BashFAQ #28: "How do I determine the location of my script? I want to read some config files from the same place."

BTW, your script has several mistakes/bad practices; I recommend running it (and any other scripts you write) through shellcheck.net and fixing what it points out. In this script, you should use $( ) instead of backticks, double-quote variable and command substitutions, and don't use echo $( ) (the echo and the $( ) basically cancel each other out). Here's the script with these cleaned up (but the original problem still there):

#!/bin/bash
dirname "$(which "$0")"
  •  Tags:  
  • bash
  • Related