rm -rf "$STEAMROOT/"* rm -rf "$STEAMROOT/"* set -eu
on top of your bash scripts -- execution will stop on errors (non-zero retvals) and on undefined variables.[[ "$VAR" ]] && rm -rf "$VAR/*"
I think most of these issues stem from the fact that most developers that write shell scripts don't actually understand what they're doing, treating the script as a necessary annoyance rather than a component of the software.
Anyways, that is not anything like other programming languages. Checking in that way is error prone and not really an improvement (nor equivalent to set -o).
[[ "$DAEMON_PATH" ]] && rm -rf "$DEAMON_PATH/*"
See what I did there? It's an rm -rf /* bug because "checking variables" is not the answer.In other programming languages, if an identifier is mis-typed things will blow up. E.g., in ruby if I write:
daemon_path=1; if daemon_path; puts deamon_path; end
I get "NameError: undefined local variable or method `deamon_path`"These issues do not always stem from bad developers. Bash's defaults are not safe in many ways and saying "people should just check the variable" isn't helpful here.
set -u
Man page quote: "Treat unset variables and parameters other than the special parameters "@" and "*" as an error when performing parameter expansion. If expansion is attempted on an unset variable or parameter, the shell prints an error message, and, if not interactive, exits with a non-zero status."
some real-world programming languages don't have undefined variables :)
Being the emptys string "" would work just as well.
These bugs are indicative of Bash's design problems. Why is it used for init scripts? And don't even get me started on how Bash interprets filenames as part of the arguments list when using * (e.g. file named "-rf").
Say what you will about Powershell, but having a typed language that can throw a null exception is useful for bugs like these. The filename isn't relevant, and a null name on a delete won't try to clear out of the OS (just throw).
That's not Bash. That's just... programs in Unix. Such is life when everything is stringly typed.
Not just scot free - during the Great systemd War of 2014 is was a talking point for the antis that using anything other than the pure, reliable simplicity of shell for service management was MADNESS!
rm -r "${VAR:-var_is_not_set_so_please_fix_this_script}"
which substitutes the var_is_... if VAR is not set.BTW, I hate hate hate -f. It has two meanings: 1. 'force' the removal 2. ignore any error
I've seen an instance of this sort of bug in my sysadmin career that I remember. It was a Solaris patch which wiped a chunk of the system.
If you're suggesting using parameter expansion, at least suggest the correct one (i.e. one that will give a meaningful error message):
${parameter:?word}
http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3...set -e and set -o pipefail really should have been the default, rather than an opt-in.
Consider /tmp/test.sh:
set -o pipefail
yes foo | head
$ bash /tmp/test.sh >/dev/null
$ echo $?
141I've collated other mishandling of closed pipes at: http://www.pixelbeat.org/programming/sigpipe_handling.html
2. set -e
3. type an invalid command or run one that returns non-zero
4. "crap, where did my shell go?"
The real answer is that this has not been the default in the time between shells being invented and this comment being posted, and so the squillions of lines of shell script out there in the wild keeping the world turning have not been written with this in mind. Making it the default now would break a lot of things.
With the benefit of hindsight, though, i would say that yes, this should have been the default in scripts. Oh well.
http://mywiki.wooledge.org/BashFAQ/105
disagrees and refers to GreyCat's preference not to use -e at the bottom of the list of 'complications'.
You can use set -e, and turn it off (set +e) for code blocks and things that are problematic. He could also add '|| true', and you may be able to use colon to avoid point problems without turning everything off. These are edge cases and you can easily work around them if you an advanced user.
If you are not an advanced user then you should certainly use -e.
$ diff -u /tmp/a /tmp/b
--- /tmp/a 2015-03-24 08:33:00.021919797 -0400
+++ /tmp/b 2015-03-24 08:33:05.629963015 -0400
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
set -e
i=0
-let i++
+let i++ || true
echo "i is $i"
$ /tmp/a
$ /tmp/b
i is 1
$ `echo /* `: /bin /dev /etc /lib ...
`echo /*/`: /bin/ /dev/ /etc/ /lib/ ...
`echo "/*"`: /*
`echo "/*/"`: /*/
If you try it with `ls`, you'll find that `ls "/* "` results in `ls: "/* ": no such file or directory`.Edit: Formatting.
[1] https://github.com/MrMEEE/bumblebee-Old-and-abbandoned/issue...