Sometimes you might hear people talk about sourcing a shell script, and sometimes you might hear them talk about running one, or executing it.
So what's the difference? Aren't they the same thing?
I'm going to set up a little test script to show you what's going on under the rug. I'll call it foo.sh
:
#!/bin/sh
new_path="/tmp/demo"
mkdir -p "$new_path"
cd "$new_path"
printf "I am a script called %s...\n" "$0"
printf "My process ID is %d...\n" "$$"
printf "... and I have changed directory to %s\n" "$new_path"
printf "TESTVAR1 started off as '%s'\n" "$TESTVAR1"
printf "and TESTVAR2 started off as '%s'\n" "$TESTVAR2"
TESTVAR1="changed 1"
printf "Now I have changed TESTVAR1 to '%s'\n" "$TESTVAR1"
Option 1: Execution
This requires the script to have the execution bit set in its permissions and a shebang on the first line telling the shell which command to use to run the rest of the file.
chmod +x foo.sh
Speaking of permissions, you can use the "setuid" method to make the script run as a different user, if you want to. This is typically done with sudo chmod +s foo.sh
and will allow regular users to run the application with elevated (root) privileges. If you don't need to, don't do it!
In foo.sh
I've used #!/bin/sh
which is conventionally a POSIX-compliant shell. Importantly, it doesn't have to be the same as the shell I'm using. You can have a shebang of #!/usr/bin/bash
and run it from a zsh
session if you like, but the script had better not have any zsh-specific syntax in it, because zsh won't be the program parsing it.
Even if the shebang says to run the script with the same shell we're using, it won't be the same same shell. It'll be another instance of the application, called a subshell. It will have its own environment and only be able to read any of our environment if we explicitly export
it.
Note about Process IDs
I'm going to use echo $$
as a way of getting the PID (Process ID) for the current shell. That's one of many built-in shell variables we don't need to go into now, just know that it works.
Let's go
$ pwd
/home/moopet
$ echo $$
8192
$ TESTVAR1="elephants"
$ export TESTVAR2="hens"
$ ./foo.sh
I am a script called ./foo.sh
My process ID is 11373
... and I have changed directory to /tmp/demo
TESTVAR1 started off as ''
and TESTVAR2 started off as 'hens'
Now I have changed TESTVAR1 to 'changed 1'
$ pwd
/home/moopet
$ echo $TESTVAR1
elephants
So what happened?
Let's break this down.
- The script ran in a subshell (the process IDs of the parent and child were different)
- it did not automatically inherit its parent's environment (
TESTVAR1
was not populated) - It did inherit
TESTVAR2
because that was explicitly exported1 - Environment variables it set or modified appeared unchanged once control returned to the parent shell (
TESTVAR1
remained "elephants") - The parent shell's working directory remained unchanged even though the subshell switched to a different one (/home/moopet vs /tmp/demo)
Option 2: Sourcing
source
is usually available as an alias for .
(which is a dot).
We'll call it "sourcing" regardless of which we use. source
is a clearer thing to write, but .
is the one that's guaranteed to work by POSIX. That means that .
will work on any shell that claims to be POSIX-compliant, whereas source
might not.
Now, as we've seen, foo.sh
has a shebang of #!/bin/sh
, but that doesn't matter. The nice thing about these special first lines is that they start with a hash, and a hash mark denotes a comment in virtually all scripting languages, so it gets ignored. Clever, huh.
A side effect of this is that since it isn't being executed, it doesn't need the execute bit set. No chmod
ding required.
Sourcing will run in the context of the current shell. It will have read- and write-access to the current environment and will execute as the current user.
It will also, perhaps unexpectedly given what I've just described, have access to the $1
, $2
, etc. positional parameters exactly as if it had been executed.
You can't sudo source
something, and you can't use setuid
on a script you source.
Let's go, again.
$ pwd
/home/moopet
$ echo $$
8192
$ TESTVAR1="elephants"
$ export TESTVAR2="hens"
$ . ./foo.sh
I am a script called ./foo.sh...
My process ID is 8192...
... and I have changed directory to /tmp/demo
TESTVAR1 started off as 'elephants'
and TESTVAR2 started off as 'hens'
Now I have changed TESTVAR1 to 'changed 1'
$ pwd
/tmp/demo
$ echo $TESTVAR1
changed 1
So what happened this time?
Let's break this down.
- The script ran in the same shell (the process IDs the script saw was the same as the interactive session)
- it had full read access to its environment (
TESTVAR1
andTESTVAR2
were both populated) - Environment variables it set or modified stayed set once interactive control returned (
TESTVAR1
remained "changed 1") - The interactive shell's working directory stayed changed to that set in the script
Conclusion: When to use what
If you need to affect the current shell, with something like an autocomplete extension, a prompt customisation or loading a set of aliases, go with sourcing.
If you want to guarantee what shell is executing the code, and to keep variables in their own separate space, go with execution.
--
Photo by Magdalena Kula Manchee on Unsplash
-
You don't have to use this syntax to export variables, you can export separately from the assignment, or you can define them on the same line as the script name, like
TESTVAR2="hens" ./foo.sh
if you want. ↩
Top comments (2)
This is a reminder for me that I really do need to understand more shell stuff. Thanks Ben, that actually explains a lot (which might be a bit embarrassing for me!)
I feel this might be more of a brain dump than a tutorial, so if I can make anything clearer, shout on me!