bash scripts

last modified

2023–11–8

References are to the Bash Manual.

Line break

A command can be broken over several input line by ending lines with a backslash:

find ~ -name ".*" -prune -o -type f \
    -name "*.text" -print -o \
    -name "*.md" -print -o \
    -name "*.qmd" -print -o \
    -name "*.Rmd" -print -o \
    -name "*.Rmd" -print > mdfiles.txt

Redirections

Redirect file to stdin: <cmd> < <file>
Redirect stdout to file: <cmd> > <file>
Redirect stdout to file, appending: <cmd> >> <file>
Redirect stderr to file: <cmd> 2> <file>
Redirect stderr to stdout: <cmd> 2>&1
Redirect stdout and stderr to file: <cmd> &> <file>

3.6 Redirections

Pipelines, lists of commands, and grouping commands

Pipe stdout to stdin: <cmdA> | <cmdB>
Pipe stdout and stderr to stdin: <cmdA> |& <cmdB>

Execute in background (subshell): <cmd> &
Execute in sequence: <cmdA> ; <cmdB>
Execute in sequence if previous success: <cmdA> && <cmdB>
Execute in sequence if previous failure: <cmdA> || <cmdB>

Grouping: { <cmdA> ; <cmdB> ; <cmdC> ; }
Grouping (in subshell): ( <cmdA> ; <cmdB> ; <cmdC> )
Grouping can be used to apply redirection or background execution to a sequence of commands.

3.2.3 Pipelines
3.2.4 Lists of Commands
3.2.5.3 Grouping Commands

Variables and variable expressions

Set variable:

VAR=<value>

Use variable (“expansion”): ${VAR}
Value of variable whose name is the value of the variable: ${!VAR}

Length of variable: ${#VAR}
Part from offset: ${parameter:<offset>}
Part from offset up to length: ${parameter:<offset>:<length>}
Part from offset up to negative offset: ${parameter:<offset>:-<offset>}
These apply to characters in strings or elements of an array.

Without shortest matching pattern at the beginning: ${VAR#<pattern>}
Without longest matching pattern at the beginning: ${VAR##<pattern>}
Without shortest matching pattern at the end: ${VAR%<pattern>}
E.g. filename without extension: ${FILENAME%.*}
Without longest matching pattern at the end: ${VAR%%<pattern>}
Replace first occurrence of pattern by string: ${VAR/<pattern>/<string>}
Replace all occurrences of pattern by string: ${VAR//<pattern>/<string>}
Translate to uppercase: ${VAR@U}
Translate to lowercase: ${VAR@L}

Separate file path into its parts

dirname="${pathname%/*}"
name="${pathname##*/}"
basename="${name%.*}"
extension="${name##*.}"
if [ "$dirname" == "$pathname" ]; then
  dirname="."
fi
if [ "$extension" == "$basename" ]; then
  extension=""
fi

3.5.3 Shell Parameter Expansion

Special variables

Number of positional parameters: $#
Positional parameters: $1, $2, $3, …
Positional parameter from index in variable: ${!<var>}

Last exit status: $?
PID of last background process: $!

"$*" is equivalent to "$1 $2 …"
"$@" is equivalent to "$1" "$2" ….
Outside of quotes, both $* and $@ are equivalent to $1 $2 ….
Almost always, "$@" is the right choice.

3.4.2 Special Parameters

Pause and user input

# wait for keypress
read -n 1
# show text and wait for keypress
read -n 1 -p "text"
# show text, wait for keypress, and store it in variable
read -n 1 -p "text" var
# show text, read string, and store it in variable
read -p "text" var

Arrays

The command

args=(
    --cap-add=NET_ADMIN
    --log-driver json-file
    --log-opt max-size=10m
)

creates an array with five elements (elements are separated by whitespace).

Access element by index: ${args[<index>]}
${args} evaluates to the first element, ${args[0]}

"${args[*]}" is equivalent to "${args[0]} ${args[1]} …"
"${args[@]}" is equivalent to "${args[0]}" "${args[1]}" ….
Outside of quotes, both $* and $@ are equivalent to ${args[0]} ${args[1]} ….

6.7 Arrays

Shell arithmetic

Expressions in $(( )) are treated as arithmetic. Variables can be referenced without prefixed $. Multiple expressions can be separated with ,, and the last value is returned.

Numerical operators: +, -, *, /, %, **.
Increment and decrement, pre- and post-: ++, --.
Assignment: =, +=, -=, *=, /=, %=.

6.5 Shell Arithmetic.

Conditional if

E.g. check whether NUM is numerically zero, and if yes print a message:

if [[ $NUM -eq 0 ]] ; then echo "Equals zero" ; fi

String comparison operators: >, <, = or ==, !=.
Integer comparison operators: -gt, -lt, -ge, -le, -eq, -ne.
Logical operators: &&, ||, !.
Regular file exists: -f <name>.
Directory exists: -d <name>.
Length of string is zero: -z <string>.
Length of string is nonzero: -n <string>.
Grouping by: (, ).

= or == can also take a pattern as its second argument, =~ matches with a regular expression.

[[ is a bash-builtin command which indicates the result through its exit code. It can also be used with while and until loops.

3.2.5.2 Conditional Constructs
6.4 Bash Conditional Expressions
3.2.5.1 Looping Constructs

Multiple conditional case

echo -n "Enter the name of an animal: "
read ANIMAL
echo -n "The $ANIMAL has "
case $ANIMAL in
  horse | dog | cat)
    echo -n "four"
    ;;
  man | kangaroo )
    echo -n "two"
    ;;
  *)
    echo -n "an unknown number of"
    ;;
esac
echo " legs."

Each clause is defined by a pattern to be matched with the specified value, and patterns are checked in sequence.

3.2.5.2 Conditional Constructs

for loop

Over integers, from START to END:

for (( NUM = START; NUM <= END; NUM++ )); do echo $NUM ; done

In this form, the for (( )), the parentheses contain three arithmetic expressions.

Over a list, e.g. of directory entries:

for NAME in * ; do echo $NAME ; done

3.2.5.1 Looping Constructs

while loop and read file line by line

while IFS= read -r line; do
    echo "Text read from file: $line"
done < $FILENAME

The same construct can also be piped into.