shellharden/ at master · anordal/shellharden
Safe ways to do things in bash
Why bash?
Bash has arrays and a safe mode, which may make it just about acceptable under safe coding practices, when used correctly.
Fish is easier to use correctly, but lacks a safe mode. Prototyping in fish is therefore a good idea, provided that you know how to translate correctly from fish to bash.
This guide accompanies ShellHarden, but I also recommend ShellCheck: ShellHarden's rules shall not disagree with ShellCheck.
Bash is not a language where the correct way to do something is also the easiest. If there is anything like a driver's license for writing bash, it must be rule zero of BashPitfalls: Always use quotes.
The first thing to know about bash coding
Quote like a maniac! An unquoted variable is to be treated as an armed bomb: It explodes upon contact with whitespace. Yes, "explode" as in splitting a string into an array. Specifically, variable expansions, like $var, and also command substitutions, like $(cmd), undergo word splitting, whereby the contained string expands to an array by splitting it on any of the characters in the special $IFS variable, which is whitespace by default. This is mostly invisible, because most of the time, the result is a 1-element array, which is indistinguishable from the string you expected.
Not only that, but wildcard characters (*?) are also expanded. This process happens after word splitting, so that when a resulting word contains any wildcard characters, that word is now a wildcard pattern, expanding to any matching file paths you may happen to have. So this feature actually looks at your filesystem!
Quoting inhibits both word splitting and wildcard expansion, for variables and command substitutions.
Variable expansion:
Good: "$my_var"
Bad: $my_var
Command substitution:
Good: "$(cmd)"
Bad: $(cmd)
There are exceptions where quoting is not necessary, but because it never hurts to quote, and the general rule is to be scared when you see an unquoted variable, pursuing the non-obvious exceptions is, for the sake of your readers, questionable. It looks wrong, and the wrong practice is common enough to raise suspicion: Enough scripts are being written with broken handling of filenames that whitespace in filenames is often avoided…
The only exceptions honored by Shellharden are variables of numeric content, such as $?, $# and ${#array[@]}.
Should I use backticks?
Command substitutions also come in this form:
Correct: "`cmd`"
Bad: `cmd`
While it is possible to use this style correctly, it looks even more awkward in quotes and is less readable when nested. The consensus around this one is pretty clear: Avoid.
Shellharden rewrites these into the dollar-parenthesis form.
Should I use curly braces?
Bad: some_command $arg1 $arg2 $arg3
Extra bad (cargo culting unnecessary braces): some_command ${arg1} ${arg2} ${arg3}
Correct: some_command "${arg1}" "${arg2}" "${arg3}"
Better: some_command "$arg1" "$arg2" "$arg3"
In the "extra bad" and "correct" examples, braces compete with quotes under the limits of tolerable verbosity.
Shellharden will rewrite all these variants into the "better" form.
Braces on variable expansions are sometimes necessary (to limit the boundary of the variable name) if you absolutely want to include more string content within the same pair of quotes. This is always avoidable:
Good: "${var1}more string content$var2"
Good: "$var1""more string content""$var2"
Shellharden is neutral among these interpolation styles, but will pick the first one if asked to put down quotes anywhere.
Gotcha: Numbered arguments
Unlike normal identifier variable names (in regex: [_a-zA-Z][_a-zA-Z0-9]*), numbered arguments require braces, this time to extend the boundary of the variable name. ShellCheck says:
echo "$10"
^-- SC1037: Braces are required for positionals over 9, e.g. ${10}.
Shellharden will refuse to fix this (deemed too subtle).
Since braces are required above 9, Shellharden permits them on all numbered arguments.
Use arrays FTW
In order to be able to quote all variables, you must use real arrays when that's what you need, not whitespace separated pseudo-array strings.
The syntax is verbose, but get over it. This bashism is reason alone to drop posix compatibility for most shellscripts.
if [ ${#array[@]} -gt 0 ]; then
rm -- "${array[@]}"
pseudoarray=" \
a \
b \
pseudoarray="$pseudoarray c"
if ! [ "$pseudoarray" = '' ]; then
rm -- $pseudoarray
Those exceptional cases where you actually intend to split the string
Example with \v as delimiter (note the second occurence):
IFS=$'\v' read -d '' -ra a < <(printf '%s\v' "$s")
This avoids wildcard expansion, and it works no matter if the delimiter is \n. The second occurence of the delimiter preserves the last element if it's empty. For some reason, the -d option must come first, so putting the options together as -rad '', which is tempting, doesn't work. Tested with bash 4.2, 4.3 and 4.4.
Alternatively, for bash 4.4:
readarray -td $'\v' a < <(printf '%s\v' "$s")
How to begin a bash script
Something like this:
#!/usr/bin/env bash
if test "$BASH" = "" || "$BASH" -uc 'a=();true "${a[@]}"' 2>/dev/null; then
# Bash 4.4, Zsh
set -euo pipefail
# Bash 4.3 and older chokes on empty arrays with set -u.
set -eo pipefail
shopt -s nullglob globstar
This includes:
The hashbang:
Portability consideration: The absolute path to env is likely more portable than the absolute path to bash. Case in point: NixOS. POSIX mandates /bin/sh and the existence of env, but bash is not a posix thing. If you want to be 100% covered by posix, give up – wrap it in a posix script instead.
Safety consideration: No language flavor options like -euo pipefail here! It is not actually possible when using the env redirection, but even if your hashbang begins with #!/bin/bash, it is not the right place for options that influence the meaning of the script, because it can be overridden, which would make it possible to run your script the wrong way. However, options that don't influence the meaning of the script, such as set -x would be a bonus to make overridable (if used).
What we need from Bash's unofficial strict mode, with set -u behind a feature check. We don't need all of Bash's strict mode because being shellcheck/shellharden compliant means quoting everything, which is a level beyond strict mode. Furthermore, set -u must not be used in Bash 4.3 and earlier. Because that option, in those versions, treats empty arrays as unset, which makes arrays unusable for the purposes described herein. With arrays being the second most imporant advice in this guide (after quoting), and the sole reason we're sacrificing POSIX compatibility, that's of course unacceptable: If using set -u at all, use Bash 4.4 or another sane shell like Zsh. This is easier said than done if there is a possibility that someone might run your script with an obsolete version of Bash. Fortunately, what works with set -u will also work without (unlike set -e). Thus why putting it behind a feature check is sane at all. Beware of the presupposition that testing and development happens with a Bash 4.4 compatible shell (so the set -u aspect of the script gets tested). If this concerns you, your other options are to give up compatibility (by failing if the feature check fails) or to give up set -u.
shopt -s nullglob is what makes for f in *.txt work correctly when *.txt matches zero files. The default behavior (aka. passglob) – pass the pattern as-is if it happens to match nothing – is dangerous for several reasons. As for globstar, that enables recursive globbing. Globbing is easier to use correctly than find. So use it.
But not:
set -f
shopt -s failglob
Setting the internal field separator to the empty string disables word splitting. Sounds like the holy grail. Sadly, this is no complete replacement for quoting variables and command substitutions, and given that you are going to use quotes, this gives you nothing. The reason you must still use quotes is that otherwise, empty strings become empty arrays (as in test $x = ""), and indirect wildcard expansion is still active. Furthermore, messing with this variable also messes with commands like read that use it, breaking constructs like cat /etc/fstab | while read -r dev mnt fs opt dump pass; do echo "$fs"; done'.
Disabling wildcard expansion: Not just the notorious indirect one, but also the unproblematic direct one, that I'm saying you should want to use. So this is a hard sell. And this too should be completely unnecessary for a script that is shellcheck/shellharden conformant.
As an alternative to nullglob, failglob fails if there are zero matches. While this makes sense for most commands, for example rm -- *.txt (because most commands that take file arguments don't expect to be called with zero of them anyway), obviously, failglob can only be used when you are able to assume that zero matches won't happen. That just means you mostly won't be putting wildcards in command arguments unless you can assume the same. But what can always be done, is to use nullglob and let the pattern expand to zero arguments in a construct that can take zero arguments, such as a for loop or array assignment (txt_files=(*.txt)).
How to use errexit
Aka set -e.
Program-level deferred cleanup
In case errexit does its thing, use this to set up any necessary cleanup to happen at exit.
tmpfile="$(mktemp -t myprogram-XXXXXX)"
cleanup() {
rm -f "$tmpfile"
trap cleanup EXIT
Gotcha: Errexit is ignored in command arguments
Here is a nice underhanded fork bomb that I learnt the hard way – my build script worked fine on various developer machines, but brought my company's buildserver to its knees:
set -e # Fail if nproc is not installed
make -j"$(nproc)"
Correct (command substitution in assignment):
set -e # Fail if nproc is not installed
make -j "$jobs"
Caution: Builtins like local and export are also commands, so this is still wrong:
set -e # Fail if nproc is not installed
local jobs="$(nproc)"
make -j"$jobs"
… [more]
This  makes  bash  with  errexit  practically  incomposable    it  is  possible  to  wrap  your  errexit  functions  so  that  they  still  work_  but  the  effort  it  saves  (over  explicit  error  handling)  becomes  questionable.  Consider  splitting  into  completely  standalone  scripts  instead.  from iphone
may 2018
immersive linear algebra
The world's first linear algebra book with fully interactive figures.
math  algebra  linear  learning 
march 2017
Filing your taxes after exercising and holding private startup shares
If you’re reading this article, you’ve probably worked or are working at a private startup for more than a year, have exercised and held your employee incentive stock options, and received form 3921 from your employer. It’s time to file your taxes for the year, and you’re not sure what to do.
february 2017
An intuitive app to display transparent images on screen.
app  mac  design  development 
february 2017
Bullet Journal
The analog system for the digital age.
gtd  notebook  tasks  notes 
november 2016
Insomnia REST Client
A simple yet powerful REST API Client with cookie management, environment variables, code generation, and authentication for Mac, Window, and Linux
mac  app  http  api 
september 2016
Child development: A cognitive case for un‑parenting : Nature : Nature Research
Josie Glausiusz relishes Alison Gopnik's study on how child-rearing demands the embrace of messy realities.
august 2016
hemanth/functional-programming-jargon: Jargon from the functional programming world in simple terms!
functional-programming-jargon - Jargon from the functional programming world in simple terms!
javascript  programming  functional 
june 2016
Google Fonts — Lora
Lora is a well-balanced contemporary serif text typeface with roots in calligraphy. A paragraph set in Lora will make a memorable appearance because of its brushed curves counterposed with driving serifs.
fonts  lora  serif 
june 2016
Markov Chains
A visual explanation by Victor Powell
visualization  markov 
march 2016
xto6 - Transpile your codes to next-generation JavaScript
xto6 transpiles your boring ES5 code into readable ES6.
javascript  es2015 
december 2015
First Aid git
Quickly find the answers and to the most frequently asked git related issues.
git  help 
april 2015
Monte Carlo Methods, Stochastic Optimization
programming  statistics  montecarlo  stochastic 
april 2015
Graphic designer Marcus Gärde
Swedish Typography teacher and author Marcus Gärde. Choosen as one of "100 Best Contemporary Graphic Designers in the world" (Goodman 2013)
design  grid  typography 
april 2015
Hexagonal Grids
Hexagonal grids are used in some games but aren’t quite as straightforward or common as square grids. I’ve been collecting hex grid resources for nearly 20 years, and wrote this guide to the most elegant approaches that lead to the simplest code, largely based on the guides by Charles Fu and Clark Verbrugge. I’ll describe the various ways to make hex grids (I’ve counted 74 so far!), the relationships between them, as well as some common algorithms. Many parts of this page are interactive; choosing a type of grid will update diagrams, code, and text to match.
hexagon  grid  games  development 
january 2015
Backbone.js with React Views - Server Rendering, Virtual DOM, and Mor…
11/18/2014 Seattle ReactJS meetup presentation -- Abstract: Ryan Roemer will discuss moving the view c…
react  backbone 
december 2014
« earlier      
(over 3d a* adaptive admin advice aes agile air air3 algebra algorithms android angularjs animated animation animations api app apple arc architecture arduino art async audit automation backbone bars bash becomes best bestpractices blog boardgames book books bootstrap bread budget bullshit business but camping canvas carolla cases cassowary chain cheatsheet chicken chocolate chromebook chromeos class clothing cloud cms cocoa code coffee coffeescript color colors command-line compass completely components computer concurrency consciousness consider control cooking credit crypto cryptography css css3 dashboard data database databases datasets debugging deployment descriptors design designpatterns dev development diet dns docs easing education effort email embed english equality equity errexit error es2015 explicit exports finance fitness flash flashbuilder flashcards fluid fonts food forms fps framework frameworks fraud free free-will frugal functional functions funny game gamedev games gem gems generator gif gifs gifts git go google gps graphics grid growl gtd guard guide hacks hal handling) hardware helicopter helmets help hexagon hexakopter home hosting hotkeys howto html html5 http https icons ideas imdb incomposable indian inheritance inspiration install instead. interface into ios ipad is it itunes japan jasmine javascript jquery js json keyboards kids language laptop layout learning lectures library limber11 linear linux list lora lorem mac machine-learning machines makes management markov marriage mashups math maths mechanical memory migrations mitm mobile mobility montecarlo movies mozilla multiplatform music mvc navigation network nodejs notebook notes nyc obj-c office opengl osx palette paperless papers parenting passwords pastebin patterns pc performance phantomjs philosophy php physics ping pixar pixel pizza placebos platform plugin plugins png politics pong possible postgresql practically practices prediction prefix privacy process productivity programming prototyping proxy psd python questionable. rails rc react reading recipe recipes refactoring reference regex research responsive rest restaurants retina ridiculous robot rowe rspec ruby rubymotion safari samples sans-serif saves scripting scripts scroll sdk security seinfeld sencha serif setup shell shopping simulation skills smoothies so socks software sorting splitting spring spritesheets sql sqlite ssh standalone startup statecharts statistics still stochastic story stove streams stretching structures styleguide sublime sublimetext suffix swf symbols table tasks taxes teams ted tennis testing textbooks textmate that the themes they this threads threed timanderic tips tls to tool tools tracking tree trees tutorial tweens twitter typography ui unix vector version vi via:popular video videos vim vimrc visualization vm web webgl webkit with work work_ wrap writing xcode xss your youtube zip

Copy this bookmark: