diff --git a/README.md b/README.md
index ec50d7f..893833a 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ src="https://img.shields.io/badge/license-MIT-blue.svg">
-The goal of this repository is to document known and unknown methods of
+The goal of this book is to document known and unknown methods of
doing various tasks using only built-in `bash` features. Using the snippets
from this bible can help to remove unneeded dependencies from your scripts
and in most cases make them that little bit faster. I came across these
@@ -45,6 +45,7 @@ scripts and not full blown utilities.
+* [Introduction](#introduction)
* [Strings](#strings)
* [Trim leading and trailing white-space from string](#trim-leading-and-trailing-white-space-from-string)
* [Trim all white-space from string and truncate spaces](#trim-all-white-space-from-string-and-truncate-spaces)
@@ -156,6 +157,17 @@ scripts and not full blown utilities.
+
+# Introduction
+
+A collection of pure `bash` alternatives to external processes and programs. The `bash` scripting language is more powerful than people realise and you can accomplish most tasks without the need or dependency of external programs.
+
+Calling an external process in `bash` is expensive and excessive use will cause a noticeable slowdown. By sticking to built-in methods (*where possible*) your scripts and programs will be faster, require less dependencies and you'll gain a better understanding of the language itself.
+
+The contents of this book provide a reference for solving the problems encountered when writing programs and scripts in `bash`. The examples are in function format showcasing how to incorporate these solutions into your code.
+
+
+
# Strings
diff --git a/manuscript/Book.txt b/manuscript/Book.txt
index 21c4edb..abb34c6 100644
--- a/manuscript/Book.txt
+++ b/manuscript/Book.txt
@@ -15,3 +15,4 @@ chapter13.txt
chapter14.txt
chapter15.txt
chapter16.txt
+chapter17.txt
diff --git a/manuscript/chapter0.txt b/manuscript/chapter0.txt
index 37a625f..b7200f3 100644
--- a/manuscript/chapter0.txt
+++ b/manuscript/chapter0.txt
@@ -1,376 +1,10 @@
-# Strings
+# Introduction
-## Trim leading and trailing white-space from string
+A collection of pure `bash` alternatives to external processes and programs. The `bash` scripting language is more powerful than people realise and you can accomplish most tasks without the need or dependency of external programs.
-This is an alternative to `sed`, `awk`, `perl` and other tools. The
-function below works by finding all leading and trailing white-space and
-removing it from the start and end of the string. The `:` built-in is used in place of a temporary variable.
+Calling an external process in `bash` is expensive and excessive use will cause a noticeable slowdown. By sticking to built-in methods (*where possible*) your scripts and programs will be faster, require less dependencies and you'll gain a better understanding of the language itself.
-**Example Function:**
-
-```sh
-trim_string() {
- # Usage: trim_string " example string "
- : "${1#"${1%%[![:space:]]*}"}"
- : "${_%"${_##*[![:space:]]}"}"
- printf '%s\n' "$_"
-}
-```
-
-**Example Usage:**
-
-```shell
-$ trim_string " Hello, World "
-Hello, World
-
-$ name=" John Black "
-$ trim_string "$name"
-John Black
-```
-
-
-## Trim all white-space from string and truncate spaces
-
-This is an alternative to `sed`, `awk`, `perl` and other tools. The
-function below works by abusing word splitting to create a new string
-without leading/trailing white-space and with truncated spaces.
-
-**Example Function:**
-
-```sh
-# shellcheck disable=SC2086,SC2048
-trim_all() {
- # Usage: trim_all " example string "
- set -f
- set -- $*
- printf '%s\n' "$*"
- set +f
-}
-```
-
-**Example Usage:**
-
-```shell
-$ trim_all " Hello, World "
-Hello, World
-
-$ name=" John Black is my name. "
-$ trim_all "$name"
-John Black is my name.
-```
-
-## Use regex on a string
-
-We can use the result of `bash`'s regex matching to replace `sed` for a
-large number of use-cases.
-
-**CAVEAT**: This is one of the few platform dependant `bash` features.
-`bash` will use whatever regex engine is installed on the user's system.
-Stick to POSIX regex features if aiming for compatibility.
-
-**CAVEAT**: This example only prints the first matching group. When using
-multiple capture groups some modification is needed.
-
-**Example Function:**
-
-```sh
-regex() {
- # Usage: regex "string" "regex"
- [[ $1 =~ $2 ]] && printf '%s\n' "${BASH_REMATCH[1]}"
-}
-```
-
-**Example Usage:**
-
-```shell
-$ # Trim leading white-space.
-$ regex ' hello' '^\s*(.*)'
-hello
-
-$ # Validate a hex color.
-$ regex "#FFFFFF" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$'
-#FFFFFF
-
-$ # Validate a hex color (invalid).
-$ regex "red" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$'
-# no output (invalid)
-```
-
-**Example Usage in script:**
-
-```shell
-is_hex_color() {
- if [[ "$1" =~ ^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$ ]]; then
- printf '%s\n' "${BASH_REMATCH[1]}"
- else
- printf '%s\n' "error: $1 is an invalid color."
- return 1
- fi
-}
-
-read -r color
-is_hex_color "$color" || color="#FFFFFF"
-
-# Do stuff.
-```
-
-
-## Split a string on a delimiter
-
-This is an alternative to `cut`, `awk` and other tools.
-
-**Example Function:**
-
-```sh
-split() {
- # Usage: split "string" "delimiter"
- IFS=$'\n' read -d "" -ra arr <<< "${1//$2/$'\n'}"
- printf '%s\n' "${arr[@]}"
-}
-```
-
-**Example Usage:**
-
-```shell
-$ split "apples,oranges,pears,grapes" ","
-apples
-oranges
-pears
-grapes
-
-$ split "1, 2, 3, 4, 5" ", "
-1
-2
-3
-4
-5
-
-# Multi char delimiters work too!
-$ split "hello---world---my---name---is---john" "---"
-hello
-world
-my
-name
-is
-john
-```
-
-## Change a string to lowercase
-
-**CAVEAT:** Requires `bash` 4+
-
-**Example Function:**
-
-```sh
-lower() {
- # Usage: lower "string"
- printf '%s\n' "${1,,}"
-}
-```
-
-**Example Usage:**
-
-```shell
-$ lower "HELLO"
-hello
-
-$ lower "HeLlO"
-hello
-
-$ lower "hello"
-hello
-```
-
-## Change a string to uppercase
-
-**CAVEAT:** Requires `bash` 4+
-
-**Example Function:**
-
-```sh
-upper() {
- # Usage: upper "string"
- printf '%s\n' "${1^^}"
-}
-```
-
-**Example Usage:**
-
-```shell
-$ upper "hello"
-HELLO
-
-$ upper "HeLlO"
-HELLO
-
-$ upper "HELLO"
-HELLO
-```
-
-## Trim quotes from a string
-
-**Example Function:**
-
-```sh
-trim_quotes() {
- # Usage: trim_quotes "string"
- : "${1//\'}"
- printf '%s\n' "${_//\"}"
-}
-```
-
-**Example Usage:**
-
-```shell
-$ var="'Hello', \"World\""
-$ trim_quotes "$var"
-Hello, World
-```
-
-## Strip all instances of pattern from string
-
-**Example Function:**
-
-```sh
-strip_all() {
- # Usage: strip_all "string" "pattern"
- printf '%s\n' "${1//$2}"
-}
-```
-
-**Example Usage:**
-
-```shell
-$ strip_all "The Quick Brown Fox" "[aeiou]"
-Th Qck Brwn Fx
-
-$ strip_all "The Quick Brown Fox" "[[:space:]]"
-TheQuickBrownFox
-
-$ strip_all "The Quick Brown Fox" "Quick "
-The Brown Fox
-```
-
-## Strip first occurrence of pattern from string
-
-**Example Function:**
-
-```sh
-strip() {
- # Usage: strip "string" "pattern"
- printf '%s\n' "${1/$2}"
-}
-```
-
-**Example Usage:**
-
-```shell
-$ strip "The Quick Brown Fox" "[aeiou]"
-Th Quick Brown Fox
-
-$ strip "The Quick Brown Fox" "[[:space:]]"
-TheQuick Brown Fox
-```
-
-## Strip pattern from start of string
-
-**Example Function:**
-
-```sh
-lstrip() {
- # Usage: lstrip "string" "pattern"
- printf '%s\n' "${1##$2}"
-}
-```
-
-**Example Usage:**
-
-```shell
-$ lstrip "The Quick Brown Fox" "The "
-Quick Brown Fox
-```
-
-## Strip pattern from end of string
-
-**Example Function:**
-
-```sh
-rstrip() {
- # Usage: rstrip "string" "pattern"
- printf '%s\n' "${1%%$2}"
-}
-```
-
-**Example Usage:**
-
-```shell
-$ rstrip "The Quick Brown Fox" " Fox"
-The Quick Brown
-```
-
-## Check if string contains a sub-string
-
-**Using a test:**
-
-```shell
-if [[ "$var" == *sub_string* ]]; then
- printf '%s\n' "sub_string is in var."
-fi
-
-# Inverse (substring not in string).
-if [[ "$var" != *sub_string* ]]; then
- printf '%s\n' "sub_string is not in var."
-fi
-
-# This works for arrays too!
-if [[ "${arr[*]}" == *sub_string* ]]; then
- printf '%s\n' "sub_string is in array."
-fi
-```
-
-**Using a case statement:**
-
-```shell
-case "$var" in
- *sub_string*)
- # Do stuff
- ;;
-
- *sub_string2*)
- # Do more stuff
- ;;
-
- *)
- # Else
- ;;
-esac
-```
-
-## Check if string starts with sub-string
-
-```shell
-if [[ "$var" == sub_string* ]]; then
- printf '%s\n' "var starts with sub_string."
-fi
-
-# Inverse (var doesn't start with sub_string).
-if [[ "$var" != sub_string* ]]; then
- printf '%s\n' "var does not start with sub_string."
-fi
-```
-
-## Check if string ends with sub-string
-
-```shell
-if [[ "$var" == *sub_string ]]; then
- printf '%s\n' "var ends with sub_string."
-fi
-
-# Inverse (var doesn't start with sub_string).
-if [[ "$var" != *sub_string ]]; then
- printf '%s\n' "var does not end with sub_string."
-fi
-```
+The contents of this book provide a reference for solving the problems encountered when writing programs and scripts in `bash`. The examples are in function format showcasing how to incorporate these solutions into your code.
diff --git a/manuscript/chapter1.txt b/manuscript/chapter1.txt
index e677440..37a625f 100644
--- a/manuscript/chapter1.txt
+++ b/manuscript/chapter1.txt
@@ -1,130 +1,376 @@
-# Arrays
+# Strings
-## Reverse an array
+## Trim leading and trailing white-space from string
-Enabling `extdebug` allows access to the `BASH_ARGV` array which stores
-the current function’s arguments in reverse.
+This is an alternative to `sed`, `awk`, `perl` and other tools. The
+function below works by finding all leading and trailing white-space and
+removing it from the start and end of the string. The `:` built-in is used in place of a temporary variable.
**Example Function:**
```sh
-reverse_array() {
- # Usage: reverse_array "array"
- shopt -s extdebug
- f()(printf '%s\n' "${BASH_ARGV[@]}"); f "$@"
- shopt -u extdebug
+trim_string() {
+ # Usage: trim_string " example string "
+ : "${1#"${1%%[![:space:]]*}"}"
+ : "${_%"${_##*[![:space:]]}"}"
+ printf '%s\n' "$_"
}
```
**Example Usage:**
```shell
-$ reverse_array 1 2 3 4 5
-5
-4
-3
-2
-1
+$ trim_string " Hello, World "
+Hello, World
-$ arr=(red blue green)
-$ reverse_array "${arr[@]}"
-green
-blue
-red
+$ name=" John Black "
+$ trim_string "$name"
+John Black
```
-## Remove duplicate array elements
-Create a temporary associative array. When setting associative array
-values and a duplicate assignment occurs, bash overwrites the key. This
-allows us to effectively remove array duplicates.
+## Trim all white-space from string and truncate spaces
+
+This is an alternative to `sed`, `awk`, `perl` and other tools. The
+function below works by abusing word splitting to create a new string
+without leading/trailing white-space and with truncated spaces.
+
+**Example Function:**
+
+```sh
+# shellcheck disable=SC2086,SC2048
+trim_all() {
+ # Usage: trim_all " example string "
+ set -f
+ set -- $*
+ printf '%s\n' "$*"
+ set +f
+}
+```
+
+**Example Usage:**
+
+```shell
+$ trim_all " Hello, World "
+Hello, World
+
+$ name=" John Black is my name. "
+$ trim_all "$name"
+John Black is my name.
+```
+
+## Use regex on a string
+
+We can use the result of `bash`'s regex matching to replace `sed` for a
+large number of use-cases.
+
+**CAVEAT**: This is one of the few platform dependant `bash` features.
+`bash` will use whatever regex engine is installed on the user's system.
+Stick to POSIX regex features if aiming for compatibility.
+
+**CAVEAT**: This example only prints the first matching group. When using
+multiple capture groups some modification is needed.
+
+**Example Function:**
+
+```sh
+regex() {
+ # Usage: regex "string" "regex"
+ [[ $1 =~ $2 ]] && printf '%s\n' "${BASH_REMATCH[1]}"
+}
+```
+
+**Example Usage:**
+
+```shell
+$ # Trim leading white-space.
+$ regex ' hello' '^\s*(.*)'
+hello
+
+$ # Validate a hex color.
+$ regex "#FFFFFF" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$'
+#FFFFFF
+
+$ # Validate a hex color (invalid).
+$ regex "red" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$'
+# no output (invalid)
+```
+
+**Example Usage in script:**
+
+```shell
+is_hex_color() {
+ if [[ "$1" =~ ^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$ ]]; then
+ printf '%s\n' "${BASH_REMATCH[1]}"
+ else
+ printf '%s\n' "error: $1 is an invalid color."
+ return 1
+ fi
+}
+
+read -r color
+is_hex_color "$color" || color="#FFFFFF"
+
+# Do stuff.
+```
+
+
+## Split a string on a delimiter
+
+This is an alternative to `cut`, `awk` and other tools.
+
+**Example Function:**
+
+```sh
+split() {
+ # Usage: split "string" "delimiter"
+ IFS=$'\n' read -d "" -ra arr <<< "${1//$2/$'\n'}"
+ printf '%s\n' "${arr[@]}"
+}
+```
+
+**Example Usage:**
+
+```shell
+$ split "apples,oranges,pears,grapes" ","
+apples
+oranges
+pears
+grapes
+
+$ split "1, 2, 3, 4, 5" ", "
+1
+2
+3
+4
+5
+
+# Multi char delimiters work too!
+$ split "hello---world---my---name---is---john" "---"
+hello
+world
+my
+name
+is
+john
+```
+
+## Change a string to lowercase
**CAVEAT:** Requires `bash` 4+
**Example Function:**
```sh
-remove_array_dups() {
- # Usage: remove_array_dups "array"
- declare -A tmp_array
-
- for i in "$@"; do
- [[ "$i" ]] && IFS=" " tmp_array["${i:- }"]=1
- done
-
- printf '%s\n' "${!tmp_array[@]}"
+lower() {
+ # Usage: lower "string"
+ printf '%s\n' "${1,,}"
}
```
**Example Usage:**
```shell
-$ remove_array_dups 1 1 2 2 3 3 3 3 3 4 4 4 4 4 5 5 5 5 5 5
-1
-2
-3
-4
-5
+$ lower "HELLO"
+hello
-$ arr=(red red green blue blue)
-$ remove_array_dups "${arr[@]}"
-red
-green
-blue
+$ lower "HeLlO"
+hello
+
+$ lower "hello"
+hello
```
-## Random array element
+## Change a string to uppercase
+
+**CAVEAT:** Requires `bash` 4+
**Example Function:**
```sh
-random_array_element() {
- # Usage: random_array_element "array"
- local arr=("$@")
- printf '%s\n' "${arr[RANDOM % $#]}"
+upper() {
+ # Usage: upper "string"
+ printf '%s\n' "${1^^}"
}
```
**Example Usage:**
```shell
-$ array=(red green blue yellow brown)
-$ random_array_element "${array[@]}"
-yellow
+$ upper "hello"
+HELLO
-# You can also just pass multiple arguments.
-$ random_array_element 1 2 3 4 5 6 7
-3
+$ upper "HeLlO"
+HELLO
+
+$ upper "HELLO"
+HELLO
```
-## Cycle through an array
+## Trim quotes from a string
-Each time the `printf` is called, the next array element is printed. When
-the print hits the last array element it starts from the first element
-again.
+**Example Function:**
```sh
-arr=(a b c d)
-
-cycle() {
- printf '%s ' "${arr[${i:=0}]}"
- ((i=i>=${#arr[@]}-1?0:++i))
+trim_quotes() {
+ # Usage: trim_quotes "string"
+ : "${1//\'}"
+ printf '%s\n' "${_//\"}"
}
```
+**Example Usage:**
-## Toggle between two values
+```shell
+$ var="'Hello', \"World\""
+$ trim_quotes "$var"
+Hello, World
+```
-This works the same as above, this is just a different use case.
+## Strip all instances of pattern from string
+
+**Example Function:**
```sh
-arr=(true false)
-
-cycle() {
- printf '%s ' "${arr[${i:=0}]}"
- ((i=i>=${#arr[@]}-1?0:++i))
+strip_all() {
+ # Usage: strip_all "string" "pattern"
+ printf '%s\n' "${1//$2}"
}
```
+**Example Usage:**
+
+```shell
+$ strip_all "The Quick Brown Fox" "[aeiou]"
+Th Qck Brwn Fx
+
+$ strip_all "The Quick Brown Fox" "[[:space:]]"
+TheQuickBrownFox
+
+$ strip_all "The Quick Brown Fox" "Quick "
+The Brown Fox
+```
+
+## Strip first occurrence of pattern from string
+
+**Example Function:**
+
+```sh
+strip() {
+ # Usage: strip "string" "pattern"
+ printf '%s\n' "${1/$2}"
+}
+```
+
+**Example Usage:**
+
+```shell
+$ strip "The Quick Brown Fox" "[aeiou]"
+Th Quick Brown Fox
+
+$ strip "The Quick Brown Fox" "[[:space:]]"
+TheQuick Brown Fox
+```
+
+## Strip pattern from start of string
+
+**Example Function:**
+
+```sh
+lstrip() {
+ # Usage: lstrip "string" "pattern"
+ printf '%s\n' "${1##$2}"
+}
+```
+
+**Example Usage:**
+
+```shell
+$ lstrip "The Quick Brown Fox" "The "
+Quick Brown Fox
+```
+
+## Strip pattern from end of string
+
+**Example Function:**
+
+```sh
+rstrip() {
+ # Usage: rstrip "string" "pattern"
+ printf '%s\n' "${1%%$2}"
+}
+```
+
+**Example Usage:**
+
+```shell
+$ rstrip "The Quick Brown Fox" " Fox"
+The Quick Brown
+```
+
+## Check if string contains a sub-string
+
+**Using a test:**
+
+```shell
+if [[ "$var" == *sub_string* ]]; then
+ printf '%s\n' "sub_string is in var."
+fi
+
+# Inverse (substring not in string).
+if [[ "$var" != *sub_string* ]]; then
+ printf '%s\n' "sub_string is not in var."
+fi
+
+# This works for arrays too!
+if [[ "${arr[*]}" == *sub_string* ]]; then
+ printf '%s\n' "sub_string is in array."
+fi
+```
+
+**Using a case statement:**
+
+```shell
+case "$var" in
+ *sub_string*)
+ # Do stuff
+ ;;
+
+ *sub_string2*)
+ # Do more stuff
+ ;;
+
+ *)
+ # Else
+ ;;
+esac
+```
+
+## Check if string starts with sub-string
+
+```shell
+if [[ "$var" == sub_string* ]]; then
+ printf '%s\n' "var starts with sub_string."
+fi
+
+# Inverse (var doesn't start with sub_string).
+if [[ "$var" != sub_string* ]]; then
+ printf '%s\n' "var does not start with sub_string."
+fi
+```
+
+## Check if string ends with sub-string
+
+```shell
+if [[ "$var" == *sub_string ]]; then
+ printf '%s\n' "var ends with sub_string."
+fi
+
+# Inverse (var doesn't start with sub_string).
+if [[ "$var" != *sub_string ]]; then
+ printf '%s\n' "var does not end with sub_string."
+fi
+```
+
diff --git a/manuscript/chapter10.txt b/manuscript/chapter10.txt
index fd3dfd7..5fc7c0c 100644
--- a/manuscript/chapter10.txt
+++ b/manuscript/chapter10.txt
@@ -1,42 +1,30 @@
-# Traps
+# Arithmetic
-Traps allow you to execute code on various signals. In `pxltrm` I'm using traps to redraw the user interface on window resize. Another use case is cleaning up temporary files on script exit.
-
-These `trap` lines should be added near the start of your script so any early errors are also caught.
-
-**NOTE:** For a full list of signals, see `trap -l`.
-
-
-## Do something on script exit
+## Simpler syntax to set variables
```shell
-# Clear screen on script exit.
-trap 'printf \\e[2J\\e[H\\e[m' EXIT
+# Simple math
+((var=1+2))
+
+# Decrement/Increment variable
+((var++))
+((var--))
+((var+=1))
+((var-=1))
+
+# Using variables
+((var=var2*arr[2]))
```
-## Ignore terminal interrupt (CTRL+C, SIGINT)
+## Ternary tests
```shell
-trap '' INT
-```
-
-## React to window resize.
-
-```shell
-# Call a function on window resize.
-trap 'code_here' SIGWINCH
-```
-
-## Do something before every command.
-
-```shell
-trap 'code_here' DEBUG
-```
-
-## Do something when a shell function or a sourced file finishes executing
-
-```shell
-trap 'code_here' RETURN
+# Set the value of var to var2 if var2 is greater than var.
+# var: variable to set.
+# var2>var: Condition to test.
+# ?var2: If the test succeeds.
+# :var: If the test fails.
+((var=var2>var?var2:var))
```
diff --git a/manuscript/chapter11.txt b/manuscript/chapter11.txt
index 81071a2..fd3dfd7 100644
--- a/manuscript/chapter11.txt
+++ b/manuscript/chapter11.txt
@@ -1,13 +1,42 @@
-# Performance
+# Traps
-## Disable Unicode
+Traps allow you to execute code on various signals. In `pxltrm` I'm using traps to redraw the user interface on window resize. Another use case is cleaning up temporary files on script exit.
-If your script doesn't require unicode, you can disable it for a speed boost. Results may vary but I've seen an improvement in Neofetch and some other smaller programs.
+These `trap` lines should be added near the start of your script so any early errors are also caught.
+
+**NOTE:** For a full list of signals, see `trap -l`.
+
+
+## Do something on script exit
```shell
-# Disable unicode.
-LC_ALL=C
-LANG=C
+# Clear screen on script exit.
+trap 'printf \\e[2J\\e[H\\e[m' EXIT
+```
+
+## Ignore terminal interrupt (CTRL+C, SIGINT)
+
+```shell
+trap '' INT
+```
+
+## React to window resize.
+
+```shell
+# Call a function on window resize.
+trap 'code_here' SIGWINCH
+```
+
+## Do something before every command.
+
+```shell
+trap 'code_here' DEBUG
+```
+
+## Do something when a shell function or a sourced file finishes executing
+
+```shell
+trap 'code_here' RETURN
```
diff --git a/manuscript/chapter12.txt b/manuscript/chapter12.txt
index d3857db..81071a2 100644
--- a/manuscript/chapter12.txt
+++ b/manuscript/chapter12.txt
@@ -1,51 +1,13 @@
-# Obsolete Syntax
+# Performance
-## Shebang
+## Disable Unicode
-Use `#!/usr/bin/env bash` instead of `#!/bin/bash`.
-
-- The former searches the user's `PATH` to find the `bash` binary.
-- The latter assumes it is always installed to `/bin/` which can cause issues.
+If your script doesn't require unicode, you can disable it for a speed boost. Results may vary but I've seen an improvement in Neofetch and some other smaller programs.
```shell
-# Right:
-
- #!/usr/bin/env bash
-
-# Wrong:
-
- #!/bin/bash
-```
-
-## Command Substitution
-
-Use `$()` instead of `` ` ` ``.
-
-```shell
-# Right.
-var="$(command)"
-
-# Wrong.
-var=`command`
-
-# $() can easily be nested whereas `` cannot.
-var="$(command "$(command)")"
-```
-
-## Function Declaration
-
-Don't use the `function` keyword, it reduces compatibility with older versions of `bash`.
-
-```shell
-# Right.
-do_something() {
- # ...
-}
-
-# Wrong.
-function do_something() {
- # ...
-}
+# Disable unicode.
+LC_ALL=C
+LANG=C
```
diff --git a/manuscript/chapter13.txt b/manuscript/chapter13.txt
index f743554..d3857db 100644
--- a/manuscript/chapter13.txt
+++ b/manuscript/chapter13.txt
@@ -1,99 +1,51 @@
-# Internal Variables
+# Obsolete Syntax
-**NOTE**: This list does not include every internal variable (*You can
-help by adding a missing entry!*).
+## Shebang
-For a complete list, see:
-http://tldp.org/LDP/abs/html/internalvariables.html
+Use `#!/usr/bin/env bash` instead of `#!/bin/bash`.
-## Get the location to the `bash` binary
+- The former searches the user's `PATH` to find the `bash` binary.
+- The latter assumes it is always installed to `/bin/` which can cause issues.
```shell
-"$BASH"
+# Right:
+
+ #!/usr/bin/env bash
+
+# Wrong:
+
+ #!/bin/bash
```
-## Get the version of the current running `bash` process
+## Command Substitution
+
+Use `$()` instead of `` ` ` ``.
```shell
-# As a string.
-"$BASH_VERSION"
+# Right.
+var="$(command)"
-# As an array.
-"${BASH_VERSINFO[@]}"
+# Wrong.
+var=`command`
+
+# $() can easily be nested whereas `` cannot.
+var="$(command "$(command)")"
```
-## Open the user's preferred text editor
+## Function Declaration
+
+Don't use the `function` keyword, it reduces compatibility with older versions of `bash`.
```shell
-"$EDITOR" "$file"
+# Right.
+do_something() {
+ # ...
+}
-# NOTE: This variable may be empty, set a fallback value.
-"${EDITOR:-vi}" "$file"
-```
-
-## Get the name of the current function
-
-```shell
-# Current function.
-"${FUNCNAME[0]}"
-
-# Parent function.
-"${FUNCNAME[1]}"
-
-# So on and so forth.
-"${FUNCNAME[2]}"
-"${FUNCNAME[3]}"
-
-# All functions including parents.
-"${FUNCNAME[@]}"
-```
-
-## Get the host-name of the system
-
-```shell
-"$HOSTNAME"
-
-# NOTE: This variable may be empty.
-# Optionally set a fallback to the hostname command.
-"${HOSTNAME:-$(hostname)}"
-```
-
-## Get the architecture of the Operating System
-
-```shell
-"$HOSTTYPE"
-```
-
-## Get the name of the Operating System / Kernel
-
-This can be used to add conditional support for different Operating
-Systems without needing to call `uname`.
-
-```shell
-"$OSTYPE"
-```
-
-## Get the current working directory
-
-This is an alternative to the `pwd` built-in.
-
-```shell
-"$PWD"
-```
-
-## Get the number of seconds the script has been running
-
-```shell
-"$SECONDS"
-```
-
-## Get a pseudorandom integer
-
-Each time `$RANDOM` is used, a different integer between `0` and `32767` is returned. This variable should not be used for anything related to security (*this includes encryption keys etc*).
-
-
-```shell
-"$RANDOM"
+# Wrong.
+function do_something() {
+ # ...
+}
```
diff --git a/manuscript/chapter14.txt b/manuscript/chapter14.txt
index ca647b9..f743554 100644
--- a/manuscript/chapter14.txt
+++ b/manuscript/chapter14.txt
@@ -1,78 +1,99 @@
-# Information about the terminal
+# Internal Variables
-## Get the terminal size in lines and columns (*from a script*)
+**NOTE**: This list does not include every internal variable (*You can
+help by adding a missing entry!*).
-This is handy when writing scripts in pure bash and `stty`/`tput` can’t be
-called.
+For a complete list, see:
+http://tldp.org/LDP/abs/html/internalvariables.html
-**Example Function:**
-
-```sh
-get_term_size() {
- # Usage: get_term_size
-
- # (:;:) is a micro sleep to ensure the variables are
- # exported immediately.
- shopt -s checkwinsize; (:;:)
- printf '%s\n' "$LINES $COLUMNS"
-}
-```
-
-**Example Usage:**
+## Get the location to the `bash` binary
```shell
-# Output: LINES COLUMNS
-$ get_term_size
-15 55
+"$BASH"
```
-## Get the terminal size in pixels
-
-**CAVEAT**: This does not work in some terminal emulators.
-
-**Example Function:**
-
-```sh
-get_window_size() {
- # Usage: get_window_size
- printf '%b' "${TMUX:+\\ePtmux;\\e}\\e[14t${TMUX:+\\e\\\\}"
- IFS=';t' read -d t -t 0.05 -sra term_size
- printf '%s\n' "${term_size[1]}x${term_size[2]}"
-}
-```
-
-**Example Usage:**
+## Get the version of the current running `bash` process
```shell
-# Output: WIDTHxHEIGHT
-$ get_window_size
-1200x800
+# As a string.
+"$BASH_VERSION"
-# Output (fail):
-$ get_window_size
-x
+# As an array.
+"${BASH_VERSINFO[@]}"
```
-## Get the current cursor position
-
-This is useful when creating a TUI in pure bash.
-
-**Example Function:**
-
-```sh
-get_cursor_pos() {
- # Usage: get_cursor_pos
- IFS='[;' read -p $'\e[6n' -d R -rs _ y x _
- printf '%s\n' "$x $y"
-}
-```
-
-**Example Usage:**
+## Open the user's preferred text editor
```shell
-# Output: X Y
-$ get_cursor_pos
-1 8
+"$EDITOR" "$file"
+
+# NOTE: This variable may be empty, set a fallback value.
+"${EDITOR:-vi}" "$file"
+```
+
+## Get the name of the current function
+
+```shell
+# Current function.
+"${FUNCNAME[0]}"
+
+# Parent function.
+"${FUNCNAME[1]}"
+
+# So on and so forth.
+"${FUNCNAME[2]}"
+"${FUNCNAME[3]}"
+
+# All functions including parents.
+"${FUNCNAME[@]}"
+```
+
+## Get the host-name of the system
+
+```shell
+"$HOSTNAME"
+
+# NOTE: This variable may be empty.
+# Optionally set a fallback to the hostname command.
+"${HOSTNAME:-$(hostname)}"
+```
+
+## Get the architecture of the Operating System
+
+```shell
+"$HOSTTYPE"
+```
+
+## Get the name of the Operating System / Kernel
+
+This can be used to add conditional support for different Operating
+Systems without needing to call `uname`.
+
+```shell
+"$OSTYPE"
+```
+
+## Get the current working directory
+
+This is an alternative to the `pwd` built-in.
+
+```shell
+"$PWD"
+```
+
+## Get the number of seconds the script has been running
+
+```shell
+"$SECONDS"
+```
+
+## Get a pseudorandom integer
+
+Each time `$RANDOM` is used, a different integer between `0` and `32767` is returned. This variable should not be used for anything related to security (*this includes encryption keys etc*).
+
+
+```shell
+"$RANDOM"
```
diff --git a/manuscript/chapter15.txt b/manuscript/chapter15.txt
index 98e8e64..ca647b9 100644
--- a/manuscript/chapter15.txt
+++ b/manuscript/chapter15.txt
@@ -1,150 +1,78 @@
-# Conversion
+# Information about the terminal
-## Convert a hex color to RGB
+## Get the terminal size in lines and columns (*from a script*)
+
+This is handy when writing scripts in pure bash and `stty`/`tput` can’t be
+called.
**Example Function:**
```sh
-hex_to_rgb() {
- # Usage: hex_to_rgb "#FFFFFF"
- ((r=16#${1:1:2}))
- ((g=16#${1:3:2}))
- ((b=16#${1:5:6}))
+get_term_size() {
+ # Usage: get_term_size
- printf '%s\n' "$r $g $b"
+ # (:;:) is a micro sleep to ensure the variables are
+ # exported immediately.
+ shopt -s checkwinsize; (:;:)
+ printf '%s\n' "$LINES $COLUMNS"
}
```
**Example Usage:**
```shell
-$ hex_to_rgb "#FFFFFF"
-255 255 255
+# Output: LINES COLUMNS
+$ get_term_size
+15 55
```
+## Get the terminal size in pixels
-## Convert an RGB color to hex
+**CAVEAT**: This does not work in some terminal emulators.
**Example Function:**
```sh
-rgb_to_hex() {
- # Usage: rgb_to_hex "r" "g" "b"
- printf '#%02x%02x%02x\n' "$1" "$2" "$3"
+get_window_size() {
+ # Usage: get_window_size
+ printf '%b' "${TMUX:+\\ePtmux;\\e}\\e[14t${TMUX:+\\e\\\\}"
+ IFS=';t' read -d t -t 0.05 -sra term_size
+ printf '%s\n' "${term_size[1]}x${term_size[2]}"
}
```
**Example Usage:**
```shell
-$ rgb_to_hex "255" "255" "255"
-#FFFFFF
+# Output: WIDTHxHEIGHT
+$ get_window_size
+1200x800
+
+# Output (fail):
+$ get_window_size
+x
```
+## Get the current cursor position
-# Code Golf
+This is useful when creating a TUI in pure bash.
-## Shorter `for` loop syntax
+**Example Function:**
-```shell
-# Tiny C Style.
-for((;i++<10;)){ echo "$i";}
-
-# Undocumented method.
-for i in {1..10};{ echo "$i";}
-
-# Expansion.
-for i in {1..10}; do echo "$i"; done
-
-# C Style.
-for((i=0;i<=10;i++)); do echo "$i"; done
-```
-
-## Shorter infinite loops
-
-```shell
-# Normal method
-while :; do echo hi; done
-
-# Shorter
-for((;;)){ echo hi;}
-```
-
-## Shorter function declaration
-
-```shell
-# Normal method
-f(){ echo hi;}
-
-# Using a subshell
-f()(echo hi)
-
-# Using arithmetic
-# You can use this to assign integer values.
-# Example: f a=1
-# f a++
-f()(($1))
-
-# Using tests, loops etc.
-# NOTE: You can also use ‘while’, ‘until’, ‘case’, ‘(())’, ‘[[]]’.
-f()if true; then echo "$1"; fi
-f()for i in "$@"; do echo "$i"; done
-```
-
-## Shorter `if` syntax
-
-```shell
-# One line
-# Note: The 3rd statement may run when the 1st is true
-[[ "$var" == hello ]] && echo hi || echo bye
-[[ "$var" == hello ]] && { echo hi; echo there; } || echo bye
-
-# Multi line (no else, single statement)
-# Note: The exit status may not be the same as with an if statement
-[[ "$var" == hello ]] && \
- echo hi
-
-# Multi line (no else)
-[[ "$var" == hello ]] && {
- echo hi
- # ...
+```sh
+get_cursor_pos() {
+ # Usage: get_cursor_pos
+ IFS='[;' read -p $'\e[6n' -d R -rs _ y x _
+ printf '%s\n' "$x $y"
}
```
-## Simpler `case` statement to set variable
-
-We can use the `:` builtin to avoid repeating `variable=` in a case
-statement. The `$_` variable stores the last argument of the last
-successful command. `:` always succeeds so we can abuse it to store the
-variable value.
+**Example Usage:**
```shell
-# Modified snippet from Neofetch.
-case "$OSTYPE" in
- "darwin"*)
- : "MacOS"
- ;;
-
- "linux"*)
- : "Linux"
- ;;
-
- *"bsd"* | "dragonfly" | "bitrig")
- : "BSD"
- ;;
-
- "cygwin" | "msys" | "win32")
- : "Windows"
- ;;
-
- *)
- printf '%s\n' "Unknown OS detected, aborting..." >&2
- exit 1
- ;;
-esac
-
-# Finally, set the variable.
-os="$_"
+# Output: X Y
+$ get_cursor_pos
+1 8
```
diff --git a/manuscript/chapter16.txt b/manuscript/chapter16.txt
index 756faa9..98e8e64 100644
--- a/manuscript/chapter16.txt
+++ b/manuscript/chapter16.txt
@@ -1,192 +1,150 @@
-# Other
+# Conversion
-## Use `read` as an alternative to the `sleep` command
-
-I was surprised to find out `sleep` is an external command and isn't a
-built-in.
-
-**CAVEAT:** Requires `bash` 4+
+## Convert a hex color to RGB
**Example Function:**
```sh
-read_sleep() {
- # Usage: sleep 1
- # sleep 0.2
- read -rst "${1:-1}" -N 999
+hex_to_rgb() {
+ # Usage: hex_to_rgb "#FFFFFF"
+ ((r=16#${1:1:2}))
+ ((g=16#${1:3:2}))
+ ((b=16#${1:5:6}))
+
+ printf '%s\n' "$r $g $b"
}
```
**Example Usage:**
```shell
-read_sleep 1
-read_sleep 0.1
-read_sleep 30
+$ hex_to_rgb "#FFFFFF"
+255 255 255
```
-## Check if a program is in the user's PATH
-```shell
-# There are 3 ways to do this and you can use either of
-# these in the same way.
-type -p executable_name &>/dev/null
-hash executable_name &>/dev/null
-command -v executable_name &>/dev/null
-
-# As a test.
-if type -p executable_name &>/dev/null; then
- # Program is in PATH.
-fi
-
-# Inverse.
-if ! type -p executable_name &>/dev/null; then
- # Program is not in PATH.
-fi
-
-# Example (Exit early if program isn't installed).
-if ! type -p convert &>/dev/null; then
- printf '%s\n' "error: convert isn't installed, exiting..."
- exit 1
-fi
-```
-
-## Get the current date using `strftime`
-
-Bash’s `printf` has a built-in method of getting the date which we can use
-in place of the `date` command in a lot of cases.
-
-**CAVEAT:** Requires `bash` 4+
+## Convert an RGB color to hex
**Example Function:**
```sh
-date() {
- # Usage: date "format"
- # See: 'man strftime' for format.
- printf "%($1)T\\n" "-1"
+rgb_to_hex() {
+ # Usage: rgb_to_hex "r" "g" "b"
+ printf '#%02x%02x%02x\n' "$1" "$2" "$3"
}
```
**Example Usage:**
```shell
-# Using above function.
-$ date "%a %d %b - %l:%M %p"
-Fri 15 Jun - 10:00 AM
-
-# Using printf directly.
-$ printf '%(%a %d %b - %l:%M %p)T\n' "-1"
-Fri 15 Jun - 10:00 AM
-
-# Assigning a variable using printf.
-$ printf -v date '%(%a %d %b - %l:%M %p)T\n' '-1'
-$ printf '%s\n' "$date"
-Fri 15 Jun - 10:00 AM
+$ rgb_to_hex "255" "255" "255"
+#FFFFFF
```
-## Generate a UUID V4
-**Example Function:**
+# Code Golf
-```sh
-uuid() {
- # Usage: uuid
- C="89ab"
+## Shorter `for` loop syntax
- for ((N=0;N<16;++N)); do
- B="$((RANDOM%256))"
+```shell
+# Tiny C Style.
+for((;i++<10;)){ echo "$i";}
- case "$N" in
- 6) printf '4%x' "$((B%16))" ;;
- 8) printf '%c%x' "${C:$RANDOM%${#C}:1}" "$((B%16))" ;;
+# Undocumented method.
+for i in {1..10};{ echo "$i";}
- 3|5|7|9)
- printf '%02x-' "$B"
- ;;
+# Expansion.
+for i in {1..10}; do echo "$i"; done
- *)
- printf '%02x' "$B"
- ;;
- esac
- done
+# C Style.
+for((i=0;i<=10;i++)); do echo "$i"; done
+```
- printf '\n'
+## Shorter infinite loops
+
+```shell
+# Normal method
+while :; do echo hi; done
+
+# Shorter
+for((;;)){ echo hi;}
+```
+
+## Shorter function declaration
+
+```shell
+# Normal method
+f(){ echo hi;}
+
+# Using a subshell
+f()(echo hi)
+
+# Using arithmetic
+# You can use this to assign integer values.
+# Example: f a=1
+# f a++
+f()(($1))
+
+# Using tests, loops etc.
+# NOTE: You can also use ‘while’, ‘until’, ‘case’, ‘(())’, ‘[[]]’.
+f()if true; then echo "$1"; fi
+f()for i in "$@"; do echo "$i"; done
+```
+
+## Shorter `if` syntax
+
+```shell
+# One line
+# Note: The 3rd statement may run when the 1st is true
+[[ "$var" == hello ]] && echo hi || echo bye
+[[ "$var" == hello ]] && { echo hi; echo there; } || echo bye
+
+# Multi line (no else, single statement)
+# Note: The exit status may not be the same as with an if statement
+[[ "$var" == hello ]] && \
+ echo hi
+
+# Multi line (no else)
+[[ "$var" == hello ]] && {
+ echo hi
+ # ...
}
```
-**Example Usage:**
+## Simpler `case` statement to set variable
+
+We can use the `:` builtin to avoid repeating `variable=` in a case
+statement. The `$_` variable stores the last argument of the last
+successful command. `:` always succeeds so we can abuse it to store the
+variable value.
```shell
-$ uuid
-d5b6c731-1310-4c24-9fe3-55d556d44374
-```
+# Modified snippet from Neofetch.
+case "$OSTYPE" in
+ "darwin"*)
+ : "MacOS"
+ ;;
-## Progress bars
+ "linux"*)
+ : "Linux"
+ ;;
-This is a simple way of drawing progress bars without needing a for loop
-in the function itself.
+ *"bsd"* | "dragonfly" | "bitrig")
+ : "BSD"
+ ;;
-**Example Function:**
+ "cygwin" | "msys" | "win32")
+ : "Windows"
+ ;;
-```sh
-bar() {
- # Usage: bar 1 10
- # ^----- Elapsed Percentage (0-100).
- # ^-- Total length in chars.
- ((elapsed=$1*$2/100))
+ *)
+ printf '%s\n' "Unknown OS detected, aborting..." >&2
+ exit 1
+ ;;
+esac
- # Create the bar with spaces.
- printf -v prog "%${elapsed}s"
- printf -v total "%$(($2-elapsed))s"
-
- printf '%s\r' "[${prog// /-}${total}]"
-}
-```
-
-**Example Usage:**
-
-```shell
-for ((i=0;i<=100;i++)); do
- # Pure bash micro sleeps (for the example).
- (:;:) && (:;:) && (:;:) && (:;:) && (:;:)
-
- # Print the bar.
- bar "$i" "10"
-done
-
-printf '\n'
-```
-
-## Get the list of functions from your script
-
-```sh
-get_functions() {
- # Usage: get_functions
- IFS=$'\n' read -d "" -ra functions < <(declare -F)
- printf '%s\n' "${functions[@]//declare -f }"
-}
-```
-
-## Bypass shell aliases
-
-```shell
-# alias
-ls
-
-# command
-# shellcheck disable=SC1001
-\ls
-```
-
-## Bypass shell functions
-
-```shell
-# function
-ls
-
-# command
-command ls
+# Finally, set the variable.
+os="$_"
```
diff --git a/manuscript/chapter17.txt b/manuscript/chapter17.txt
new file mode 100644
index 0000000..756faa9
--- /dev/null
+++ b/manuscript/chapter17.txt
@@ -0,0 +1,193 @@
+# Other
+
+## Use `read` as an alternative to the `sleep` command
+
+I was surprised to find out `sleep` is an external command and isn't a
+built-in.
+
+**CAVEAT:** Requires `bash` 4+
+
+**Example Function:**
+
+```sh
+read_sleep() {
+ # Usage: sleep 1
+ # sleep 0.2
+ read -rst "${1:-1}" -N 999
+}
+```
+
+**Example Usage:**
+
+```shell
+read_sleep 1
+read_sleep 0.1
+read_sleep 30
+```
+
+## Check if a program is in the user's PATH
+
+```shell
+# There are 3 ways to do this and you can use either of
+# these in the same way.
+type -p executable_name &>/dev/null
+hash executable_name &>/dev/null
+command -v executable_name &>/dev/null
+
+# As a test.
+if type -p executable_name &>/dev/null; then
+ # Program is in PATH.
+fi
+
+# Inverse.
+if ! type -p executable_name &>/dev/null; then
+ # Program is not in PATH.
+fi
+
+# Example (Exit early if program isn't installed).
+if ! type -p convert &>/dev/null; then
+ printf '%s\n' "error: convert isn't installed, exiting..."
+ exit 1
+fi
+```
+
+## Get the current date using `strftime`
+
+Bash’s `printf` has a built-in method of getting the date which we can use
+in place of the `date` command in a lot of cases.
+
+**CAVEAT:** Requires `bash` 4+
+
+**Example Function:**
+
+```sh
+date() {
+ # Usage: date "format"
+ # See: 'man strftime' for format.
+ printf "%($1)T\\n" "-1"
+}
+```
+
+**Example Usage:**
+
+```shell
+# Using above function.
+$ date "%a %d %b - %l:%M %p"
+Fri 15 Jun - 10:00 AM
+
+# Using printf directly.
+$ printf '%(%a %d %b - %l:%M %p)T\n' "-1"
+Fri 15 Jun - 10:00 AM
+
+# Assigning a variable using printf.
+$ printf -v date '%(%a %d %b - %l:%M %p)T\n' '-1'
+$ printf '%s\n' "$date"
+Fri 15 Jun - 10:00 AM
+```
+
+## Generate a UUID V4
+
+**Example Function:**
+
+```sh
+uuid() {
+ # Usage: uuid
+ C="89ab"
+
+ for ((N=0;N<16;++N)); do
+ B="$((RANDOM%256))"
+
+ case "$N" in
+ 6) printf '4%x' "$((B%16))" ;;
+ 8) printf '%c%x' "${C:$RANDOM%${#C}:1}" "$((B%16))" ;;
+
+ 3|5|7|9)
+ printf '%02x-' "$B"
+ ;;
+
+ *)
+ printf '%02x' "$B"
+ ;;
+ esac
+ done
+
+ printf '\n'
+}
+```
+
+**Example Usage:**
+
+```shell
+$ uuid
+d5b6c731-1310-4c24-9fe3-55d556d44374
+```
+
+## Progress bars
+
+This is a simple way of drawing progress bars without needing a for loop
+in the function itself.
+
+**Example Function:**
+
+```sh
+bar() {
+ # Usage: bar 1 10
+ # ^----- Elapsed Percentage (0-100).
+ # ^-- Total length in chars.
+ ((elapsed=$1*$2/100))
+
+ # Create the bar with spaces.
+ printf -v prog "%${elapsed}s"
+ printf -v total "%$(($2-elapsed))s"
+
+ printf '%s\r' "[${prog// /-}${total}]"
+}
+```
+
+**Example Usage:**
+
+```shell
+for ((i=0;i<=100;i++)); do
+ # Pure bash micro sleeps (for the example).
+ (:;:) && (:;:) && (:;:) && (:;:) && (:;:)
+
+ # Print the bar.
+ bar "$i" "10"
+done
+
+printf '\n'
+```
+
+## Get the list of functions from your script
+
+```sh
+get_functions() {
+ # Usage: get_functions
+ IFS=$'\n' read -d "" -ra functions < <(declare -F)
+ printf '%s\n' "${functions[@]//declare -f }"
+}
+```
+
+## Bypass shell aliases
+
+```shell
+# alias
+ls
+
+# command
+# shellcheck disable=SC1001
+\ls
+```
+
+## Bypass shell functions
+
+```shell
+# function
+ls
+
+# command
+command ls
+```
+
+
+
diff --git a/manuscript/chapter2.txt b/manuscript/chapter2.txt
index 90fc12f..e677440 100644
--- a/manuscript/chapter2.txt
+++ b/manuscript/chapter2.txt
@@ -1,94 +1,129 @@
-# Loops
+# Arrays
-## Loop over a range of numbers
+## Reverse an array
-Don't use `seq`.
+Enabling `extdebug` allows access to the `BASH_ARGV` array which stores
+the current function’s arguments in reverse.
-```shell
-# Loop from 0-100 (no variable support).
-for i in {0..100}; do
- printf '%s\n' "$i"
-done
+**Example Function:**
+
+```sh
+reverse_array() {
+ # Usage: reverse_array "array"
+ shopt -s extdebug
+ f()(printf '%s\n' "${BASH_ARGV[@]}"); f "$@"
+ shopt -u extdebug
+}
```
-## Loop over a variable range of numbers
-
-Don't use `seq`.
+**Example Usage:**
```shell
-# Loop from 0-VAR.
-VAR=50
-for ((i=0;i<=VAR;i++)); do
- printf '%s\n' "$i"
-done
+$ reverse_array 1 2 3 4 5
+5
+4
+3
+2
+1
+
+$ arr=(red blue green)
+$ reverse_array "${arr[@]}"
+green
+blue
+red
```
-## Loop over an array
+## Remove duplicate array elements
-```shell
-arr=(apples oranges tomatoes)
+Create a temporary associative array. When setting associative array
+values and a duplicate assignment occurs, bash overwrites the key. This
+allows us to effectively remove array duplicates.
-# Just elements.
-for element in "${arr[@]}"; do
- printf '%s\n' "$element"
-done
+**CAVEAT:** Requires `bash` 4+
+
+**Example Function:**
+
+```sh
+remove_array_dups() {
+ # Usage: remove_array_dups "array"
+ declare -A tmp_array
+
+ for i in "$@"; do
+ [[ "$i" ]] && IFS=" " tmp_array["${i:- }"]=1
+ done
+
+ printf '%s\n' "${!tmp_array[@]}"
+}
```
-## Loop over an array with an index
+**Example Usage:**
```shell
-arr=(apples oranges tomatoes)
+$ remove_array_dups 1 1 2 2 3 3 3 3 3 4 4 4 4 4 5 5 5 5 5 5
+1
+2
+3
+4
+5
-# Elements and index.
-for i in "${!arr[@]}"; do
- printf '%s\n' "${arr[$i]}"
-done
-
-# Alternative method.
-for ((i=0;i<${#arr[@]};i++)); do
- printf '%s\n' "${arr[$i]}"
-done
+$ arr=(red red green blue blue)
+$ remove_array_dups "${arr[@]}"
+red
+green
+blue
```
-## Loop over the contents of a file
+## Random array element
-```shell
-while read -r line; do
- printf '%s\n' "$line"
-done < "file"
+**Example Function:**
+
+```sh
+random_array_element() {
+ # Usage: random_array_element "array"
+ local arr=("$@")
+ printf '%s\n' "${arr[RANDOM % $#]}"
+}
```
-## Loop over files and directories
-
-Don’t use `ls`.
+**Example Usage:**
```shell
-# Greedy example.
-for file in *; do
- printf '%s\n' "$file"
-done
+$ array=(red green blue yellow brown)
+$ random_array_element "${array[@]}"
+yellow
-# PNG files in dir.
-for file in ~/Pictures/*.png; do
- printf '%s\n' "$file"
-done
+# You can also just pass multiple arguments.
+$ random_array_element 1 2 3 4 5 6 7
+3
+```
-# Iterate over directories.
-for dir in ~/Downloads/*/; do
- printf '%s\n' "$dir"
-done
+## Cycle through an array
-# Brace Expansion.
-for file in /path/to/parentdir/{file1,file2,subdir/file3}; do
- printf '%s\n' "$file"
-done
+Each time the `printf` is called, the next array element is printed. When
+the print hits the last array element it starts from the first element
+again.
-# Iterate recursively.
-shopt -s globstar
-for file in ~/Pictures/**/*; do
- printf '%s\n' "$file"
-done
-shopt -u globstar
+```sh
+arr=(a b c d)
+
+cycle() {
+ printf '%s ' "${arr[${i:=0}]}"
+ ((i=i>=${#arr[@]}-1?0:++i))
+}
+```
+
+
+## Toggle between two values
+
+This works the same as above, this is just a different use case.
+
+```sh
+arr=(true false)
+
+cycle() {
+ printf '%s ' "${arr[${i:=0}]}"
+ ((i=i>=${#arr[@]}-1?0:++i))
+}
```
diff --git a/manuscript/chapter3.txt b/manuscript/chapter3.txt
index cdef4a2..90fc12f 100644
--- a/manuscript/chapter3.txt
+++ b/manuscript/chapter3.txt
@@ -1,188 +1,94 @@
-# File handling
+# Loops
-**CAVEAT:** `bash` doesn't handle binary data properly in versions `< 4.4`.
+## Loop over a range of numbers
-## Read a file to a string
-
-Alternative to the `cat` command.
+Don't use `seq`.
```shell
-file_data="$(<"file")"
+# Loop from 0-100 (no variable support).
+for i in {0..100}; do
+ printf '%s\n' "$i"
+done
```
-## Read a file to an array (*by line*)
+## Loop over a variable range of numbers
-Alternative to the `cat` command.
+Don't use `seq`.
```shell
-# Bash <4
-IFS=$'\n' read -d "" -ra file_data < "file"
-
-# Bash 4+
-mapfile -t file_data < "file"
+# Loop from 0-VAR.
+VAR=50
+for ((i=0;i<=VAR;i++)); do
+ printf '%s\n' "$i"
+done
```
-## Get the first N lines of a file
-
-Alternative to the `head` command.
-
-**CAVEAT:** Requires `bash` 4+
-
-**Example Function:**
-
-```sh
-head() {
- # Usage: head "n" "file"
- mapfile -tn "$1" line < "$2"
- printf '%s\n' "${line[@]}"
-}
-```
-
-**Example Usage:**
+## Loop over an array
```shell
-$ head 2 ~/.bashrc
-# Prompt
-PS1='➜ '
+arr=(apples oranges tomatoes)
-$ head 1 ~/.bashrc
-# Prompt
+# Just elements.
+for element in "${arr[@]}"; do
+ printf '%s\n' "$element"
+done
```
-## Get the last N lines of a file
-
-Alternative to the `tail` command.
-
-**CAVEAT:** Requires `bash` 4+
-
-**Example Function:**
-
-```sh
-tail() {
- # Usage: tail "n" "file"
- mapfile -tn 0 line < "$2"
- printf '%s\n' "${line[@]: -$1}"
-}
-```
-
-**Example Usage:**
+## Loop over an array with an index
```shell
-$ tail 2 ~/.bashrc
-# Enable tmux.
-# [[ -z "$TMUX" ]] && exec tmux
+arr=(apples oranges tomatoes)
-$ tail 1 ~/.bashrc
-# [[ -z "$TMUX" ]] && exec tmux
+# Elements and index.
+for i in "${!arr[@]}"; do
+ printf '%s\n' "${arr[$i]}"
+done
+
+# Alternative method.
+for ((i=0;i<${#arr[@]};i++)); do
+ printf '%s\n' "${arr[$i]}"
+done
```
-## Get the number of lines in a file
-
-Alternative to `wc -l`.
-
-**Example Function (bash 4):**
-
-```sh
-lines() {
- # Usage: lines "file"
- mapfile -tn 0 lines < "$1"
- printf '%s\n' "${#lines[@]}"
-}
-```
-
-**Example Function (bash 3):**
-
-This method uses less memory than the `mapfile` method and it's more
-compatible but it's slower for bigger files.
-
-```sh
-lines_loop() {
- # Usage: lines_loop "file"
- count=0
- while IFS= read -r _; do
- ((count++))
- done < "$1"
- printf '%s\n' "$count"
-}
-```
-
-**Example Usage:**
+## Loop over the contents of a file
```shell
-$ lines ~/.bashrc
-48
-
-$ lines_loop ~/.bashrc
-48
+while read -r line; do
+ printf '%s\n' "$line"
+done < "file"
```
-## Count files or directories in directory
+## Loop over files and directories
-This works by passing the output of the glob as function arguments. We
-then count the arguments and print the number.
-
-**Example Function:**
-
-```sh
-count() {
- # Usage: count /path/to/dir/*
- # count /path/to/dir/*/
- printf '%s\n' "$#"
-}
-```
-
-**Example Usage:**
+Don’t use `ls`.
```shell
-# Count all files in dir.
-$ count ~/Downloads/*
-232
+# Greedy example.
+for file in *; do
+ printf '%s\n' "$file"
+done
-# Count all dirs in dir.
-$ count ~/Downloads/*/
-45
+# PNG files in dir.
+for file in ~/Pictures/*.png; do
+ printf '%s\n' "$file"
+done
-# Count all jpg files in dir.
-$ count ~/Pictures/*.jpg
-64
-```
+# Iterate over directories.
+for dir in ~/Downloads/*/; do
+ printf '%s\n' "$dir"
+done
-## Create an empty file
+# Brace Expansion.
+for file in /path/to/parentdir/{file1,file2,subdir/file3}; do
+ printf '%s\n' "$file"
+done
-Alternative to `touch`.
-
-```shell
-# Shortest.
-:> file
-
-# Longer alternatives:
-echo -n > file
-printf '' > file
-```
-
-## Extract lines between two markers
-
-**Example Function:**
-
-```sh
-extract() {
- # Usage: extract file "opening marker" "closing marker"
- while IFS=$'\n' read -r line; do
- [[ "$extract" && "$line" != "$3" ]] && \
- printf '%s\n' "$line"
-
- [[ "$line" == "$2" ]] && extract=1
- [[ "$line" == "$3" ]] && extract=
- done < "$1"
-}
-```
-
-**Example Usage:**
-
-```shell
-# Extract code blocks from MarkDown file.
-$ extract ~/projects/pure-bash/README.md '```sh' '```'
-# Output here...
+# Iterate recursively.
+shopt -s globstar
+for file in ~/Pictures/**/*; do
+ printf '%s\n' "$file"
+done
+shopt -u globstar
```
diff --git a/manuscript/chapter4.txt b/manuscript/chapter4.txt
index 397e4ad..cdef4a2 100644
--- a/manuscript/chapter4.txt
+++ b/manuscript/chapter4.txt
@@ -1,50 +1,188 @@
-# File Paths
+# File handling
-## Get the directory name of a file path
+**CAVEAT:** `bash` doesn't handle binary data properly in versions `< 4.4`.
-Alternative to the `dirname` command.
+## Read a file to a string
+
+Alternative to the `cat` command.
+
+```shell
+file_data="$(<"file")"
+```
+
+## Read a file to an array (*by line*)
+
+Alternative to the `cat` command.
+
+```shell
+# Bash <4
+IFS=$'\n' read -d "" -ra file_data < "file"
+
+# Bash 4+
+mapfile -t file_data < "file"
+```
+
+## Get the first N lines of a file
+
+Alternative to the `head` command.
+
+**CAVEAT:** Requires `bash` 4+
**Example Function:**
```sh
-dirname() {
- # Usage: dirname "path"
- printf '%s\n' "${1%/*}/"
+head() {
+ # Usage: head "n" "file"
+ mapfile -tn "$1" line < "$2"
+ printf '%s\n' "${line[@]}"
}
```
**Example Usage:**
```shell
-$ dirname ~/Pictures/Wallpapers/1.jpg
-/home/black/Pictures/Wallpapers/
+$ head 2 ~/.bashrc
+# Prompt
+PS1='➜ '
-$ dirname ~/Pictures/Downloads/
-/home/black/Pictures/
+$ head 1 ~/.bashrc
+# Prompt
```
-## Get the base-name of a file path
+## Get the last N lines of a file
-Alternative to the `basename` command.
+Alternative to the `tail` command.
+
+**CAVEAT:** Requires `bash` 4+
**Example Function:**
```sh
-basename() {
- # Usage: basename "path"
- : "${1%/}"
- printf '%s\n' "${_##*/}"
+tail() {
+ # Usage: tail "n" "file"
+ mapfile -tn 0 line < "$2"
+ printf '%s\n' "${line[@]: -$1}"
}
```
**Example Usage:**
```shell
-$ basename ~/Pictures/Wallpapers/1.jpg
-1.jpg
+$ tail 2 ~/.bashrc
+# Enable tmux.
+# [[ -z "$TMUX" ]] && exec tmux
-$ basename ~/Pictures/Downloads/
-Downloads
+$ tail 1 ~/.bashrc
+# [[ -z "$TMUX" ]] && exec tmux
+```
+
+## Get the number of lines in a file
+
+Alternative to `wc -l`.
+
+**Example Function (bash 4):**
+
+```sh
+lines() {
+ # Usage: lines "file"
+ mapfile -tn 0 lines < "$1"
+ printf '%s\n' "${#lines[@]}"
+}
+```
+
+**Example Function (bash 3):**
+
+This method uses less memory than the `mapfile` method and it's more
+compatible but it's slower for bigger files.
+
+```sh
+lines_loop() {
+ # Usage: lines_loop "file"
+ count=0
+ while IFS= read -r _; do
+ ((count++))
+ done < "$1"
+ printf '%s\n' "$count"
+}
+```
+
+**Example Usage:**
+
+```shell
+$ lines ~/.bashrc
+48
+
+$ lines_loop ~/.bashrc
+48
+```
+
+## Count files or directories in directory
+
+This works by passing the output of the glob as function arguments. We
+then count the arguments and print the number.
+
+**Example Function:**
+
+```sh
+count() {
+ # Usage: count /path/to/dir/*
+ # count /path/to/dir/*/
+ printf '%s\n' "$#"
+}
+```
+
+**Example Usage:**
+
+```shell
+# Count all files in dir.
+$ count ~/Downloads/*
+232
+
+# Count all dirs in dir.
+$ count ~/Downloads/*/
+45
+
+# Count all jpg files in dir.
+$ count ~/Pictures/*.jpg
+64
+```
+
+## Create an empty file
+
+Alternative to `touch`.
+
+```shell
+# Shortest.
+:> file
+
+# Longer alternatives:
+echo -n > file
+printf '' > file
+```
+
+## Extract lines between two markers
+
+**Example Function:**
+
+```sh
+extract() {
+ # Usage: extract file "opening marker" "closing marker"
+ while IFS=$'\n' read -r line; do
+ [[ "$extract" && "$line" != "$3" ]] && \
+ printf '%s\n' "$line"
+
+ [[ "$line" == "$2" ]] && extract=1
+ [[ "$line" == "$3" ]] && extract=
+ done < "$1"
+}
+```
+
+**Example Usage:**
+
+```shell
+# Extract code blocks from MarkDown file.
+$ extract ~/projects/pure-bash/README.md '```sh' '```'
+# Output here...
```
diff --git a/manuscript/chapter5.txt b/manuscript/chapter5.txt
index ef9d0ce..397e4ad 100644
--- a/manuscript/chapter5.txt
+++ b/manuscript/chapter5.txt
@@ -1,16 +1,50 @@
-# Variables
+# File Paths
-## Assign and access a variable using a variable
+## Get the directory name of a file path
+
+Alternative to the `dirname` command.
+
+**Example Function:**
+
+```sh
+dirname() {
+ # Usage: dirname "path"
+ printf '%s\n' "${1%/*}/"
+}
+```
+
+**Example Usage:**
```shell
-hello_world="test"
+$ dirname ~/Pictures/Wallpapers/1.jpg
+/home/black/Pictures/Wallpapers/
-# Create the variable name.
-var1="world"
-var2="hello_${var1}"
+$ dirname ~/Pictures/Downloads/
+/home/black/Pictures/
+```
-# Print the value of the variable name stored in 'hello_$var1'.
-printf '%s\n' "${!var2}"
+## Get the base-name of a file path
+
+Alternative to the `basename` command.
+
+**Example Function:**
+
+```sh
+basename() {
+ # Usage: basename "path"
+ : "${1%/}"
+ printf '%s\n' "${_##*/}"
+}
+```
+
+**Example Usage:**
+
+```shell
+$ basename ~/Pictures/Wallpapers/1.jpg
+1.jpg
+
+$ basename ~/Pictures/Downloads/
+Downloads
```
diff --git a/manuscript/chapter6.txt b/manuscript/chapter6.txt
index b83c87a..ef9d0ce 100644
--- a/manuscript/chapter6.txt
+++ b/manuscript/chapter6.txt
@@ -1,57 +1,17 @@
-# Escape Sequences
+# Variables
-Contrary to popular belief, there's no issue in using raw escape sequences. Using `tput` just abstracts the same ANSI escape sequences. What's worse is that `tput` isn't actually portable, there are a number of different `tput` variants on different Operating Systems each with different commands (*try and run `tput setaf 3` on a FreeBSD system*). The easiest solution ends up being raw ANSI sequences.
+## Assign and access a variable using a variable
-## Text Colors
+```shell
+hello_world="test"
-**NOTE:** Sequences requiring RGB values only work in True-Color Terminal Emulators.
-
-| Sequence | What does it do? | Value |
-| -------- | ---------------- | ----- |
-| `\e[38;5;m` | Set text foreground color. | `0-255`
-| `\e[48;5;m` | Set text background color. | `0-255`
-| `\e[38;2;;;m` | Set text foreground color to RGB color. | `R`, `G`, `B`
-| `\e[48;2;;;m` | Set text background color to RGB color. | `R`, `G`, `B`
-
-## Text Attributes
-
-| Sequence | What does it do? |
-| -------- | ---------------- |
-| `\e[m` | Reset text formatting and colors.
-| `\e[1m` | Bold text. |
-| `\e[2m` | Faint text. |
-| `\e[3m` | Italic text. |
-| `\e[4m` | Underline text. |
-| `\e[5m` | Slow blink. |
-| `\e[7m` | Swap foreground and background colors. |
-
-
-## Cursor Movement
-
-| Sequence | What does it do? | Value |
-| -------- | ---------------- | ----- |
-| `\e[;H` | Move cursor to absolute position. | `line`, `column`
-| `\e[H` | Move cursor to home position (`0,0`). |
-| `\e[A` | Move cursor up N lines. | `num`
-| `\e[B` | Move cursor down N lines. | `num`
-| `\e[C` | Move cursor right N columns. | `num`
-| `\e[D` | Move cursor left N columns. | `num`
-| `\e[s` | Save cursor position. |
-| `\e[u` | Restore cursor position. |
-
-
-## Erasing Text
-
-| Sequence | What does it do? |
-| -------- | ---------------- |
-| `\e[K` | Erase from cursor position to end of line.
-| `\e[1K` | Erase from cursor position to start of line.
-| `\e[2K` | Erase the entire current line.
-| `\e[J` | Erase from the current line to the bottom of the screen.
-| `\e[1J` | Erase from the current line to the top of the screen.
-| `\e[2J` | Clear the screen.
-| `\e[2J\e[H` | Clear the screen and move cursor to `0,0`.
+# Create the variable name.
+var1="world"
+var2="hello_${var1}"
+# Print the value of the variable name stored in 'hello_$var1'.
+printf '%s\n' "${!var2}"
+```
diff --git a/manuscript/chapter7.txt b/manuscript/chapter7.txt
index 187315c..b83c87a 100644
--- a/manuscript/chapter7.txt
+++ b/manuscript/chapter7.txt
@@ -1,67 +1,56 @@
-# Parameter Expansion
+# Escape Sequences
-## Indirection
+Contrary to popular belief, there's no issue in using raw escape sequences. Using `tput` just abstracts the same ANSI escape sequences. What's worse is that `tput` isn't actually portable, there are a number of different `tput` variants on different Operating Systems each with different commands (*try and run `tput setaf 3` on a FreeBSD system*). The easiest solution ends up being raw ANSI sequences.
-| Parameter | What does it do? |
-| --------- | ---------------- |
-| `${!VAR}` | Access a variable based on the value of `VAR`. See: [link](#assign-and-access-a-variable-using-a-variable)
-| `${!VAR*}` | Expand to `IFS` separated list of variable names starting with `VAR`. |
-| `${!VAR@}` | Expand to `IFS` separated list of variable names starting with `VAR`. |
+## Text Colors
+
+**NOTE:** Sequences requiring RGB values only work in True-Color Terminal Emulators.
+
+| Sequence | What does it do? | Value |
+| -------- | ---------------- | ----- |
+| `\e[38;5;m` | Set text foreground color. | `0-255`
+| `\e[48;5;m` | Set text background color. | `0-255`
+| `\e[38;2;;;m` | Set text foreground color to RGB color. | `R`, `G`, `B`
+| `\e[48;2;;;m` | Set text background color to RGB color. | `R`, `G`, `B`
+
+## Text Attributes
+
+| Sequence | What does it do? |
+| -------- | ---------------- |
+| `\e[m` | Reset text formatting and colors.
+| `\e[1m` | Bold text. |
+| `\e[2m` | Faint text. |
+| `\e[3m` | Italic text. |
+| `\e[4m` | Underline text. |
+| `\e[5m` | Slow blink. |
+| `\e[7m` | Swap foreground and background colors. |
-## Replacement
+## Cursor Movement
-| Parameter | What does it do? |
-| --------- | ---------------- |
-| `${VAR#PATTERN}` | Remove shortest match of pattern from start of string. |
-| `${VAR##PATTERN}` | Remove longest match of pattern from start of string. |
-| `${VAR%PATTERN}` | Remove shortest match of pattern from end of string. |
-| `${VAR%%PATTERN}` | Remove longest match of pattern from end of string. |
-| `${VAR/PATTERN/REPLACE}` | Replace first match with string.
-| `${VAR//PATTERN/REPLACE}` | Replace all matches with string.
-| `${VAR/PATTERN}` | Remove first match.
-| `${VAR//PATTERN}` | Remove all matches.
-
-## Length
-
-| Parameter | What does it do? |
-| --------- | ---------------- |
-| `${#VAR}` | Length of var in characters.
-| `${#ARR[@]}` | Length of array in elements.
-
-## Expansion
-
-| Parameter | What does it do? |
-| --------- | ---------------- |
-| `${VAR:OFFSET}` | Remove first `N` chars from variable.
-| `${VAR:OFFSET:LENGTH}` | Get substring from `N` character to `N` character.
(`${VAR:10:10}`: Get sub-string from char `10` to char `20`)
-| `${VAR:: OFFSET}` | Get first `N` chars from variable.
-| `${VAR:: -OFFSET}` | Remove last `N` chars from variable.
-| `${VAR: -OFFSET}` | Get last `N` chars from variable.
-| `${VAR:OFFSET:-OFFSET}` | Cut first `N` chars and last `N` chars. | `bash 4.2+` |
-
-## Case Modification
-
-| Parameter | What does it do? | CAVEAT |
-| --------- | ---------------- | ------ |
-| `${VAR^}` | Uppercase first character. | `bash 4+` |
-| `${VAR^^}` | Uppercase all characters. | `bash 4+` |
-| `${VAR,}` | Lowercase first character. | `bash 4+` |
-| `${VAR,,}` | Lowercase all characters. | `bash 4+` |
+| Sequence | What does it do? | Value |
+| -------- | ---------------- | ----- |
+| `\e[;H` | Move cursor to absolute position. | `line`, `column`
+| `\e[H` | Move cursor to home position (`0,0`). |
+| `\e[A` | Move cursor up N lines. | `num`
+| `\e[B` | Move cursor down N lines. | `num`
+| `\e[C` | Move cursor right N columns. | `num`
+| `\e[D` | Move cursor left N columns. | `num`
+| `\e[s` | Save cursor position. |
+| `\e[u` | Restore cursor position. |
-## Default Value
+## Erasing Text
-| Parameter | What does it do? |
-| --------- | ---------------- |
-| `${VAR:-STRING}` | If `VAR` is empty or unset, use `STRING` as it's value.
-| `${VAR-STRING}` | If `VAR` is unset, use `STRING` as it's value.
-| `${VAR:=STRING}` | If `VAR` is empty or unset, set the value of `VAR` to `STRING`.
-| `${VAR=STRING}` | If `VAR` is unset, set the value of `VAR` to `STRING`.
-| `${VAR:+STRING}` | If `VAR` isn't empty, use `STRING` as it's value.
-| `${VAR+STRING}` | If `VAR` is set, use `STRING` as it's value.
-| `${VAR:?STRING}` | Display an error if empty or unset.
-| `${VAR?STRING}` | Display an error if unset.
+| Sequence | What does it do? |
+| -------- | ---------------- |
+| `\e[K` | Erase from cursor position to end of line.
+| `\e[1K` | Erase from cursor position to start of line.
+| `\e[2K` | Erase the entire current line.
+| `\e[J` | Erase from the current line to the bottom of the screen.
+| `\e[1J` | Erase from the current line to the top of the screen.
+| `\e[2J` | Clear the screen.
+| `\e[2J\e[H` | Clear the screen and move cursor to `0,0`.
diff --git a/manuscript/chapter8.txt b/manuscript/chapter8.txt
index 4a926e1..187315c 100644
--- a/manuscript/chapter8.txt
+++ b/manuscript/chapter8.txt
@@ -1,42 +1,68 @@
-# Brace Expansion
+# Parameter Expansion
-## Ranges
+## Indirection
-```shell
-# Syntax: {..}
+| Parameter | What does it do? |
+| --------- | ---------------- |
+| `${!VAR}` | Access a variable based on the value of `VAR`. See: [link](#assign-and-access-a-variable-using-a-variable)
+| `${!VAR*}` | Expand to `IFS` separated list of variable names starting with `VAR`. |
+| `${!VAR@}` | Expand to `IFS` separated list of variable names starting with `VAR`. |
-# Print numbers 1-100.
-echo {1..100}
-# Print range of floats.
-echo 1.{1..9}
+## Replacement
-# Print chars a-z.
-echo {a..z}
-echo {A..Z}
+| Parameter | What does it do? |
+| --------- | ---------------- |
+| `${VAR#PATTERN}` | Remove shortest match of pattern from start of string. |
+| `${VAR##PATTERN}` | Remove longest match of pattern from start of string. |
+| `${VAR%PATTERN}` | Remove shortest match of pattern from end of string. |
+| `${VAR%%PATTERN}` | Remove longest match of pattern from end of string. |
+| `${VAR/PATTERN/REPLACE}` | Replace first match with string.
+| `${VAR//PATTERN/REPLACE}` | Replace all matches with string.
+| `${VAR/PATTERN}` | Remove first match.
+| `${VAR//PATTERN}` | Remove all matches.
-# Nesting.
-echo {A..Z}{0..9}
+## Length
-# Print zero-padded numbers.
-# CAVEAT: bash 4+
-echo {01..100}
+| Parameter | What does it do? |
+| --------- | ---------------- |
+| `${#VAR}` | Length of var in characters.
+| `${#ARR[@]}` | Length of array in elements.
-# Change increment amount.
-# Syntax: {....}
-# CAVEAT: bash 4+
-echo {1..10..2} # Increment by 2.
-```
+## Expansion
-## String Lists
+| Parameter | What does it do? |
+| --------- | ---------------- |
+| `${VAR:OFFSET}` | Remove first `N` chars from variable.
+| `${VAR:OFFSET:LENGTH}` | Get substring from `N` character to `N` character.
(`${VAR:10:10}`: Get sub-string from char `10` to char `20`)
+| `${VAR:: OFFSET}` | Get first `N` chars from variable.
+| `${VAR:: -OFFSET}` | Remove last `N` chars from variable.
+| `${VAR: -OFFSET}` | Get last `N` chars from variable.
+| `${VAR:OFFSET:-OFFSET}` | Cut first `N` chars and last `N` chars. | `bash 4.2+` |
-```shell
-echo {apples,oranges,pears,grapes}
+## Case Modification
+
+| Parameter | What does it do? | CAVEAT |
+| --------- | ---------------- | ------ |
+| `${VAR^}` | Uppercase first character. | `bash 4+` |
+| `${VAR^^}` | Uppercase all characters. | `bash 4+` |
+| `${VAR,}` | Lowercase first character. | `bash 4+` |
+| `${VAR,,}` | Lowercase all characters. | `bash 4+` |
+
+
+## Default Value
+
+| Parameter | What does it do? |
+| --------- | ---------------- |
+| `${VAR:-STRING}` | If `VAR` is empty or unset, use `STRING` as it's value.
+| `${VAR-STRING}` | If `VAR` is unset, use `STRING` as it's value.
+| `${VAR:=STRING}` | If `VAR` is empty or unset, set the value of `VAR` to `STRING`.
+| `${VAR=STRING}` | If `VAR` is unset, set the value of `VAR` to `STRING`.
+| `${VAR:+STRING}` | If `VAR` isn't empty, use `STRING` as it's value.
+| `${VAR+STRING}` | If `VAR` is set, use `STRING` as it's value.
+| `${VAR:?STRING}` | Display an error if empty or unset.
+| `${VAR?STRING}` | Display an error if unset.
-# Example Usage:
-# Remove dirs Movies, Music and ISOS from ~/Downloads/.
-rm -rf ~/Downloads/{Movies,Music,ISOS}
-```
diff --git a/manuscript/chapter9.txt b/manuscript/chapter9.txt
index 5fc7c0c..4a926e1 100644
--- a/manuscript/chapter9.txt
+++ b/manuscript/chapter9.txt
@@ -1,30 +1,41 @@
-# Arithmetic
+# Brace Expansion
-## Simpler syntax to set variables
+## Ranges
```shell
-# Simple math
-((var=1+2))
+# Syntax: {..}
-# Decrement/Increment variable
-((var++))
-((var--))
-((var+=1))
-((var-=1))
+# Print numbers 1-100.
+echo {1..100}
-# Using variables
-((var=var2*arr[2]))
+# Print range of floats.
+echo 1.{1..9}
+
+# Print chars a-z.
+echo {a..z}
+echo {A..Z}
+
+# Nesting.
+echo {A..Z}{0..9}
+
+# Print zero-padded numbers.
+# CAVEAT: bash 4+
+echo {01..100}
+
+# Change increment amount.
+# Syntax: {....}
+# CAVEAT: bash 4+
+echo {1..10..2} # Increment by 2.
```
-## Ternary tests
+## String Lists
```shell
-# Set the value of var to var2 if var2 is greater than var.
-# var: variable to set.
-# var2>var: Condition to test.
-# ?var2: If the test succeeds.
-# :var: If the test fails.
-((var=var2>var?var2:var))
+echo {apples,oranges,pears,grapes}
+
+# Example Usage:
+# Remove dirs Movies, Music and ISOS from ~/Downloads/.
+rm -rf ~/Downloads/{Movies,Music,ISOS}
```