3

Is it good to delete the variables used in a shell-script at the end of a script.

rm -rf $abc
rm -rf $def

or

unset $abc
unset $def

and so on.

Is this actually a good practice?

2
  • 13
    Or worse, depending on exactly what's in the variables (abc='5 * 3', def='-r /'). Apr 21, 2017 at 7:44
  • 15
    your are trying to delete files, not variables! Apr 21, 2017 at 8:10

5 Answers 5

25

This is a very bad practice.

rm deletes files. It is nothing to do with variables. In any case, the variables themselves will be disposed of when the script ends and the operating system reclaims the shell's memory.


In the simple case, a file with the same name as the value of one of the variables will be deleted by this construction:

abc=filename
rm -f $abc  # Deletes "filename" in the current directory

It gets worse. If abc and def contain the names of files as individual words separated by a space (or any other character of IFS), you will delete those files, and wildcards like * will be expanded too if they appear in any of the words.

abc='hello world'
rm -f $abc  # Deletes file "hello" and "world" (leaves "hello world" alone)
abc='5 * 3'
rm -f $abc  # Deletes all files, because * is expanded (!)
def='-r /'
rm -f $def  # Really deletes *all* files this user can access

Shell parameter expansion with $var is subject to word splitting, where every character of the IFS variable divides the variable into different arguments. Each word is then subject to filename expansion, which uses *, ?, and [abc...] patterns to create filenames. This could get very bad, depending on what your variables have in them. Do not do this.


There is no need to blank or unset variables at the end of a shell script in any way.

8
  • 1
    As an addition: the command to delete variables is unset. But it is not neccessary to unset them as they are not transferred to the calling shell, if you simply execute the script (you'd need to call . yourscript.sh if you would like to have the variables of the script in your currecnt shell environment)
    – Philippos
    Apr 21, 2017 at 8:03
  • Note that there is a protection against the latter (-r / ) on several current rm implementations (see unix.stackexchange.com/a/19548/2594 )
    – jlliagre
    Apr 21, 2017 at 10:26
  • I agree with the rm part, but not with the "_ the variables themselves will be disposed of when the script ends_" part; the after the script exits, the variables will still be set in the shell where the script ran.
    – toraritte
    Mar 24, 2023 at 12:52
  • @toraritte No, they won't, unless for some fleeting fraction of a second before the shell terminates and some convoluted concept of "ends". The executing shell is dead when the script is and has no independent existence. Mar 26, 2023 at 5:05
  • 1
    @markgraf See this thread. The gist is that if you run your script using . or source, then all the variables (exported or otherwise) will be available from the shell you ran your script. Otherwise (e.g., by calling it via an interpreter like bash) it will run in a subshell, and then it doesn't matter if you exported your variables or not as they will be discarded once the scripts exits (and thus the subshell ends).
    – toraritte
    Mar 26, 2023 at 20:31
12

I wonder if you actually mean shell variables, or temporary files.

If what you're doing is this (a temporary file):

tmp=$(mktemp)
something > "$tmp"
something else < "$tmp"
rm "$tmp"

Then sure, go ahead and remove the temp file after you're done. Though if the script crashes in the middle, the file will be left laying there, but that's not exactly uncommon either. You could use trap 'rm -f -- "$tmp"' EXIT to remove the file when the shell exits, if you care.

You should probably use rm -f -- "$tmp", as -fwould help in avoiding error messages if the file was e.g. removed earlier for some reason. No reason to use-r` when removing a single file, though.

(Also, remember to quote the variable, even though mktemp by default makes "nice" filenames.)


However, if you're doing this (a variable):

read var
do something with "$var"
rm "$var"

Then you don't want to do that: you have no file to remove, just something entered by the user. Shell variables cease to exist when the shell exits, so no need to unset them.


Though if your script is meant to be sourced from another shell (or it's in .bashrc or similar), the shell doesn't exit when your script ends. In that case it might be useful to unset any temporary variables you used at the end, to not leave them set for the rest of the shell's lifetime.

# in .bashrc
__hour=$(date +%H)
if [ "$__hour" -lt 12 ] ; then echo "Good Morning!" ; 
elif [ "$__hour" -gt 18 ] ; then echo "Good Evening!" ;
fi
unset __hour

The variable name might still collide with some variable used outside that script so some care must be taken with naming.

4
  • 1
    To avoid variable name collisions, the safest way is to use the positional parameters. e.g., set -- "$(date +'%H')" ${1+"$@"}; <do whatever with $1>; shift; We never need worry about whether __hour pre-exists in the environment where we are to source stuff.
    – user218374
    Apr 21, 2017 at 21:49
  • Also consider using the local builtin to keep variables within the scope of the function and its children without polluting the global environment. Dec 17, 2021 at 18:12
  • Without -f, rm may prompt the user which we likely don't want here. rm -f -- "$file" would be more correct and would have also have the benefit of not returning an error if the file was already gone. Mar 26, 2023 at 19:44
  • 1
    Note that in bash, unset may unset a function, if a variable by that name is not found. In that shell, it's a good habit to use unset -v var to make sure you unset a variable. Mar 26, 2023 at 19:45
1

I was just got bugged by an erroneous output from my shell script today. The reason is that, exported environment variables can actually creep-in into your shell script and cause nasty side-effects. Here is how:

#!/bin/sh
#
line="$line $1"
echo $line

When invoked as, myscript.sh smeagol, an already exported line variable creeps in. I have to unset line in my script entry to avoid pollution.

1
  • While the above might seem trivial, I mostly use the above construct to concatinate strings together. Hence, i learned the painful lesson why I must avoid it and instead I'm now using bash arrays if i'm in bash. Otherwise, you must first unset to ensure previously defined vars will not introduce nasty and hard to debug side-effects. It could drive you to the edge...
    – daparic
    Oct 22, 2018 at 16:03
0

No.rm doesn't deal with variables. If you wrote rm $abc it will delete the file in current directory with the name stored in variable abc.

Example: A variable abc stores value 52. and you performed rm $abc then it will delete file 52 in your current directory (use pwd command to print current directory and ls to list files in current directory).

Variables will be disposed automatically (at least in shell script) if you are not running it with . or source.

-1

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

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
(ASCII table because the site doesn't support markdown tables; see a better formatted version of this answer in this gist.)

|    Shell script calling     |   Preserves     | Relevant  |
|          method             | variables and   | section   |
|                             |    exports      |           |
| --------------------------- | --------------- | --------- |
| `<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:

1.2 The dot (.) POSIX standard command

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

    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=
    

    It can be a bit more complicated though: unix&linux: What does unset do?.

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

[3]: Good threads in this topic:

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .