1 | ---
|
2 | default_highlighter: oils-sh
|
3 | ---
|
4 |
|
5 | Shell Language Idioms
|
6 | =====================
|
7 |
|
8 | These are like the [YSH vs. Shell Idioms](idioms.html), but the advice also
|
9 | applies to other Unix shells.
|
10 |
|
11 | <div id="toc">
|
12 | </div>
|
13 |
|
14 | ## Style
|
15 |
|
16 | ### Use Only `"$@"`
|
17 |
|
18 | There's no reason to use anything but `"$@"`. All the other forms like `$*`
|
19 | can be disallowed, because if you want to join to a string, you can write:
|
20 |
|
21 | joined_str="$@"
|
22 |
|
23 | The same advice applies to arrays. You can always use `"${myarray[@]}"`; you
|
24 | never need to use `${myarray[*]}` or any other form.
|
25 |
|
26 | Related: [Thirteen Incorrect Ways and Two Awkward Ways to Use
|
27 | Arrays](http://www.oilshell.org/blog/2016/11/06.html)
|
28 |
|
29 | ### Prefer `test` to `[`
|
30 |
|
31 | Idiomatic OSH code doesn't use "puns".
|
32 |
|
33 | No:
|
34 |
|
35 | [ -d /tmp ]
|
36 |
|
37 | Yes:
|
38 |
|
39 | test -d /tmp
|
40 |
|
41 | The [simple_test_builtin](ref/chap-option.html#ysh:all) option enforces this.
|
42 |
|
43 | ## Use Statically Parsed Language Constructs
|
44 |
|
45 | Static parsing is one of the [syntactic concepts](syntactic-concepts.html). It
|
46 | leads to better error messages, earlier error messages, and lets tools
|
47 | understand your code.
|
48 |
|
49 | ### `test` Should Only Have 2 or 3 Arguments
|
50 |
|
51 | In POSIX, the `test` builtin has a lot of unnecessary flexibility, which leads
|
52 | to bugs.
|
53 |
|
54 | See [Problems With the test Builtin: What Does -a
|
55 | Mean?](//www.oilshell.org/blog/2017/08/31.html)
|
56 |
|
57 | No:
|
58 |
|
59 | test ! -d /tmp
|
60 | test -d /tmp -a -d /tmp/foo
|
61 |
|
62 | Yes:
|
63 |
|
64 | ! test -d /tmp
|
65 | test -d /tmp && test -d /tmp/foo
|
66 |
|
67 | The [simple_test_builtin](ref/chap-option.html#ysh:all) option enforces that
|
68 | `test` receives 3 or fewer arguments.
|
69 |
|
70 | ### Prefer Shell Functions to Aliases
|
71 |
|
72 | Functions subsume all the common uses of alias, and can be parsed statically.
|
73 |
|
74 | No:
|
75 |
|
76 | alias ll='ls -l'
|
77 |
|
78 | Yes:
|
79 |
|
80 | ll() { # Shell Style
|
81 | ls -l "$@"
|
82 | }
|
83 |
|
84 | proc ll { # YSH Style
|
85 | ls -l @ARGV
|
86 | }
|
87 |
|
88 | If you're wrapping an external command with a function of the same, use the
|
89 | [command](ref/chap-builtin-cmd.html#command) builtin:
|
90 |
|
91 | proc ls {
|
92 | command ls --color @ARGV
|
93 | }
|
94 |
|
95 | ### Prefer `$'\n'` to `echo -e`
|
96 |
|
97 | No:
|
98 |
|
99 | echo -e '\n' # arg to -e is dynamically parsed
|
100 |
|
101 | Yes:
|
102 |
|
103 | echo $'\n' # statically parsed
|
104 |
|
105 | ## How to Fix Code That `strict_errexit` Disallows
|
106 |
|
107 | The `strict_errexit` feature warns you when you would **lose errors** in shell
|
108 | code.
|
109 |
|
110 | ### The `local d=$(date %x)` Pitfall
|
111 |
|
112 | No:
|
113 |
|
114 | local d=$(date %x) # ignores failure
|
115 |
|
116 | Yes:
|
117 |
|
118 | local d
|
119 | d=$(date %x) # fails properly
|
120 |
|
121 | Better YSH style:
|
122 |
|
123 | var d = $(date %x) # fails properly
|
124 |
|
125 | ### Variations With `readonly` and `export`
|
126 |
|
127 | In these cases, the builtin comes after the assignment.
|
128 |
|
129 | No:
|
130 |
|
131 | readonly d1=$(date %x)
|
132 | export d2=$(date %x)
|
133 |
|
134 | Yes:
|
135 |
|
136 | d1=$(date %x)
|
137 | readonly d1
|
138 |
|
139 | d2=$(date %x)
|
140 | export d2
|
141 |
|
142 |
|
143 | ### The `if myfunc` Pitfall
|
144 |
|
145 | No:
|
146 |
|
147 | if myfunc; then
|
148 | echo 'Success'
|
149 | fi
|
150 |
|
151 | Shell workaround when the *$0 Dispatch Pattern* is used:
|
152 |
|
153 | myfunc() {
|
154 | echo hi
|
155 | }
|
156 |
|
157 | mycaller() {
|
158 | if $0 myfunc; then # $0 starts this script as a new process
|
159 | echo 'Success'
|
160 | fi
|
161 | }
|
162 |
|
163 | "$@" # invoked like myscript.sh mycaller arg1 arg2 ...
|
164 |
|
165 |
|
166 | Better YSH Style:
|
167 |
|
168 | try {
|
169 | myfunc
|
170 | }
|
171 | if (_error.code === 0)
|
172 | echo 'Success'
|
173 | }
|
174 |
|
175 |
|
176 | ## Remove Dynamic Parsing
|
177 |
|
178 | ### Replacing `declare -i`, `local -i`, ...
|
179 |
|
180 | The `-i` flag on assignment builtins doesn't add any functionality to bash —
|
181 | it's merely a different and confusing style.
|
182 |
|
183 | OSH doesn't support it; instead it has *true integers*.
|
184 |
|
185 | For example, don't rely on "punning" of the `+=` operator; use `$(( ))`
|
186 | instead.
|
187 |
|
188 | No:
|
189 |
|
190 | declare -i x=3
|
191 | x+=1 # Now it's '4' because += will do integer arithmetic
|
192 |
|
193 | Yes (shell style):
|
194 |
|
195 | x=3
|
196 | x=$(( x + 1 )) # No -i flag needed
|
197 |
|
198 | Yes (YSH style):
|
199 |
|
200 | var x = 3
|
201 | setvar x += 1
|
202 |
|
203 | Likewise, don't rely on dynamic parsing of arithmetic.
|
204 |
|
205 | No:
|
206 |
|
207 | declare -i x
|
208 | x='1 + 2' # Now it's the string '3'
|
209 |
|
210 | Yes (shell style):
|
211 |
|
212 | x=$(( 1 + 2 ))
|
213 |
|
214 | Yes (YSH style):
|
215 |
|
216 | var x = 1 + 2
|
217 |
|
218 |
|