## Writing Good Bash Scripts v2021
[Mirek Biňas](https://bletvaska.github.io)
/ [****]()
## Intro
* I am not an enthusiastic _bash ~~stripter~~ scripter_
* bash is not suitable for every problem
* it's great tool for piping of different solutions/tools
* works as **glue**
* it's **swiss army knife** of every _DevOps_
* following list is not complete
## #1 Don't forget about portability!
### Portable `Hello world!`
```bash
#!/usr/bin/env bash
echo "Hello world!"
```
* `env` - run a program in a modified environment
* looks for interpreter in `$PATH`
### Wrong Script
```bash
#!/usr/bin/env bash
echo "start here"
VAR = "syntax error on this line"
echo "this line will be executed too"
```
### Start This Way
```bash
#!/usr/bin/env bash
set -o errexit # stop when error occurs
set -o pipefail # if not, expressions like `error here | true`
# will always succeed
set -o nounset # detects uninitialised variables
set -o xtrace # prints every expression
# before executing it (debugging)
echo "start here"
VAR = "syntax error on this line"
echo "this line is now not reachable"
```
## #3 Enclose Vars as `"${variable}"`
* _escaping_
* the `'"'` helps with variables with white spaces
```bash
$ track="with or without you.mp3"
$ file $track
with: cannot open `with' (No such file or directory)
or: cannot open `or' (No such file or directory)
without: cannot open `without' (No such file or directory)
you.mp3: cannot open `you.mp3' (No such file or directory)
```
* the `'{}'` are not necessary, but are used in _bash expansion_:
* string interpolation:
```bash
"${variable}.yml"
```
* default (fallback) value:
```bash
"${variable:-something_else}"
```
* string replacement:
```bash
"${variable//from/to}"
```
## #4 Use `[[ expr ]]` instead of `[ expr ]`
note:
* `[[` - extension of bash
* `[` - short for `test`
## Cool Features I.
* no need to enclose variables into quotes
```bash
[[ -f ${file} ]] # vs. [ -f "${file}" ]
[[ $var = "joe" ]] # vs. [ $var = "joe" ]
```
* support for operators `"&&"` and `"||"`
```bash
[[ ${USERNAME} == "root" && $(id -g) == 1 ]]
```
* string comparision with `"<"` and `">"`
```bash
[[ "apple" < "banana" ]]
```
note:
```bash
[ $var = "joe" ]
```
## Cool Features II.
* operator `"=~"` for regex comparisions
```bash
[[ ${answer} =~ ^y(es)?$ ]]
```
* supports _globbing_
```bash
[[ ${answer} = y* ]]
```
## #5 Use Modular Approach
* write scripts in modules (not a single file)
* easy maintenance
* easy navigation
* modules are separated by functionality
* script can be extended with new functionality with `source`
```bash
# ...
source "${DATADIR}/helper.sh"
# ...
```
## Detect Sourcing
* detect, if the script is being run directly, or if it is just being sourced
```bash
# call the func only if the script is executed directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main
fi
```
* script can be executed normally, but if it's sourced, only the function definitions will be imported without doing anything else
note:
* https://advancedweb.hu/unit-testing-bash-scripts/
## #6 Don't Use Tools whose Functions `bash` Handles
* one of the most common (unconscious) mistakes
* most common `grep`, `awk`, `sed`, `bc`, `expr`, e.g.:
```bash
size=$(du -hs images/ | awk '{print $1}')
```
can be solved with bash as
```bash
array=($(du -hs images/))
size=${array[0]}
```
* instead of `seq 1 10` use `{1..10}`
* use `$(cmd)` instead of `cmd`
## #7 Write Scripts so That They can be Tested
* close everything to the function (approach)
```bash
#!/usr/bin/env bash
main(){
echo "Hello world!"
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main
fi
```
* many tools for testing in bash are available (e.g. [bats](https://github.com/sstephenson/bats))
```bash
#!/usr/bin/env bats
@test "when new folder is created, then exit status is 0" {
run mkdir "${dir}"
assert_equal "${status}" 0
}
```
> **Lint** or a **linter**, is a static code analysis tool used to flag programming errors, bugs, stylistic errors and suspicious constructs. It helps to increase the quality of code.
>
> -- [Wikipedia](https://en.wikipedia.org/wiki/Linter_%28software%29)
### [ShellCheck](https://www.shellcheck.net/)
* Shell script analysis tool
* contains [Gallery of bad code](https://github.com/koalaman/shellcheck/blob/master/README.md#user-content-gallery-of-bad-code)
* shows, what kind of things does ShellCheck look for
* in your Linux distro
## #9 Use Variable Annotations
* `readonly` - for readonly (constant) variables
```bash
readonly var='immutable value'
```
* `local` - for local variables inside of functions
```bash
_greetings(){
local name="$1"
printf "Hello %s!\n" "${name}"
}
```
## #10 Avoid Temporary Files
* some commands expect filenames as params
* pipelining does not work
* operator `<()`
```bash
jq .main.temp <(http https://openweathermap.org)
```
* _here documents_
```bash
cat <<EOF
This is
Here
Document.
EOF
```
* add `||` to commands expected to fail
```bash
ls /root || {
echo "Error" >&2
exit 1
}
```
note:
* https://linuxhint.com/bash_error_handling/
![qr code](https://api.qrserver.com/v1/create-qr-code/?data=https://bit.ly/31skY47&size=300x300)
(**https://bit.ly/31skY47**)