# 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