mention temporary files (somebody should write this)
[wiki.git] / doc / safe-shell.mdwn
1 [[!meta title="Writing Safe Shell Scripts"]]
2
3 Writing shell scripts leaves a lot of room to make mistakes, in ways that will
4 cause your scripts to break on certain input, or (if some input is untrusted)
5 open up security vulnerabilities. Here are some tips on how to make your shell
6 scripts safer.
7
8 ## Don't
9
10 The simplest step is to avoid using shell at all. Many higher-level languages
11 are both easier to write the code in in the first place, and avoid some of the
12 issues that shell has. For example, Python will automatically error out if you
13 try to read from an uninitialized variable (though not if you try to write to
14 one), or if some function call you make produces an error.
15
16 One of shell's chief advantages is that it's easy to call out to the huge
17 variety of command-line utilities available. Much of that functionality will be
18 available through libraries in Python or other languages. For the handful of
19 things that aren't, you can still call external programs. In Python, the
20 [subprocess](http://docs.python.org/2/library/subprocess.html) module is very
21 useful for this. It also has two big advantages over shell — it's a lot
22 easier to avoid
23 [word-splitting](http://www.gnu.org/software/bash/manual/html_node/Word-Splitting.html)
24 or similar issues, and since calls to subprocess will tend to be relatively
25 uncommon, it's easy to scrutinize them especially hard.
26
27 ## Shell settings
28
29 POSIX sh and especially bash have a number of settings that can help write safe shell scripts.
30
31 I recommend the following in bash scripts:
32
33     set -euf -o pipefail
34
35 In dash, `set -o` doesn't exist, so use only `set -euf`.
36
37 What do those do?
38
39 ### [`set -e`](http://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html)
40
41 If a command fails, `set -e` will make the whole script exit, instead of just
42 resuming on the next line. If you have commands that can fail without it being
43 an issue, you can append `|| true` or `|| :` to suppress this behavior —
44 for example `set -e` followed by `false || :` will not cause your script to
45 terminate.
46
47 ### [`set -u`](http://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html)
48
49 Treat unset variables as an error, and immediately exit.
50
51 ### [`set -f`](http://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html)
52
53 Disable filename expansion (globbing) upon seeing `*`, `?`, etc..
54
55 If your script depends on globbing, you obviously shouldn't set this. Instead,
56 you may find
57 `[shopt](http://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html)
58 -s failglob` useful, which causes globs that don't get expanded to cause
59 errors, rather than getting passed to the command with the `*` intact.
60
61 ### [`set -o pipefail`](http://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html)
62
63 `set -o pipefail` causes a pipeline (for example, `curl -s http://sipb.mit.edu/
64 | grep foo`) to produce a failure return code if any command errors. Normally,
65 pipelines only return a failure if the last command errors. In combination with
66 `set -e`, this will make your script exit if any command in a pipeline errors.
67
68 ## Quote liberally
69
70 Whenever you pass a variable to a command, you should probably quote it.
71 Otherwise, the shell will perform
72 [word-splitting](http://www.gnu.org/software/bash/manual/html_node/Word-Splitting.html)
73 and
74 [globbing](http://www.gnu.org/software/bash/manual/html_node/Filename-Expansion.html),
75 which is likely not what you want.
76
77 For example, consider the following:
78
79     alex@kronborg tmp [15:23] $ dir="foo bar"
80     alex@kronborg tmp [15:23] $ ls $dir
81     ls: cannot access foo: No such file or directory
82     ls: cannot access bar: No such file or directory
83     alex@kronborg tmp [15:23] $ cd "$dir"
84     alex@kronborg foo bar [15:25] $ file=*.txt
85     alex@kronborg foo bar [15:26] $ echo $file
86     bar.txt foo.txt
87     alex@kronborg foo bar [15:26] $ echo "$file"
88     *.txt
89
90 Depending on what you are doing in your script, it is likely that the
91 word-splitting and globbing shown above are not what you expected to have
92 happen. By using `"$foo"` to access the contents of the `foo` variable instead
93 of just `$foo`, this problem does not arise.
94
95 When writing a wrapper script, you may wish pass along all the arguments your
96 script received. Do that with:
97
98     wrapped-command "$@"
99
100 See ["Special Parameters" in the bash
101 manual](http://www.gnu.org/software/bash/manual/html_node/Special-Parameters.html)
102 for details on the distinction between `$*`, `$@`, and `"$@"` — the first
103 and second are rarely what you want in a safe shell script.
104
105 ## Temporary files
106
107 TODO: mumble `mktemp`?
108
109 ## Conclusion
110
111 When possible, instead of writing a "safe" shell script, *use a higher-level
112 language like Python*. If you can't do that, the shell has several *options* that
113 you can enable that will reduce your chances of having bugs, and you should be
114 sure to *quote liberally*.