File execution with dot space versus dot slash

16,392

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.

Share:
16,392

Related videos on Youtube

TTT
Author by

TTT

I'm a scientist/engineer (not computer/software-related) that does a fair amount of coding, including numerical modeling and various kinds of data analytics. My formal training in coding is quite limited, a 1-semester course in Matlab (a high-level science/engineering language) and another semester course in real-time systems coding in C/C++. Though because I do it fairly regularly (1-10 hours/week) and because it interests me, I've worked hard to gain a deeper understanding of the both the details and the "big picture" on the software and hardware sides of systems I use.

Updated on June 22, 2022

Comments

  • TTT
    TTT almost 2 years

    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.

    • Reinstate Monica Please
      Reinstate Monica Please almost 10 years
      Is B executable? . B doesn't technically execute B, it sources it. If one of them is ksh, try using ksh yourscript
    • TTT
      TTT almost 10 years
      Yes, B is executable. I'm not clear on the difference between the two, will read now.
    • TTT
      TTT almost 10 years
      Never mind, I see source is the term (and a command) for running something in the same shell.
  • TTT
    TTT almost 10 years
    You're right about the current directory not being in $PATH. I had thought about this, but then why does . B work at the command line? I tried echo $PATH and it's the same in both the script and my. That's the real core of my question, why does the script fail where the command line succeeds if it looks like everything (shell, environmental variables, etc.) is the same?
  • TTT
    TTT almost 10 years
    Yep, fails with bash -posix. I went back and figured out why the script was running with POSIX-compliant mode and added a full explanation to the original question (wouldn't fit here).
  • tripleee
    tripleee about 6 years
    Maybe also explain that sourcing a file in the current process means the sourced file can manipulate your environment. So if you run . B and B does foo=bar then this variable will be set when B finishes, until the current script or interactive shell exits, whereas ./A cannot change your variables (a subprocess cannot change its parent).