Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save toraritte/64ffbd187ec3b9929d07524a9554ecbb to your computer and use it in GitHub Desktop.
Save toraritte/64ffbd187ec3b9929d07524a9554ecbb to your computer and use it in GitHub Desktop.

Is it a good practice to unset all variables at the end of a shell script?1

(See other opinions in this Unix & Linux thread with the same title.)

It depends on your use case(s) and intention(s) because a shell script can be run in different ways (see "1. Ways to call shell scripts" section below) which affect whether the variables are retained by the origin shell (the shell the script is called from) or not.

For example, a couple of cases for and against unsetting variables:

  • Bad, if the point of the script is to set environment variables3

    Then again, this also depends on how the shell script is called (see section 1.).

  • Bad, if the script is intended to expose shell variables for some purpose.

    For example, the script is a "module" in a larger "shell script suite", and other shell scripts depend on the variables set by the called "sub-scripts". (The same caveat applies as in the previous item; see section 1.)

  • Good, if one wants to protect ignorant users (myself included) AND ->if the previous points don't apply<- (many times they do not).

    NOTE: ignorant = less knowledgeable

    For example, an "unset section", where all the shell variables are enumerated (see example) saves me time and future anguish; I don't write shell scripts every day, and even though I leave excessive amount of notes to myself when I do, muscle memory in my hands doesn't care that I'm running the script in a way that will result in long debugging sessions (once again, see "1. Ways to call shell scripts" section below). Also, if the scripts are expected to be used by others, even good documentation doesn't help, because that is usually ignored (my ignorant self included).

  • (What else am I missing?)

1. Ways to call shell scripts

TL;DR

Shell script calling method Preserves variables / exports section described in
<interpreter> <file_name> no 1.1
. yes 1.2 (workaround in 1.2.1)
executable script no 1.3
source yes 1.4 (workaround in 1.2.1)

1.1 <interpreter> <file_name>

For example, if the script is called script.sh and it is called from its containing directory:

  •     sh script.sh
  • bash script.sh
  •   zsh script.sh
  • fish script.sh

This method will start a subshell (Advanced Bash Scripting Guide: Chapter 21. Subshells), that is a new shell in a child process, which means that

shell and environment variables will be discarded after the script has finished running, therefore the shell the script is called from remains unaffected.

If the point of the script is to set environment variables to be used in the current shell, then use:

Relevant thread: stackoverflow: Preserve environments vars after shell script finishes

. will "execute commands in the current environment" (POSIX standard, dot man page) without starting a subshell.

Shell and environment variables will remain in effect after the script has finished running.

If the same script is run again, it may lead to unexpected behaviour. For example, given the script below (let's call it test.sh),

#!/usr/bin/env bash

# https://unix.stackexchange.com/questions/129391/passing-named-arguments-to-shell-scripts
while [ $# -gt 0 ]; do
  case "$1" in

    --option_a|-a)
      OPTION_A=$2
      ;;
    --option_b|-b)
      OPTION_B=$2
      ;;
    *)
      printf "***************************\n"
      printf "* Error: Invalid argument.*\n"
      printf "***************************\n"
      exit 1
  esac
  shift
  shift
done

# Setting default value if script is not called with `--option`
OPTION_A="${OPTION_A:-"default A"}"
OPTION_B="${OPTION_B:-"default B"}"

echo "OPTION_A: ${OPTION_A}"
echo "OPTION_B: ${OPTION_B}"

if it is run via methods described in sections 1.1 and 1.3, all is fine:

$ chmod +x test.sh
$ ./test.sh
OPTION_A: default A
OPTION_B: default B

$ bash test.sh -a 27
OPTION_A: 27
OPTION_B: default B

$ ./test.sh      
OPTION_A: default A
OPTION_B: default B

However using . or source (see section 1.4 below), it will mess with assumptions, if one hopes use a variable's default value if a command line options is not provided:

# source test.sh === . ./test.sh

$ source test.sh
OPTION_A: default A
OPTION_B: default B

$ . ./test.sh -a 27 
OPTION_A: 27
OPTION_B: default B

$ source test.sh      
OPTION_A: 27
OPTION_B: default B

1.2.1 How to use . (and source) without messing up the current shell?

Use parentheses (( and )), and the commands between them will be executed in a subshell (which is the same way as with the methods in sections 1.1 and 1.3).

For example, ( source test.sh ).

Relevant docs and threads:

1.3 Set the executable bit on the script file

See description in "1.1 <interpreter> <file_name>" above.

Relevant thread: unix&linux: Why does Bash's source not need the execution bit?

For example, if the script is called script.sh and it is called from its containing directory:

$ chmod +x script.sh

$ ./script.sh

1.4 source command

A synonym for the dot (.) command (see " 1.2 The dot (.) POSIX standard command" section above).

As far as I can tell, the source synonym exists in bash, fish, and zsh, but probably in other shells as well, but it is better to use . for portability.

Relevant thread: unix&linux: Why was the dot (.) used as an alias for source & why don't other commands have shortcuts too?


Footnotes

[1]: Reiterating the question, because the answers here focus on rm and that it doesn't unset variables. To sum up:

  • rm deletes files

  • unset variable_name

    The $, which is used for parameter expansion (GNU Bash docs, 3.5.3 Shell Parameter Expansion) in this case2, is not needed before variable_name; otherwise it will try to unset whatever variable_name refers to:

    $ abc=27
    $ def=abc
    
    $ echo "abc=${abc}; def=${def}"
    abc=27; def=abc
    
    #       v
    $ unset $def
    #       ^
    $ echo "abc=${abc}; def=${def}"
    abc=; def=abc
    
    #       V
    $ unset  def
    #       ^
    $ echo "abc=${abc}; def=${def}"
    abc=; def=
    

[2]: See also stackoverflow: What are the special dollar sign shell variables?

[3]: Good threads in this topic:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment