I am attempting to work with an existing library of code but have encountered an issue. In short, I execute a shell script (let's call this one A
) whose first act is to call another script (B
). Script B
is in my current directory (a requirement of the program I'm using). The software's manual makes reference to bash
, however comments in A
suggest it was developed in ksh
. I've been operating in bash
so far.
Inside A
, the line to execute B
is simply:
. B
It uses the "dot space" syntax to call the program. It doesn't do anything unusual like sudo
.
When I call A
without dot space syntax, i.e.:
./A
it always errors saying it cannot find the file B
. I added pwd
, ls
, whoami
, echo $SHELL
, and echo $PATH
lines to A
to debug and confirmed that B
is in fact right there, the script is running with the same $SHELL
as I am at the command prompt, the script is the same user as I am, and the script has the same search path $PATH
as I do. I also verified if I do:
. B
at the command line, it works just fine. But, if I change the syntax inside A
to:
./B
instead, then A
executes successfully.
Similarly, if I execute A
with dot space syntax, then both . B
and ./B
work.
Summarizing:
./A
only works if A
contains ./B
syntax.
. A
works for A
with either ./B
or . B
syntax.
I understand that using dot space (i.e. . A
) syntax executes without forking to a subshell, but I don't see how this could result in the behavior I'm observing given that the file is clearly right there. Is there something I'm missing about the nuances of syntax or parent/child process workspaces? Magic?
UPDATE1: Added info indicating that the script may have been developed in ksh
, while I'm using bash
.
UPDATE2: Added checking to verify $PATH
is the same.
UPDATE3: The script says it was written for ksh
, but it is running in bash
. In response to Kenster's answer, I found that running bash -posix
then . B
fails at the command line. That indicates that the difference in environments between the command line and the script is that the latter is running bash
in a POSIX-compliant mode, whereas the command line is not. Looking a little closer, I see this in the bash
man
page:
When invoked as sh, bash enters posix mode after the startup files are read.
The shebang
for A
is indeed #!/bin/sh
.
In summary, when I run A
without dot space syntax, it's forking to its own subshell, which is in POSIX-compliant mode because the shebang
is #!/bin/sh
(instead of, e.g., #!/bin/bash
. This is the critical difference between the command line and script runtime environments that leads to A
being unable to find B
.
Let's start with how the command path works and when it's used. When you run a command like:
ls /tmp
The ls
here doesn't contain a / character, so the shell searches the directories in your command path (the value of the PATH environment variable) for a file named ls
. If it finds one, it executes that file. In the case of ls
, it's usually in /bin
or /usr/bin
, and both of those directories are typically in your path.
When you issue a command with a / in the command word:
/bin/ls /tmp
The shell doesn't search the command path. It looks specifically for the file /bin/ls
and executes that.
Running ./A
is an example of running a command with a / in its name. The shell doesn't search the command path; it looks specifically for the file named ./A
and executes that. "." is shorthand for your current working directory, so ./A
refers to a file that ought to be in your current working directory. If the file exists, it's run like any other command. For example:
cd /bin
./ls
would work to run /bin/ls
.
Running . A
is an example of sourcing a file. The file being sourced must be a text file containing shell commands. It is executed by the current shell, without starting a new process. The file to be sourced is found in the same way that commands are found. If the name of the file contains a /, then the shell reads the specific file that you named. If the name of the file doesn't contain a /, then the shell looks for it in the command path.
. A # Looks for A using the command path, so might source /bin/A for example
. ./A # Specifically sources ./A
So, your script tries to execute . B
and fails claiming that B
doesn't exist, even though there's a file named B
right there in your current directory. As discussed above, the shell would have searched your command path for B
because B
didn't contain any / characters. When searching for a command, the shell doesn't automatically search the current directory. It only searches the current directory if that directory is part of the command path.
In short, . B
is probably failing because you don't have "." (current directory) in your command path, and the script which is trying to source B
is assuming that "." is part of your path. In my opinion, this is a bug in the script. Lots of people run without "." in their path, and the script shouldn't depend on that.
Edit:
You say the script uses ksh
, while you are using bash
. Ksh follows the POSIX standard--actually, KSH was the basis for the POSIX standard--and always searches the command path as I described. Bash has a flag called "POSIX mode" which controls how strictly it follows the POSIX standard. When not in POSIX mode--which is how people generally use it--bash will check the current directory for the file to be sourced if it doesn't find the file in the command path.
If you were to run bash -posix
and run . B
within that bash instance, you should find that it won't work.