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
#!/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"
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!
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
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.
$ 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
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 (
TESTVAR1was not populated)
- It did inherit
TESTVAR2because that was explicitly exported1
- Environment variables it set or modified appeared unchanged once control returned to the parent shell (
- The parent shell's working directory remained unchanged even though the subshell switched to a different one (/home/moopet vs /tmp/demo)
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
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
$2, etc. positional parameters exactly as if it had been executed.
sudo source something, and you can't use
setuid on a script you source.
$ 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
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 (
TESTVAR2were both populated)
- Environment variables it set or modified stayed set once interactive control returned (
TESTVAR1remained "changed 1")
- The interactive shell's working directory stayed changed to that set in the script
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.shif you want. ↩