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
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
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
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"
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"
Note, this also works with single quotes!
foo="fooo" a=$(cat <<EOF hello '$foo' EOF ) echo "$a"
without interpolation
foo="bar" a=$(cat <<'EOF' hello $foo EOF ) echo "$a"
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
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
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
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
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
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[@]}
arr=("foo" "bar" "baz") for x in "${arr[@]}"; do echo $x done
Length
arr=("foo" "bar" "baz") echo ${#arr[@]}
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"
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
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
While printf
does not.
printf "foo" | od -c
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"
Numbered List
echo $PATH | tr ":" "\n" | nl