--- title: Bash subtitle: best practices layout: plain links: - {rel: stylesheet, href: 'assets/css/bash.css'} features: - title: Script header topics: - do: - | #!/usr/bin/env bash set -o errexit -o nounset -o pipefail shopt -s inherit_errexit lastpipe dont: - | #!/bin/sh -e - title: Arrays topics: - title: Declaration do: - | local -a xs=() declare -a xs=() local -A xs=() declare -A xs=() dont: - | local -a xs declare -a xs local -A xs declare -A xs # Doesn't work with nounset: echo "${#xs[@]}" - title: Expansion do: - | func ${arr[@]+"${arr[@]}"} dont: - | # Doesn't work with nounset: func "${arr[@]}" - | # Expands to 0 arguments instead of 1: declare -a arr=('') func "${arr[@]+"${arr[@]}"}" - title: unset do: - | unset -v 'arr[x]' unset -v 'arr[$i]' dont: - | # May break due to globbing: unset -v arr[x] # In addition, possible quoting problem: unset -v arr[$i] # Doesn't work for some reason: unset -v 'arr["x"]' unset -v 'arr["]"]' # Also rejected: unset -v 'arr["$i"]' # An insightful discussion on the topic: # https://lists.gnu.org/archive/html/help-bash/2016-09/msg00020.html - title: errexit topics: - title: Command substitution do: - | shopt -s inherit_errexit foo() { echo foo ; } bar() { false ; echo bar >&2 ; } output="$( bar )" foo "$output" # If inherit_errexit is unavailable, you can do #output="$( set -e; bar )" dont: - | foo() { echo foo ; } bar() { false ; echo bar >&2 ; } # This will print both "foo" and "bar": foo "$( bar )" # This will also print "foo": foo "$( false )" - | foo() { echo foo ; } bar() { false ; echo bar >&2 ; } # This will still print both "foo" and "bar". output="$( bar )" foo "$output" # This won't print anything. output="$( false )" foo "$output" - title: Process substitution do: - | shopt -s lastpipe result=() cmd | while IFS= read -r line; do result+=("$( process_line "$line" )") done dont: - | # Without lastpipe, the loop is executed is a subshell, # and the array will be empty: result=() cmd | while IFS= read -r line; do result+=("$( process_line "$line" )") done - | # errexit doesn't work for <( cmd ) no matter what: while IFS= read -r line; do process_line "$line" done < <( cmd ) # This will be printed even if cmd fails: echo 'should never see this' - | # This breaks if $output contains the \0 byte: output="$( cmd )" while IFS= read -r line; do process_line "$line" done <<< "$output" - title: Functions do: - | foo() { false ; echo foo >&2 ; } foo echo ok dont: - | foo() { false ; echo foo >&2 ; } # This will print "foo" no matter what. if foo; then echo ok fi # Same below. foo && echo ok foo || echo fail # It currently appears to be completely impossible to # execute a function inside a conditional with errexit # enabled. Therefore, you should try to avoid this # whenever possible. --- {% for feature in page.features %}