1 #!/usr/bin/env bash
2 #
3 # Test set flags, sh flags.
4
5 #### $- with -c
6 # dash's behavior seems most sensible here?
7 $SH -o nounset -c 'echo $-'
8 ## stdout: u
9 ## OK bash stdout: huBc
10 ## OK mksh stdout: uhc
11 ## status: 0
12
13 #### $- with pipefail
14 set -o pipefail -o nounset
15 echo $-
16 ## stdout: u
17 ## status: 0
18 ## OK bash stdout: huBs
19 ## OK mksh stdout: ush
20 ## N-I dash stdout-json: ""
21 ## N-I dash status: 2
22
23 #### $- with interactive shell
24 $SH -c 'echo $-' | grep i || echo FALSE
25 $SH -i -c 'echo $-' | grep -q i && echo TRUE
26 ## STDOUT:
27 FALSE
28 TRUE
29 ## END
30 #### pass short options like sh -e
31 $SH -e -c 'false; echo status=$?'
32 ## stdout-json: ""
33 ## status: 1
34
35 #### pass long options like sh -o errexit
36 $SH -o errexit -c 'false; echo status=$?'
37 ## stdout-json: ""
38 ## status: 1
39
40 #### pass shopt options like sh -O nullglob
41 $SH +O nullglob -c 'echo foo *.nonexistent bar'
42 $SH -O nullglob -c 'echo foo *.nonexistent bar'
43 ## STDOUT:
44 foo *.nonexistent bar
45 foo bar
46 ## END
47 ## N-I dash/mksh stdout-json: ""
48 ## N-I dash status: 2
49 ## N-I mksh status: 1
50
51 #### can continue after unknown option
52 # dash and mksh make this a fatal error no matter what.
53 set -o errexit
54 set -o STRICT || true # unknown option
55 echo hello
56 ## stdout: hello
57 ## status: 0
58 ## BUG dash/mksh stdout-json: ""
59 ## BUG dash status: 2
60 ## BUG mksh status: 1
61
62 #### set with both options and argv
63 set -o errexit a b c
64 echo "$@"
65 false
66 echo done
67 ## stdout: a b c
68 ## status: 1
69
70 #### set -o vi/emacs
71 set -o vi
72 echo $?
73 set -o emacs
74 echo $?
75 ## STDOUT:
76 0
77 0
78 ## END
79
80 #### nounset
81 echo "[$unset]"
82 set -o nounset
83 echo "[$unset]"
84 echo end # never reached
85 ## stdout: []
86 ## status: 1
87 ## OK dash status: 2
88
89 #### -u is nounset
90 echo "[$unset]"
91 set -u
92 echo "[$unset]"
93 echo end # never reached
94 ## stdout: []
95 ## status: 1
96 ## OK dash status: 2
97
98 #### nounset with "$@"
99 set a b c
100 set -u # shouldn't touch argv
101 echo "$@"
102 ## stdout: a b c
103
104 #### set -u -- clears argv
105 set a b c
106 set -u -- # shouldn't touch argv
107 echo "$@"
108 ## stdout:
109
110 #### set -u -- x y z
111 set a b c
112 set -u -- x y z
113 echo "$@"
114 ## stdout: x y z
115
116 #### reset option with long flag
117 set -o errexit
118 set +o errexit
119 echo "[$unset]"
120 ## stdout: []
121 ## status: 0
122
123 #### reset option with short flag
124 set -u
125 set +u
126 echo "[$unset]"
127 ## stdout: []
128 ## status: 0
129
130 #### set -eu (flag parsing)
131 set -eu
132 echo "[$unset]"
133 echo status=$?
134 ## stdout-json: ""
135 ## status: 1
136 ## OK dash status: 2
137
138 #### -n for no execution (useful with --ast-output)
139 # NOTE: set +n doesn't work because nothing is executed!
140 echo 1
141 set -n
142 echo 2
143 set +n
144 echo 3
145 # osh doesn't work because it only checks -n in bin/oil.py?
146 ## STDOUT:
147 1
148 ## END
149 ## status: 0
150
151 #### pipefail
152 # NOTE: the sleeps are because osh can fail non-deterministically because of a
153 # bug. Same problem as PIPESTATUS.
154 { sleep 0.01; exit 9; } | { sleep 0.02; exit 2; } | { sleep 0.03; }
155 echo $?
156 set -o pipefail
157 { sleep 0.01; exit 9; } | { sleep 0.02; exit 2; } | { sleep 0.03; }
158 echo $?
159 ## STDOUT:
160 0
161 2
162 ## END
163 ## status: 0
164 ## N-I dash STDOUT:
165 0
166 ## END
167 ## N-I dash status: 2
168
169 #### shopt -p -o prints 'set' options
170 shopt -po nounset
171 set -o nounset
172 shopt -po nounset
173 ## STDOUT:
174 set +o nounset
175 set -o nounset
176 ## END
177 ## N-I dash/mksh stdout-json: ""
178 ## N-I dash/mksh status: 127
179
180 #### shopt -p prints 'shopt' options
181 shopt -p nullglob
182 shopt -s nullglob
183 shopt -p nullglob
184 ## STDOUT:
185 shopt -u nullglob
186 shopt -s nullglob
187 ## END
188 ## N-I dash/mksh stdout-json: ""
189 ## N-I dash/mksh status: 127
190
191 #### shopt with no flags prints options
192 cd $TMP
193
194 # print specific options. OSH does it in a different format.
195 shopt nullglob failglob > one.txt
196 wc -l one.txt
197 grep -o nullglob one.txt
198 grep -o failglob one.txt
199
200 # print all options
201 shopt | grep nullglob | wc -l
202 ## STDOUT:
203 2 one.txt
204 nullglob
205 failglob
206 1
207 ## END
208 ## N-I dash/mksh STDOUT:
209 0 one.txt
210 0
211 ## END
212
213 #### noclobber off
214 set -o errexit
215 echo foo > $TMP/can-clobber
216 set +C
217 echo foo > $TMP/can-clobber
218 set +o noclobber
219 echo foo > $TMP/can-clobber
220 cat $TMP/can-clobber
221 ## stdout: foo
222
223 #### noclobber on
224 # Not implemented yet.
225 rm $TMP/no-clobber
226 set -C
227 echo foo > $TMP/no-clobber
228 echo $?
229 echo foo > $TMP/no-clobber
230 echo $?
231 ## stdout-json: "0\n1\n"
232 ## OK dash stdout-json: "0\n2\n"
233
234 #### SHELLOPTS is updated when options are changed
235 echo $SHELLOPTS | grep -q xtrace
236 echo $?
237 set -x
238 echo $SHELLOPTS | grep -q xtrace
239 echo $?
240 set +x
241 echo $SHELLOPTS | grep -q xtrace
242 echo $?
243 ## stdout-json: "1\n0\n1\n"
244 ## N-I dash/mksh stdout-json: "1\n1\n1\n"
245
246 #### SHELLOPTS is readonly
247 SHELLOPTS=x
248 echo status=$?
249 ## stdout: status=1
250 ## N-I dash/mksh stdout: status=0
251
252 # Setting a readonly variable in osh is a hard failure.
253 ## OK osh status: 1
254 ## OK osh stdout-json: ""
255
256 #### set -o lists options
257 # NOTE: osh doesn't use the same format yet.
258 set -o | grep -o noexec
259 ## STDOUT:
260 noexec
261 ## END
262
263 #### set without args lists variables
264 __GLOBAL=g
265 f() {
266 local __mylocal=L
267 local __OTHERLOCAL=L
268 __GLOBAL=mutated
269 set | grep '^__'
270 }
271 g() {
272 local __var_in_parent_scope=D
273 f
274 }
275 g
276 ## status: 0
277 ## STDOUT:
278 __GLOBAL='mutated'
279 __OTHERLOCAL='L'
280 __mylocal='L'
281 __var_in_parent_scope='D'
282 ## END
283 ## OK bash STDOUT:
284 __GLOBAL=mutated
285 __OTHERLOCAL=L
286 __mylocal=L
287 __var_in_parent_scope=D
288 ## END
289 ## OK mksh STDOUT:
290 __GLOBAL=mutated
291 __var_in_parent_scope=D
292 __OTHERLOCAL=L
293 __mylocal=L
294 ## END
295
296 #### 'set' and 'eval' round trip
297
298 # NOTE: not testing arrays and associative arrays!
299 _space='[ ]'
300 _whitespace=$'[\t\r\n]'
301 _sq="'single quotes'"
302 _backslash_dq="\\ \""
303 _unicode=$'[\u03bc]'
304
305 # Save the variables
306 varfile=$TMP/vars-$(basename $SH).txt
307
308 set | grep '^_' > "$varfile"
309
310 # Unset variables
311 unset _space _whitespace _sq _backslash_dq _unicode
312 echo [ $_space $_whitespace $_sq $_backslash_dq $_unicode ]
313
314 # Restore them
315
316 . $varfile
317 echo "Code saved to $varfile" 1>&2 # for debugging
318
319 test "$_space" = '[ ]' && echo OK
320 test "$_whitespace" = $'[\t\r\n]' && echo OK
321 test "$_sq" = "'single quotes'" && echo OK
322 test "$_backslash_dq" = "\\ \"" && echo OK
323 test "$_unicode" = $'[\u03bc]' && echo OK
324
325 ## STDOUT:
326 [ ]
327 OK
328 OK
329 OK
330 OK
331 OK
332 ## END
333
334 #### set without args and array variables (not in OSH)
335 declare -a __array
336 __array=(1 2 '3 4')
337 set | grep '^__'
338 ## STDOUT:
339 __array=([0]="1" [1]="2" [2]="3 4")
340 ## END
341 ## OK mksh STDOUT:
342 __array[0]=1
343 __array[1]=2
344 __array[2]='3 4'
345 ## N-I dash stdout-json: ""
346 ## N-I dash status: 2
347 ## N-I osh stdout-json: ""
348 ## N-I osh status: 1
349
350 #### set without args and assoc array variables (not in OSH)
351 typeset -A __assoc
352 __assoc['k e y']='v a l'
353 __assoc[a]=b
354 set | grep '^__'
355 ## STDOUT:
356 __assoc=(["k e y"]="v a l" [a]="b" )
357 ## END
358 ## N-I mksh stdout-json: ""
359 ## N-I mksh status: 1
360 ## N-I dash stdout-json: ""
361 ## N-I dash status: 1
362 ## N-I osh stdout-json: ""
363 ## N-I osh status: 1
364
365 #### shopt -q
366 shopt -q nullglob
367 echo nullglob=$?
368
369 # set it
370 shopt -s nullglob
371
372 shopt -q nullglob
373 echo nullglob=$?
374
375 shopt -q nullglob failglob
376 echo nullglob,failglob=$?
377
378 # set it
379 shopt -s failglob
380 shopt -q nullglob failglob
381 echo nullglob,failglob=$?
382
383 ## STDOUT:
384 nullglob=1
385 nullglob=0
386 nullglob,failglob=1
387 nullglob,failglob=0
388 ## END
389 ## N-I dash/mksh STDOUT:
390 nullglob=127
391 nullglob=127
392 nullglob,failglob=127
393 nullglob,failglob=127
394 ## END
395
396 #### shopt -q invalid
397 shopt -q invalidZZ
398 echo invalidZZ=$?
399 ## STDOUT:
400 invalidZZ=2
401 ## END
402 ## OK bash STDOUT:
403 invalidZZ=1
404 ## END
405 ## N-I dash/mksh STDOUT:
406 invalidZZ=127
407 ## END
408
409 #### shopt -s strict:all
410 n=2
411
412 show-strict() {
413 shopt -p | grep 'strict_' | head -n $n
414 echo -
415 }
416
417 show-strict
418 shopt -s strict:all
419 show-strict
420 shopt -u strict_arith
421 show-strict
422 ## STDOUT:
423 shopt -u strict_argv
424 shopt -u strict_arith
425 -
426 shopt -s strict_argv
427 shopt -s strict_arith
428 -
429 shopt -s strict_argv
430 shopt -u strict_arith
431 -
432 ## END
433 ## N-I dash status: 2
434 ## N-I dash stdout-json: ""
435 ## N-I bash/mksh STDOUT:
436 -
437 -
438 -
439 ## END
440
441 #### shopt allows for backward compatibility like bash
442
443 # doesn't have to be on, but just for testing
444 set -o errexit
445
446 shopt -p nullglob || true # bash returns 1 here? Like -q.
447
448 # This should set nullglob, and return 1, which can be ignored
449 shopt -s nullglob strict_OPTION_NOT_YET_IMPLEMENTED 2>/dev/null || true
450 echo status=$?
451
452 shopt -p nullglob || true
453
454 ## STDOUT:
455 shopt -u nullglob
456 status=0
457 shopt -s nullglob
458 ## END
459 ## N-I dash/mksh STDOUT:
460 status=0
461 ## END
462 ## N-I dash/mksh status: 0
463
464 #### shopt -p validates option names
465 shopt -p nullglob invalid failglob
466 echo status=$?
467 # same thing as -p, slightly different format in bash
468 shopt nullglob invalid failglob > $TMP/out.txt
469 status=$?
470 sed --regexp-extended 's/\s+/ /' $TMP/out.txt # make it easier to assert
471 echo status=$status
472 ## STDOUT:
473 shopt -u nullglob
474 status=2
475 shopt -u nullglob
476 status=2
477 ## END
478 ## OK bash STDOUT:
479 shopt -u nullglob
480 shopt -u failglob
481 status=1
482 nullglob off
483 failglob off
484 status=1
485 ## END
486 ## N-I dash/mksh STDOUT:
487 status=127
488 status=127
489 ## END
490
491 #### shopt -p -o validates option names
492 shopt -p -o errexit invalid nounset
493 echo status=$?
494 ## STDOUT:
495 set +o errexit
496 status=2
497 ## END
498 ## OK bash STDOUT:
499 set +o errexit
500 set +o nounset
501 status=1
502 ## END
503 ## N-I dash/mksh STDOUT:
504 status=127
505 ## END