There are two options to run a shell script:
$ ./some/script.sh
$ . ./some/script.sh
As far as I unterstand, the first one starts a new shell environment based on the given shebang line withing the script. While the second one executes the statements within the same shell environemnt.
Are there more differences ?
Where can I find more documentation about the second one ?
Is . a real command ? I can not find a manpage to it.
Are there more differences ?
The gist of the matter is that using . the script is executed line by line in the same process. Otherwise a new process is forked. And a separate process has no way of changing the parent, for example it can't change environment variables such as the current directory.
Where can I find more documentation about the second one ?
[cnicutar#fresh ~]$ help source
source: source filename [arguments]
...
Is . a real command
[cnicutar#fresh ~]$ type .
. is a shell builtin
In case it isn't obvious already, . and source are identical*.
As rush commented, source isn't specified by POSIX so you should probably use . in code intended to be portable. The dot is specified in chapter 2.
. is a Bourne Shell command for reading a file and executing the commands in the file, your analysis is essentially correct. bash and other shells add source as an alias for ..
See the manual for bash builtins, and see . at the top of the manual for Bourne sh builtins.
Read and execute commands from the filename argument in the current shell context. If filename does not contain a slash, the PATH variable is used to find filename. When Bash is not in posix mode, the current directory is searched if filename is not found in $PATH. If any arguments are supplied, they become the positional parameters when filename is executed. Otherwise the positional parameters are unchanged. The return status is the exit status of the last command executed, or zero if no commands are executed. If filename is not found, or cannot be read, the return status is non-zero. This builtin is equivalent to source.
it is used to source an environment. such as the .profile.
Related
I am used to running shell scripts with for example
source script.sh
I was surprised to learn recently that this also works
. script.sh
The single dot of course normally indicates the current directory so I am now confused. Can anyone explain this use of .?
Taken from IBM docs: ".(dot) runs a shell script in the current environment and then returns. Normally, the shell runs a command file in a child shell so that changes to the environment by such commands as cd, set, and trap are local to the command file. The . (dot) command circumvents this feature.
If there are slashes in the file name, . (dot) looks for the named file. If there are no slashes . (dot) searches for file in the directories specified in the PATH variable. This may surprise some people when they use dot to run a file in the working directory, but their search rules are not set up to look at the working directory. As a result, the shell does not find the shell file."
. (dot) and source mean the same thing in the bash language.
In fact the (dot) form is the standard form according to the POSIX Shell Specification. The source form is a bash extension, but not a bash invention. (The venerable Berkley C-shell (csh) used to use source rather than ..)
According to the POSIX Shell specification, source is a command name whose meaning is unspecified; see https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
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.
I am trying to have a sourced shell script determine its own location, and I have found that this is a difficult task for dash.
In bash, sh, and csh, I can use: $_.
In fish, I can use (status -f).
In dash, I have had no luck...
I have tried sourcing the path.sh file shown below with the following results:
# path.sh
called=$_
echo called: $called
echo underscore: $_
echo zero: $0
echo dash_source: $DASH_SOURCE
echo bash_source: $BASH_SOURCE
dash -c ". path.sh"
outputs:
called: /usr/local/bin/dash
underscore: /usr/local/bin/dash
zero: dash
dash_source:
bash_source:
How can I get the path to path.sh in dash?
There does not appear to be any portable (POSIX-standard) way to do this.
POSIX refers to sourcing as "dot scripts." While several other parts of the shell language reference do discuss "dot scripts," none of these instances appear to provide any way to find out the path to the currently-executing dot script. In particular, $0 is reserved for "shell scripts" which are a different thing from "dot scripts." The word "source" does not appear on the page in any capitalization or as part of any larger word (so no $BASH_SOURCE-like thing). None of the standard environment variables which affect the shell seem relevant either. I'm going to say this is not possible within the POSIX spec. Since Dash follows POSIX quite closely, it is unlikely to have a Dashism for this specific case (if it was going to do that, it would've borrowed $BASH_SOURCE or created an analogous variable).
I agree with the accepted answer, though I was able to make it work using lsof:
x=$(lsof -p $$ -Fn0 | tail -1); script=${x#n}
This works because dash keeps the script open when it is executing. This needs to be the first line of the script, otherwise it may fail if dash later opens additional file descriptors.
This is not possible under POSIX shell standard (which dash implements, nothing more). . is a built-in, not a shell script, therefore, the $0 positional argument refers to the caller of ., and $1 refers to an argument to that caller, if one exists. In particular, none of these things refer to the . itself or its argument script.
If you can change the code that sources, put this function before all source calls.
.() {
command . "$1"
}
. script.sh
# more dot scripts
what is does it to hook the 'dot script' function, now inside the function, positional parameters are the function parameters.
So inside your sourced script, positional parameters are:
. # $0
script.sh # $1
Inside the script being sourced:
# script.sh
echo "$(basename "$1")" # this is the name of the script itself
I have noticed some weird behavior when sourcing another script within my shell script. The script that I am sourcing to setup the environment in my shell script takes an optional argument, e.g.
source setup.sh version1
However in my shell script I have also have command line argument variables. For example:
./myscript.sh TEST 1
Inside myscript.sh:
#!/bin/zsh
source setup.sh
echo ROOT version setup $ROOT_SYS
...more of the script
The problem that I have noticed with my script above is that the $1 argument (TEST in this example) is used in the source setup.sh command. This causes the command to become
source setup.sh TEST
which of course fails as setup.sh does not have a version TEST.
I solved this problem by editing my script to below.
#!/bin/zsh
source setup.sh version1
echo ROOT version setup $ROOT_SYS
...more of the script
The source command now does not pick up the $1 argument.
Why/How does the source command pick up the $1 argument when I am running my shell script?
Historically, unix shells didn't allow any arguments to be passed to scripts called with the . built-in (source is an alias of . available in bash, ksh and zsh). The . built-in means “act as if this file was actually included here”.
In bash, ksh and zsh, if you pass extra arguments to the . built-in, they become positional parameters ($1 and so on) in the sourced script. If you pass zero arguments, the positional parameters of the main script remain in effect. In those shells, . behaves rather like calling a function, though not perfectly so (in particular, in bash, if you modify the positional parameters inside the sub-script, the modification is seen by the main script).
A simple way of avoiding this kind of difficulty is to only ever define functions (and perhaps variables) in the subscript. Treat it as a code library, such that merely sourcing it has no effect, and then call functions from the sub-script to actually do something.
This is because source executes the code of setup.sh as if it was in place, so when setup.sh access, say, $1, the value it has is that of the first argument of the actual script. If you want to avoid that you could either execute it:
setup.sh
or, if you need to get back some variables or values from it, change it to return the result in form of an output, something like:
ROOT_SYS=`setup.sh`
Finally, as you figured out, the source keywords also allows providing arguments to the scripts, but it bypasses current arguments if you don't provide any.
For example, when I say . .bashrc on my Linux command prompt, is there a corresponding binary/script that gets executed in place of the first dot? If the dot itself is a command, where is its location?
The . operator is shorthand for the source Bash builtin (as pointed out by John Kugelman below). Typing
help .
or
help source
at the Bash prompt will give you some information. For more on how source works, see http://www.ss64.com/bash/period.html.
Additionally I want to point out that you don't "execute" anything with it (in terms of fork/exec), which is very important (and probably the only reason '.' exists).