top | item 24401085

Bash Pitfalls

288 points| aaron-santos | 5 years ago |mywiki.wooledge.org | reply

111 comments

order
[+] nikisweeting|5 years ago|reply
I consider shellcheck absolutely essential if you're writing even a single line of Bash. I also start all my scripts with this "unofficial bash strict mode" and DIR= shortcut:

    #!/usr/bin/env bash
    
    ### Bash Environment Setup
    # http://redsymbol.net/articles/unofficial-bash-strict-mode/
    # https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html
    # set -o xtrace
    set -o errexit
    set -o errtrace
    set -o nounset
    set -o pipefail
    IFS=$'\n'

    DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
I have more tips/tricks here: https://github.com/pirate/bash-utils/blob/master/util/base.s...
[+] abathur|5 years ago|reply
I've had a thought percolating (for many months?) but I haven't tried to phrase it and I'm leery it may sound condescending (this is also more of a public address than direct response) ...

Shellcheck tends to steer us away from "weird" parts of Bash/shell and will happily suggest changing code that was correct as written. There are good reasons for this (and I still think most scripts/projects should use Shellcheck), but Bash is a weird language, and there's a lot of power/potential in some of the weird stuff Shellcheck walls off.

If I had to nail down some heuristics for working with it...:

- do lean heavily on Shellcheck for writing new code if you're unfamiliar with the language or are only writing it under duress

- don't implement its suggestions in bulk (the time you save on iterating can easily be blown later trying to debug subtle behavior changes)

- don't apply them to code you don't understand (if you have the time and interest to understand it but find yourself stuck on some un-searchable syntax, explainshell may help)

- don't adopt them without careful testing

- do take Shellcheck warnings about code that is correct-as-written as a hint to leave a comment explaining what's going on

- do leave Shellcheck off (or only use it intermittently) if you're trying to explore, play, or otherwise learn the language

[+] rgrau|5 years ago|reply
You've a lot of cool stuff in that repo! I hope you don't mind me 'stealing' some stuff for a kind of booklet I'm working on with several shell tips and tricks.

https://github.com/kidd/scripting-field-guide/

PS: Any comments/PRs are super welcome there too

[+] draebek|5 years ago|reply
Note that this same wiki has a good page on the numerous surprising behaviors of errexit: https://mywiki.wooledge.org/BashFAQ/105

My perception is that there is no consensus on whether or not to use it. I go back and forth, but I probably use it more often than not.

[+] nikisweeting|5 years ago|reply
Here's my full bash reading list:

- ShellCheck: life-changing BASH linter and testing toolkit https://github.com/koalaman/shellcheck

- 30 interesting commands for the Linux shell – Víctor López Ferrando https://www.lopezferrando.com/30-interesting-shell-commands/

- 7 Surprising Bash Variables https://zwischenzugs.com/2019/05/11/seven-surprising-bash-va...

- anordal/shellharden https://github.com/anordal/shellharden/blob/master/how_to_do...

- barryclark/bashstrap https://github.com/barryclark/bashstrap

- BashPitfalls : Greg's Wiki http://mywiki.wooledge.org/BashPitfalls

- Common shell script mistakes http://www.pixelbeat.org/programming/shell_script_mistakes.h...

- Comparison of all the UNIX shells http://hyperpolyglot.org/unix-shells

- Defensive Bash Programming https://www.kfirlavi.com/blog/2012/11/14/defensive-bash-prog... or https://jonlabelle.com/snippets/view/markdown/defensive-bash...

- Bash FAQ and Cookbook https://mywiki.wooledge.org/BashFAQ

- Detecting the use of "curl | bash" server side https://idontplaydarts.com/2016/04/detecting-curl-pipe-bash-...

- Gensokyo Blog - Use Bash Builtins shell,fish,bash https://blog.gensokyo.io/a/fafbe742.html

- Rich’s sh (POSIX shell) tricks http://www.etalabs.net/sh_tricks.html

- Shell Scripts Matter https://dev.to/thiht/shell-scripts-matter

- Shell Style Guide https://google.github.io/styleguide/shell.xml

- Shellcode Injection - Dhaval Kapil https://dhavalkapil.com/blogs/Shellcode-Injection/

- Something you didn't know about functions in bash http://catonmat.net/blog/bash-functions

- Ten More Things I Wish I’d Known About bash https://zwischenzugs.com/2018/01/21/ten-more-things-i-wish-i...

