What sourcery is this?

Ben Sinclair - Jun 14 '20 - - Dev Community

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 chmodding 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
Enter fullscreen mode Exit fullscreen mode

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 and TESTVAR2 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


  1. 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. 

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .