aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/_notes/bash.html
blob: cedfb4361004788fe57e7bd340b35ef89baaef8f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
---
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 %}
  <h2>{{ feature.title }}</h2>
  {% for topic in feature.topics %}
    {% if topic.title %}
      <h3>{{ topic.title }}</h3>
    {% endif %}
    <div class="row">
      <div class="col-md-6">
        {% for guide in topic.do %}
          <div class="pre_container pre_do">
            {% highlight bash %}{{ guide }}{% endhighlight %}
            <div class="pre_mark"><span class="glyphicon glyphicon-ok"></span></div>
          </div>
        {% endfor %}
      </div>
      <div class="col-md-6">
        {% for guide in topic.dont %}
          <div class="pre_container pre_dont">
            {% highlight bash %}{{ guide }}{% endhighlight %}
            <div class="pre_mark"><span class="glyphicon glyphicon-remove"></span></div>
          </div>
        {% endfor %}
      </div>
    </div>
  {% endfor %}
{% endfor %}