- Ten Things I Wish I’d Known About bash https://zwischenzugs.com/2018/01/06/ten-things-i-wish-id-kno...

- Testing Bash scripts with BATS https://opensource.com/article/19/2/testing-bash-bats

- Testing Bash scripts with Critic.sh https://github.com/Checksum/critic.sh

- Useful BASH and UNIX commands https://cb.vu/unixtoolbox.xhtml

- When Bash Scripts Bite :: Jane Street Tech Blogs https://blogs.janestreet.com/when-bash-scripts-bite/

- Bashible: Ansible-like framework for bash-based devops https://github.com/mig1984/bashible

- Auto-parse help text from comment at the top of script https://samizdat.dev/help-message-for-shell-scripts/

- Make bash scripts safer by writing them in Rust https://github.com/rust-shell-script/rust_cmd_lib

- Additional shell options for non-trivial bash scripts https://saveriomiroddi.github.io/Additional-shell-options-fo...

[+] rantwasp|5 years ago|reply
set -euxo pipefail
[+] BiteCode_dev|5 years ago|reply
Same, but I also do "shopt -s dotglob" so that * includes hidden files.
[+] ausjke|5 years ago|reply
errtrace is new to me, otherwise I do similar things.

set -ueEo pipefail

[+] zests|5 years ago|reply
Bash is one of my favorite programming languages. Not for production. Writing significant production Bash that others have to use and maintain is more of a struggle than it is worth.

But for personal use? Hitting APIs, organizing data, writing to files... Bash has been enjoyable because it requires much more creativity than almost any other language. At some point it's just you and the coreutils against the world.

[+] bor100003|5 years ago|reply
I have the same feeling. In most cases you do the job without any external libs which is rarely the case with other programming languages.
[+] Ambol|5 years ago|reply
These type of things is why I always use a real programming language rather than shell scripting. Can't wrap my head around all the different pitfalls of treating random string outputs as input for other commands without first validating and parsing them into an actual data structure.
[+] as-j|5 years ago|reply
Shell scripting has it’s place. In general it works fairly well and it’s an important skill to have.

I used to have a report who had a similar train of thought, and ended up writing shell scripts in python which was way more fragile, unreadable and more complicated.

The edge cases are there, but you don’t run into them all that often. Or if you do, you fix your scripts. Take the cp —- 2nd example. You generally don’t start file names with a -. It just makes Unix life annoying so you get pushed away from doing it.

Keep scripts sane and straight forward, you can do really nice and powerful work knowing Unix tools. You can also shoot yourself in the foot some day when you create a file called ‘-rf’.

