Bash
Style
Google Shell Style Guide
Interestingly, says to use
#!/bin/bash
.-
One person's opinions, but I like them.
Function Parameters
How to specify bash functions with required parameters. When the function is applied to the wrong number of arguments, a non-0 exit status should be returned.
One important question: is the empty string ""
a valid parameter?
Using [[ (new-test)
The double-bracket [[
syntax is called "new-test".
-n
is a string comparison operator that tests if a string is not null zero-length.
foo() { [[ -n "$1" ]] || { echo "Error: param 1 is required" return 1 } echo "Param is: $1" } foo foo "" foo bar
Error: param 1 is required Error: param 1 is required Param is: bar
Using [ (test)
Same as above, but not as good. For more info, see FAQ: "What is the
difference between test
, [
and [[
?"
foo() { [ -n "$1" ] || { echo "Error: param 1 is required" return 1 } }
Note that [
]
is an alias for test
. The above example could be written
as:
foo() { a=${1-} test -n "$a" || { echo "Error: a is required" return 1 } }
if-else
foo() { if [ -z ${1+x} ]; then echo "Error: param 1 is required" return 1 else local a=$1 fi echo "a is '$a'" } foo foo "" foo bar
Error: param 1 is required a is '' a is 'bar'
Set
set -u
Without set -u
this works fine and prints nothing, even though foo
is not
bound.
echo $foo
This produces an error: "bash: line 2: foo: unbound variable" (TODO: org mode to print this).
set -u echo $foo
Works with eval
.
set -u vars="FOO=foo BAR=bar" eval $vars echo $FOO
foo
Script Arguments
Same patterns as function parameters can be used.
if [ -z ${1+x} ]; then echo "arg 1 (foo) is required" exit 1 fi foo=$1 echo "foo is: '$foo'"
Script Options
getopts
See home/bin/ssm script for a good example.
showHelp() { cat <<END Usage: $(basename "$0") [-p profile] END } while getopts :hp: arg; do case ${arg} in h) showHelp exit 0 ;; p) profile="$OPTARG" ;; \?) echo "Invalid option: -$OPTARG" >&2 exit 1 ;; :) echo "Option -$OPTARG requires an argument" >&2 exit 1 ;; esac done shift $((OPTIND -1)) # Define default option values profile=${profile:="chrisc"} aws="aws --profile $profile --region us-east-1"
Heredocs
Whitespace
Use <<-
. Each line in the HEREDOC has to begin with a TAB. Having trouble
getting this working with org-babel.
foo="bar" a=$(cat <<-EOF hello $foo EOF ) echo "$a"
hello bar EOF
Interpolation
A heredoc can interpolate variable names to values, or the whole heredoc can be taken completely literally without any interpolation. The difference is whether the limit string has single quotes around it.
with interpolation
foo="fooo" a=$(cat <<EOF hello $foo EOF ) echo "$a"
hello fooo
Note, this also works with single quotes!
foo="fooo" a=$(cat <<EOF hello '$foo' EOF ) echo "$a"
hello 'fooo'
without interpolation
foo="bar" a=$(cat <<'EOF' hello $foo EOF ) echo "$a"
hello $foo
Verify Environment Variables
Verify some environment variables exist.
: "${FOO:?FOO is not set}" : "${BAR:?BAR is not set}"
Parameter Expansion
Resources
- GNU Bash docs section on paremeter expansion
- The Open Group has some excellent POSIX documentation with a section on parameter expansion
:-
Docs for ${parameter:-[word]}
If parameter is unset or null, the expansion of word (or an empty string if word is omitted) shall be substituted; otherwise, the value of parameter shall be substituted.
var="" if [ -z ${var:-x"} ]; then echo "var is unset" else echo "var is set to '$var'" fi
var is set to ''
if [ -z ${var:-"foo"} ]; then echo "var is unset" else echo "var is set to '$var'" fi
+
Docs for ${parameter:+[word]}
If parameter is unset or null, null shall be substituted; otherwise, the expansion of word (or an empty string if word is omitted) shall be substituted.
-z
is a comparison operator that tests if the argument is null or empty.
var="" if [ -z ${var+x} ]; then echo "var is unset" else echo "var is set to '$var'" fi
var is set to ''
In this case, var
is the empty string, which is not "unset or null".
Therefore the +
parameter expansion expands this to x
(which passes the
-z
check).
if [ -z ${var+x} ]; then echo "var is unset" else echo "var is set to '$var'" fi
var is unset
In this case, var
has not been declared, so it is "unset or null". The +
parameter expansion expands it to null
.
var="" if [ -z ${var+} ]; then echo "var is unset" else echo "var is set to '$var'" fi
var is unset
In this case we leave the word
spot empty in the parameter expansion, so if
var
is empty, it is subsituted by an empty string. Now the -z
test sees an
empty argument, and this prints out var is unset
.
##
Remove largest prefix pattern.
a_list=("foo/aa.foo" "bar/ab.foo" "baz/ac.foo") for a in ${a_list[@]}; do # The "*/" is a regex echo ${a##*/} done
aa.foo ab.foo ac.foo
Arrays
* vs @
See this StackOverflow answer.
The difference between
[@]
and[*]
-expanded arrays in double-quotes is that"${myarray[@]}"
leads to each element of the array being treated as a separate shell-word, while"${myarray[*]}"
results in a single shell-word with all of the elements of the array separated by spaces (or whatever the first character ofIFS
is).Usually, the
[@]
behavior is what you want.
Basic
arr=("foo" "bar" "baz") echo ${arr[@]}
foo bar baz
arr=("foo" "bar" "baz") for x in "${arr[@]}"; do echo $x done
foo bar baz
Length
arr=("foo" "bar" "baz") echo ${#arr[@]}
3
Pipe array to xargs
Start a subprocess per element in an array, where each subprocess runs $script
.
script=$(cat <<"EOF" echo "some {}" EOF ) arr=("foo" "bar" "baz") printf "%s\n" "${arr[@]}" \ | xargs -n 1 \ -P ${#arr[@]} \ -I {} \ bash -c "$script"
some foo some bar some baz
Associative Arrays
Using cut
arr=("key1,val1", "key2,val2", "key3,val3") for i in "${arr[@]}"; do k=$(echo "$i" | cut -d, -f 1) v=$(echo "$i" | cut -d, -f 2) echo "Key is $k and val is $v" done
Key is key1 and val is val1 Key is key2 and val is val2 Key is key3 and val is val3
Range
for i in {1..5}; do echo $i done
Strings, Echo, and Printf
Echo adds a newline
Notice that echo
adds a newline.
echo "foo" | od -c
0000000 f o o \n 0000004
While printf
does not.
printf "foo" | od -c
0000000 f o o 0000003
Print a multiline string
echo $a
prints a heredoc on one line, while echo "$a"
preserves the
newlines in the heredoc. Why is that?
a=$(cat <<EOF hello world EOF ) echo $a echo "---" echo "$a"
hello world --- hello world
Numbered List
echo $PATH | tr ":" "\n" | nl
1 /Users/chris.clark/.rbenv/shims 2 /usr/local/Cellar/pyenv-virtualenv/1.1.5/shims 3 /Users/chris.clark/.pyenv/shims 4 /Users/chris.clark/go/bin 5 /Users/chris.clark/bin 6 /Users/chris.clark/.local/bin 7 /Users/chris.clark/.cargo/bin 8 /Users/chris.clark/.cabal/bin 9 /Users/chris.clark/IronNet/bin 10 /usr/local/sbin 11 /usr/local/opt/make/libexec/gnubin 12 /usr/local/opt/texinfo/bin 13 /usr/local/opt/openjdk/bin 14 /usr/local/opt/curl/bin 15 /usr/local/opt/openssl@1.1/bin 16 /usr/local/bin 17 /usr/bin 18 /bin 19 /usr/sbin 20 /sbin 21 /Library/TeX/texbin 22 /Applications/Wireshark.app/Contents/MacOS