# bash ~~nerd~~ style guide #bash #shell #linux ### Aesthetics #### Tabs / Spaces - Use tabs. #### Columns - Do not exceed 80 characters per line. #### Semicolons - Avoid semicolons in scripts unless used for control statements like `if` or `while`. ```bash # wrong name='kolia'; echo "hello $name"; # right name='mykola' echo "hello $name" ``` ### Functions - Do not use the `function` keyword. - All variables created in a function should be made local. ```bash # wrong function foo { i=foo # this is now global, wrong depending on intent } # right foo() { local i=foo # this is local, preferred } ``` ### Block Statements - `then` should be on the same line as `if`, and `do` should be on the same line as `while`. ```bash # wrong if true then ... fi # right if true; then ... fi ``` ### Spacing - No more than 2 consecutive newline characters. ### Comments - No explicit style guide for comments. Avoid changing someone else's comments for aesthetic reasons unless you are rewriting or updating them. ### Bashisms - Prefer Bash builtins or keywords instead of external commands or sh(1) syntax. ```bash # wrong test -d /etc # also wrong [ -d /etc ] # correct [[ -d /etc ]] ``` ### Sequences - Use Bash builtins for generating sequences. ```bash n=10 # wrong for f in $(seq 1 5); do ... done # right for f in {1..5}; do ... done for ((i = 0; i < n; i++)); do ... done ``` ### Command Substitution - Use `$(...)` for command substitution. ```bash foo=`date` # wrong foo=$(date) # right ``` ### Math / Integer Manipulation - Use `((...))` and `$((...))`. ```bash a=5 b=4 # wrong if [[ $a -gt $b ]]; then ... fi # right if ((a > b)); then ... fi ``` - Do not use the `let` command. ### Parameter Expansion - Prefer parameter expansion over external commands like `echo`, `sed`, `awk`, etc. ```bash name='bahamas10' # wrong prog=$(basename "$0") nonumbers=$(echo "$name" | sed -e 's/[0-9]//g') # right prog=${0##*/} nonumbers=${name//[0-9]/} ``` ### Listing Files - Do not parse `ls(1)`, use Bash builtin functions to loop files. ```bash # very wrong, potentially unsafe for f in $(ls); do ... done # right for f in *; do ... done ``` ### Determining Path of the Executable - It is complex to determine the full path of the executing program. Rethink your software design. See [BashFAQ/028](http://mywiki.wooledge.org/BashFAQ/028) for more information. ### Arrays and Lists - Use Bash arrays instead of a string separated by spaces. ```bash # wrong modules='json httpserver jshint' for module in $modules; do npm install -g "$module" done # right modules=(json httpserver jshint) for module in "${modules[@]}"; do npm install -g "$module" done ``` If the command supports multiple arguments: ```bash npm install -g "${modules[@]}" ``` ### Read Builtin - Use the Bash `read` builtin to avoid forking external commands. ```bash fqdn='computer1.daveeddy.com' IFS=. read -r hostname domain tld <<< "$fqdn" echo "$hostname is in $domain.$tld" # => "computer1 is in daveeddy.com" ``` ### External Commands #### GNU Userland Tools - Avoid GNU-specific options to ensure portability. #### UUOC (Useless Use of `cat`) - Don’t use `cat` when unnecessary. ```bash # wrong cat file | grep foo # right grep foo < file # also right grep foo file ``` ### Style #### Quoting - Use double quotes for strings that require variable expansion or command substitution interpolation, and single quotes for all others. ```bash # right foo='Hello World' bar="You are $USER" # wrong foo="hello world" # possibly wrong, depending on intent bar='You are $USER' ``` - Quote variables that will undergo word-splitting. ```bash foo='hello world' if [[ -n $foo ]]; then # no quotes needed echo "$foo" # quotes needed fi bar=$foo # no quotes needed ``` - Controlled variables may remain unquoted. ```bash printf_date_supported=false if printf '%()T' &>/dev/null; then printf_date_supported=true fi if $printf_date_supported; then ... fi ``` - Variables like `$`, `$?`, `#
, etc. don’t require quotes. #### Variable Declaration - Avoid uppercase variable names unless necessary. - Don’t use `let` or `readonly` to create variables. - `declare` should only be used for associative arrays. - `local` should always be used in functions. ```bash # wrong declare -i foo=5 let foo++ readonly bar='something' FOOBAR=baz # right i=5 ((i++)) bar='something' foobar=baz ``` ### Shebang - Use `#!/usr/bin/env bash`. This will find bash through all your $PATH. ### Error Checking - Check for errors after commands like `cd` and exit or break if they are present. ```bash # wrong cd /some/path # this could fail rm file # if cd fails where am I? what am I deleting? # right cd /some/path || exit rm file ``` ### `set -e` - Don’t use `errexit`. See [BashFAQ/105](http://mywiki.wooledge.org/BashFAQ/105). ### `eval` - Never use `eval`. ### Common Mistakes #### Using `{}` Instead of Quotes - `${f}` is different from `"$f"` due to word-splitting. ```bash for f in '1 space' '2 spaces' '3 spaces'; do echo ${f} done ``` This yields: ``` 1 space 2 spaces 3 spaces ``` Proper use: ```bash for f in '1 space' '2 spaces' '3 spaces'; do echo "$f" done ``` This yields: ``` 1 space 2 spaces 3 spaces ``` #### Abusing `for` Loops When `while` Would Work Better - Use `while` loops for newline-separated data. ```bash # wrong users=$(awk -F: '{print $1}' /etc/passwd) for user in $users; do echo "user is $user" done # right while IFS=: read -r user _; do echo "$user is user" done < /etc/passwd ``` --- Useful links: https://mywiki.wooledge.org/BashPitfalls