[+] jeremysalwen|5 years ago|reply
Many people have responded to your comment saying that this is bad and that using other languages makes things more complicated when you are doing 'bash-like' things. I'm guessing they haven't actually tried it, because I have used python with the [sh](https://github.com/amoffat/sh) package many times to replace bash scripts, and I have never regretted it.
[+] nijave|5 years ago|reply
Yup and something rarely mentioned is lack of good testing frameworks and all the pitfalls when you try to make something portable

I will admit Python doesn't have great, simple primitives for shell-like tasks but you can create a few functions to make a simple sh("") wrapper.

With Ruby's backtick execution syntax you can blend in shell/system utilities pretty easily. Unfortunately, Ruby isn't installed on as many systems by default, afaik

Here's a Ruby equivalent of looping through mp3 files in the current directory

```

Dir["*.mp3"].each do |file|

  puts file # print the file name
end

```

[+] bluedino|5 years ago|reply
I find a lot of times someone extends or duplicates a bash script and soon gets over their head in complexity, or doesn’t realize how much easier a Ruby/Python/whatever script would be to change, mostly because it’s so much more readable.

Bash has all kinds of pitfalls and things like checking if a file exists vs if it’s larger than 0 bytes.

Then you get people who instead use php to do scripts. Ugh.

[+] rantwasp|5 years ago|reply
yeah no. you should start with bash and drop into a proper programming language when complexity warrants it
[+] jayd16|5 years ago|reply
What kind of parsing do you think can be done in "a real programming language" that can't be done in bash?

I like objects and performance and abstraction and other things about other languages but bash has a _lot_ of parsing tools.

[+] renewiltord|5 years ago|reply
What frameworks/libraries do you use? I find that the reason I prefer using a shell script is:

* ergonomic integration with the shell

* easy piping

* easy input/output redirection

Maybe Ruby comes close with the backticks or `system` but it still isn't as nice as a shell IMHO

[+] Spivak|5 years ago|reply
The cost you pay for this is verbose and unergonomic interop when your problem domain is executing a bunch of commands. For all its warts shells shine at being extremely expressive.

It’s usually not worth the effort to shove everything into a real programming language but write a few self-contained commands designed to be called from a shell and then use the shell as the glue.

[+] amirkdv|5 years ago|reply
Years ago I spent a lot of time (arguably too much time) writing (ba)sh scripts and this document was a great resource! But my main lesson from that period was that bash pitfalls are readability/maintenance nightmares, confounded by the fact that most people don't know about most of the pitfalls. To make matters worse, you can't know which pitfalls are or aren't known to the original author / future maintainer of the script in front of you.

But I'm not sure to what extent the language warts are inevitable consequences of what makes it so expressive and powerful. For example: unix pipes are amazing and the bash syntax for them is quite expressive. I can reproduce the same behavior in any of the more "sane" languages but I find that the equivalent code is a less readable procedural soup of string munging, control flow, and error control.

[+] rashkov|5 years ago|reply
Is there a greater principle behind all of these pitfalls, a mental model that I can learn and use to avoid these kinds of mistakes? Or is it sort of like CSS where years and years of ad-hoc development make it an exercise in memorizing edge-cases?
[+] Spivak|5 years ago|reply
Some off the top of my head.

* On Linux file and directory names can be anything except NUL. Because of how much software already assumes they’re UTF-8 you can probably safely assume it too and not run into much trouble. But since bash basically just does some text substitutions and then calls eval you have to be defensive.

* Know how bash parses front and back. Almost all the pitfalls are unexpected consequences of the parser not behaving how you expect. https://web.archive.org/web/20200124085320/http://mywiki.woo...

[+] chubot|5 years ago|reply
The most important thing for understanding shell (without loads of special cases) is understanding the Unix kernel, as well as the kernel interface that the shell uses (i.e. which syscalls).

If you strace the shell, that goes a long way. If you haven't used strace or don't know what it is, I recommend learning.

These comics give more details, and so do Julia Evan's other comics (I think the first one was about strace):

Three Comics For Understanding Unix Shell

http://www.oilshell.org/blog/2020/04/comics.html

For example this will help you understand that filenames and argv arrays are byte strings in Unix, which may be different than the notion of strings in your programming language.

-----

That will go a long way. However there is still a ton of accidental and needless complexity in bash, particularly related to string processing, and which is unrelated to the kernel. Hence the Oil project:

http://www.oilshell.org/blog/2020/01/simplest-explanation.ht...

[+] ufo|5 years ago|reply
I think it is different than CSS here. With CSS I could never really understand how it worked and each new browser released introduced new concepts that added more and more corner cases to the mix. On the other hand, with shell scripting after a while I got a decent mental model of how things worked. It is just that there are some bad design choices that make it very easy to shoot yourself on the foot.

For example, word splitting "feature" of POSIX shell is the source of many of the pitfalls in the list and the solution in those cases is always the same -- always quote your variables.

By the way, check out Shellcheck if you haven't already. It can detect many of these common pitfalls and the error messages have a link to the explanation so it also works as a learning tool.

[+] mey|5 years ago|reply
https://web.archive.org/web/20200907092725/https://mywiki.wo...

Since the site is being hugged a little too much.

I also really like https://github.com/koalaman/shellcheck

[+] INTPenis|5 years ago|reply
Seeing this wiki hugged to death by HN makes me realize that it would be perfect for migrating to some simple cloud hosting and using static markdown in git.

All their maintainers are already technical enough to handle doing updates in git. And with certain static doc generators like mkdocs you can have an Edit link in each page that brings the user to a Gitlab repo with an editor and Preview.

[+] scns|5 years ago|reply
Shellcheck and IdeaVim are the first plugins i install on Jetbrains IDEs
[+] 1vuio0pswjnm7|5 years ago|reply
"13. cat file | sed s/foo/bar/ > file

   You cannot ..."
With sed, in some cases, you can. One of the differences between Linux-provided sed and BSD-provided sed is the -a option.

FreeBSD manpage:

"-a The files listed as parameters for the "w" functions are created (or truncated) before any processing begins, by default. The -a option causes sed to delay opening each file until a command containing the related "w" function is applied to a line of input."

So let's say you have a file

   cat > file
   sandfoo
   ^D
 
BSD-provided sed

   cat file|sed -na 's/foo/bar/;H;$!d;g;w'file 
The replacement is made without any temp file.

Note this example will add a blank line at the top.

Why use sed? sed is part of BSD base system, the toolchain^1 and install images thus it can easily be utilised early in boot process. I am not aware that "sponge" is part of any base system.

1. NetBSD's toolchain can be reliably cross-compiled on Linux.

[+] koala_man|5 years ago|reply
An easier way to do the same thing but for any tool is `cat file | sed 's/foo/bar/' | sponge file`
[+] samoa42|5 years ago|reply
what is wrong with the '-i' option?

-i[SUFFIX], --in-place[=SUFFIX] edit files in place (makes backup if SUFFIX supplied)

[+] simias|5 years ago|reply
Most of the pitfalls described in this doc are /bin/sh pitfalls, not bash per-se.

On that note, let me rant: stop writing bash scripts people. If POSIX shell is not good enough, it's a good sign that you should move to a more expressive language with better error handling. Bash is a crappy compromise: it's not as portable as POSIX but it's also a very crappy scripting language.

You might say I'm a pedant because "bash is everywhere nowadays anyway, your /bin/sh might even be bash in disguise" but I'll be the one having the last laugh the day you have to script something in some busybox or bash-less BSD environment. Knowing how to write portable shell scripts is a skill I value a lot personally, but that might be because I work a lot with embedded systems.

Also if you assume that your target system will always have bash, there's a very good chance that it'll also have perl and/or python too. So no excuses.

[+] scipute68|5 years ago|reply
This is basic nonsense. Design your scripts and provision your environments. _You_ can stop writing bash scripts. I find them eminently useful and I control/provision the environments they run in. If you are not in control then do what you are told. Most people would puke if I told them that a lot of csh is alive and well in sci-prod environments but that is the way it are. You _do_ tailor your environment bc that is the way *nix was designed. Controlling people and peoples habits is faang land.
[+] bonoboTP|5 years ago|reply
The biggest transformative learning experience for me in Unix command gluing was to understand argument vectors. That a command execution is not just one big string, but will at the point of calling the command, be transformed by Bash to a vector of strings, to be passed on to the execve system call.

The point is, all those quotes etc. are syntax we use to tell the Bash I terpreter what the strings passed on should be. Think in terms of the string vector and not in terms of the one big string in front of your eyes that is a line of Bash code. The latter is a recipe for memorizing magic incantations and cargo cult sprinkling of quotes here and there.

The horrible thing with Bash is that while normal programming languages make the difference crystal clear between the name of a function being called and its mandatorily quoted string args neatly separated with commas, Bash will come up with this split on the spot using arcane rules.

Letting a string automatically fall apart into multiple argemts on the spot may have seemed convenient in the old days for forwarding invocations, but it seems clear to me that this was a design mistake.

In the end if you want to have robust Bash code you have to keep track of string splitting and will almost never rely on the default behavior of letting Bash "helpfully" pre-digest and split things up for you.

Much of Bash expertise is then about how to fight vigorously against the default behavior of silently chunking up your args. But fighting really strong means using e. g. arrays with ugly syntax, and other magic looking constructs. It shouldn't take so much alertness to just keep to sane behavior. The problem is, the interpreter is actively hostile to your endeavor and will explicitly try to screw with you.

[+] slavik81|5 years ago|reply
I was writing a script recently and I was going to use `cp -- "$src" "$dst"` but neither `man cp` nor `cp --help` mentioned `--` as an option. I'm not sure I'm using info right, but it doesn't seem to be mentioned in `info cp`, either.

Is that feature described somewhere else, or is it just undocumented?

[+] JdeBP|5 years ago|reply
The -- mechanism in general, not specific to the cp command albeit mostly limited to programs that use getopt/popt for their argument parsing, is described all over the place. I first encountered it in a book, written by Eric Foxley, entitled UNIX for Super-Users and published in 1985.

* It's perennially asked about on Unix and Linux Stack Exchange, ranging from https://unix.stackexchange.com/q/11376/5132 almost a decade ago to https://unix.stackexchange.com/q/570729/5132 last year.

* It's in a whole bunch of duplicate questions there, including (but not limited to) https://unix.stackexchange.com/questions/linked/1519?lq=1 .

* It's in the Single Unix Specification, and has been for a long time. (https://pubs.opengroup.org/onlinepubs/7990989775/xbd/utilcon...)

* It's in the getopt(3) manual page from the Linux man-pages project. It's in the getopt(1) manual page from the util-linux project.

* It's in getopt(1) and getopt(3) in the NetBSD Manual. The same goes for the OpenBSD and FreeBSD manuals.

* It's in Blum's and Bresnehan's 2015 Linux Command Line and Shell Scripting Bible, on page 377.

* It's in Marcel Gagné's 2002 Linux System Administration: A User's Guide, on page 48.

* It's in Kirk Waingrow's 1999 UNIX Hints & Hacks.

* It was Q&A number 32 in part 2 of the Usenet comp.unix.shell FAQ document. (http://cfajohnson.com/shell/cus-faq-2.html#Q32)

Specific to the cp command, or rather to the many cp commands that you could end up using, as this is not a "bash command":

* No, the OpenBSD, FreeBSD, and NetBSD cp(1) manuals do not mention it.

* Neither does the GNU CoreUtils cp(1) manual page, as you noted. It's instead in the GNU CoreUtils doco section on "common options". The actual CoreUtils cp(1) manual doesn't mention that there even are "common options". (https://manpages.debian.org/sid/coreutils/cp.1.en.html) That's only mentioned, as an aside, in the info page. (https://gnu.org/software/coreutils/manual/html_node/cp-invoc...)

* For the cp command that is part of Rob Landley's toybox, this is covered in the introduction to the toybox(1) manual page, and there isn't really a distinct cp(1) manual page. (http://landley.net/toybox/help.html)

* No, BusyBox doesn't similarly document this for its cp command, or anywhere on busybox(1). (https://busybox.net/downloads/BusyBox.html)

* It's tangentially in several GNU CoreUtils FAQ Q&As, inasmuch as CoreUtils cp is the same as CoreUtils rm in this regard. (https://gnu.org/software/coreutils/faq/coreutils-faq.html#Ho...)

* It's tangentially in the Linux Documentation Project's Advanced Bash-Scripting Guide, again because cp is the same as rm here. (https://tldp.org/LDP/abs/html/special-chars.html#DOUBLEDASHR...)

* For an operating system where this is actually in the cp(1) manual itself, look to Solaris, and its successor Illumos, where you'll find it in the NOTES section. (https://illumos.org/man/1/cp)

* Another operating system where this is actually in the cp(1) manual itself is AIX, actually in the FLAGS table alongside all of the other flags. (https://ibm.com/support/knowledgecenter/ssw_aix_72/c_command...)

[+] bluedays|5 years ago|reply
Oh neat. I found a problem with a script I wrote today because of this.

I wrote a script recently that takes several screenshots from the same folder, merges them using Image Magick and outputs the merged image into a PDF. Found out through the first tip that I shouldn't be using ls: https://mywiki.wooledge.org/BashPitfalls#for_f_in_.24.28ls_....

[+] chousuke|5 years ago|reply
One new pitfall I learned yesterday is that sourcing a file searches $PATH. I ran a tool that did ". config" on OpenBSD and it tried to source config(8) instead of its configuration in the local directory. Oops.
[+] rurban|5 years ago|reply
I have almost no bash scripts.

Whenever sh is not good enough and I have to switch to bash, I'd rather rewrite it in proper Perl. Bash really looks like a hack, and not all environments have bash. Everybody has a conforming sh.

[+] LockAndLol|5 years ago|reply
I was half-expecting a page with the bash grammar. It's super easy to make mistakes in that language. I'm glad there are things like xonsh around trying to act as sane alternatives.
[+] znpy|5 years ago|reply
It's both interesting to read and meh.

It's meh because like many things, you actually have to read the fine manual if you want stuff to actually work.

It's interesting because it's always nice to have a refresher and/or reminder of various bash things.

That being said... I've said this many times, I'll say it again: bash scripts are software, and thus must not be exempted from various programming best practices. The first thing that comes to my mind is input validation and error checking.

Bash is not magical. It's one of those things that have to be mastered through practice... Just like many other programming languages.

[+] inoffensivename|5 years ago|reply
It would be easier to list all the constructs in bash that are well-behaved and intuitive.
[+] zerofrancisco|5 years ago|reply
Excellent caveats and suggestions to do good bash. Thank you! :)