Bash · 35942 bytes Raw Blame History
1 #!/bin/sh
2 # =====================================
3 # POSIX Compliance Shell Options and Read Builtin Test Suite for shell
4 # =====================================
5 # Tests set options and read builtin per IEEE Std 1003.1-2017
6 # Section: Shell Command Language - set, read
7
8 # Colors (POSIX-compliant way)
9 RED='\033[0;31m'
10 GREEN='\033[0;32m'
11 YELLOW='\033[1;33m'
12 BLUE='\033[0;34m'
13 NC='\033[0m'
14
15 # Test identification
16 TEST_PREFIX="[posix-options]"
17 CURRENT_SECTION=""
18 TEST_NUM=0
19
20 PASSED=0
21 FAILED=0
22 SKIPPED=0
23 FAILED_TESTS_LIST=""
24
25 # Get script directory (POSIX way)
26 SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
27 SHELL_BIN="${SHELL_BIN:?ERROR: SHELL_BIN must be set}"
28 BASH_REF="${BASH_REF:-bash}"
29
30 # Check if shell binary exists
31 if [ ! -x "$SHELL_BIN" ]; then
32 printf "${RED}ERROR${NC}: shell binary not found at $SHELL_BIN\n"
33 printf "Please set SHELL_BIN or set SHELL_BIN environment variable\n"
34 exit 1
35 fi
36
37 # Test result trackers
38 pass() {
39 TEST_NUM=$((TEST_NUM + 1))
40 printf "${GREEN}✓ PASS${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s\n" "$1"
41 PASSED=$((PASSED + 1))
42 }
43
44 fail() {
45 TEST_NUM=$((TEST_NUM + 1))
46 TEST_ID="${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}"
47 printf "${RED}✗ FAIL${NC} ${TEST_ID}: %s\n" "$1"
48 FAILED_TESTS_LIST="${FAILED_TESTS_LIST} ${TEST_ID}: $1\n"
49 if [ -n "$2" ]; then
50 printf " expected: %s\n" "$2"
51 fi
52 if [ -n "$3" ]; then
53 printf " got: %s\n" "$3"
54 fi
55 FAILED=$((FAILED + 1))
56 }
57
58 skip() {
59 TEST_NUM=$((TEST_NUM + 1))
60 printf "${YELLOW}⊘ SKIP${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s - %s\n" "$1" "$2"
61 SKIPPED=$((SKIPPED + 1))
62 }
63
64 section() {
65 CURRENT_SECTION=$(echo "$1" | grep -oE '^[0-9]+' || echo "0")
66 TEST_NUM=0
67 printf "\n"
68 printf "${BLUE}==========================================\n"
69 printf "%s\n" "$1"
70 printf "==========================================${NC}\n"
71 }
72
73 # =====================================
74 section "381. SET -e (ERREXIT)"
75 # =====================================
76
77 result=$("$SHELL_BIN" -c 'set -e; true; echo reached' 2>&1)
78 if [ "$result" = "reached" ]; then
79 pass "set -e allows true command"
80 else
81 fail "set -e allows true command" "reached" "$result"
82 fi
83
84 result=$("$SHELL_BIN" -c 'set -e; false; echo "should not reach"' 2>&1)
85 if [ -z "$result" ] || ! echo "$result" | grep -q "should not reach"; then
86 pass "set -e exits on false"
87 else
88 fail "set -e exits on false" "(no output)" "$result"
89 fi
90
91 result=$("$SHELL_BIN" -c 'set -e; if false; then echo no; fi; echo reached' 2>&1)
92 if [ "$result" = "reached" ]; then
93 pass "set -e ignores false in if condition"
94 else
95 fail "set -e ignores false in if condition" "reached" "$result"
96 fi
97
98 result=$("$SHELL_BIN" -c 'set -e; false || echo fallback' 2>&1)
99 if [ "$result" = "fallback" ]; then
100 pass "set -e ignores false with || continuation"
101 else
102 fail "set -e ignores false with || continuation" "fallback" "$result"
103 fi
104
105 result=$("$SHELL_BIN" -c 'set -e; ! false; echo reached' 2>&1)
106 if [ "$result" = "reached" ]; then
107 pass "set -e ignores negated false"
108 else
109 fail "set -e ignores negated false" "reached" "$result"
110 fi
111
112 # =====================================
113 section "382. SET -u (NOUNSET)"
114 # =====================================
115
116 result=$("$SHELL_BIN" -c 'set -u; x=hello; echo $x' 2>&1)
117 if [ "$result" = "hello" ]; then
118 pass "set -u allows set variable"
119 else
120 fail "set -u allows set variable" "hello" "$result"
121 fi
122
123 result=$("$SHELL_BIN" -c 'set -u; echo $UNSET_VAR_12345' 2>&1)
124 exit_code=$?
125 if echo "$result" | grep -qi "unbound\|unset\|error" || [ $exit_code -ne 0 ]; then
126 pass "set -u errors on unset variable"
127 else
128 fail "set -u errors on unset variable" "error message" "$result"
129 fi
130
131 result=$("$SHELL_BIN" -c 'set -u; echo ${UNSET_VAR_12345:-default}' 2>&1)
132 if [ "$result" = "default" ]; then
133 pass "set -u allows default expansion"
134 else
135 fail "set -u allows default expansion" "default" "$result"
136 fi
137
138 # =====================================
139 section "383. SET -f (NOGLOB)"
140 # =====================================
141
142 result=$("$SHELL_BIN" -c 'set -f; echo *' 2>&1)
143 if [ "$result" = "*" ]; then
144 pass "set -f disables globbing"
145 else
146 fail "set -f disables globbing" "*" "$result"
147 fi
148
149 result=$("$SHELL_BIN" -c 'set -f; echo [a-z]*' 2>&1)
150 if [ "$result" = "[a-z]*" ]; then
151 pass "set -f disables bracket patterns"
152 else
153 fail "set -f disables bracket patterns" "[a-z]*" "$result"
154 fi
155
156 result=$("$SHELL_BIN" -c 'set -f; set +f; cd /tmp && echo [a-z]* | head -c 20' 2>&1)
157 if [ "$result" != "[a-z]*" ] && [ -n "$result" ]; then
158 pass "set +f re-enables globbing"
159 else
160 fail "set +f re-enables globbing" "(expanded)" "$result"
161 fi
162
163 # =====================================
164 section "384. SET -n (NOEXEC)"
165 # =====================================
166
167 result=$("$SHELL_BIN" -c 'set -n; echo hello' 2>&1)
168 if [ -z "$result" ]; then
169 pass "set -n prevents execution"
170 else
171 fail "set -n prevents execution" "(empty)" "$result"
172 fi
173
174 # =====================================
175 section "385. SET -x (XTRACE)"
176 # =====================================
177
178 result=$("$SHELL_BIN" -c 'set -x; echo hello' 2>&1)
179 if echo "$result" | grep -q "echo hello\|+ echo"; then
180 pass "set -x shows trace output"
181 else
182 fail "set -x shows trace output" "trace line" "$result"
183 fi
184
185 result=$("$SHELL_BIN" -c 'set -x; x=5; echo $x' 2>&1)
186 if echo "$result" | grep -qE "x=5|echo 5|\+ echo"; then
187 pass "set -x traces variable assignment"
188 else
189 fail "set -x traces variable assignment" "trace lines" "$result"
190 fi
191
192 # =====================================
193 section "386. SET -v (VERBOSE)"
194 # =====================================
195
196 # Note: set -v only echoes lines as they're READ, not from -c argument
197 # Test that -v option is accepted and runs without error
198 "$SHELL_BIN" -c 'set -v; echo hello' >/dev/null 2>&1
199 shell_exit=$?
200 "$BASH_REF" -c 'set -v; echo hello' >/dev/null 2>&1
201 bash_exit=$?
202 if [ "$shell_exit" -eq "$bash_exit" ]; then
203 pass "set -v runs without error"
204 else
205 fail "set -v runs without error" "exit=$bash_exit" "exit=$shell_exit"
206 fi
207
208 # =====================================
209 section "387. SET -C (NOCLOBBER)"
210 # =====================================
211
212 TEST_DIR="/tmp/bensch_options_$$"
213 mkdir -p "$TEST_DIR"
214
215 result=$("$SHELL_BIN" -c 'set -C; echo test > '"$TEST_DIR"'/clobber1; echo test2 > '"$TEST_DIR"'/clobber1' 2>&1)
216 if echo "$result" | grep -qi "exist\|cannot\|error\|clobber"; then
217 pass "set -C prevents clobbering existing file"
218 else
219 # Check if file still has original content
220 if [ -f "$TEST_DIR/clobber1" ]; then
221 content=$(cat "$TEST_DIR/clobber1")
222 if [ "$content" = "test" ]; then
223 pass "set -C prevents clobbering existing file"
224 else
225 fail "set -C prevents clobbering existing file" "error or original content" "$result"
226 fi
227 else
228 fail "set -C prevents clobbering existing file" "error message" "$result"
229 fi
230 fi
231
232 rm -rf "$TEST_DIR"
233
234 # =====================================
235 section "388. SET -o OPTIONS"
236 # =====================================
237
238 result=$("$SHELL_BIN" -c 'set -o errexit; true; echo ok' 2>&1)
239 if [ "$result" = "ok" ]; then
240 pass "set -o errexit (same as -e)"
241 else
242 fail "set -o errexit (same as -e)" "ok" "$result"
243 fi
244
245 result=$("$SHELL_BIN" -c 'set -o noglob; echo *' 2>&1)
246 if [ "$result" = "*" ]; then
247 pass "set -o noglob (same as -f)"
248 else
249 fail "set -o noglob (same as -f)" "*" "$result"
250 fi
251
252 result=$("$SHELL_BIN" -c 'set -o nounset; x=val; echo $x' 2>&1)
253 if [ "$result" = "val" ]; then
254 pass "set -o nounset (same as -u)"
255 else
256 fail "set -o nounset (same as -u)" "val" "$result"
257 fi
258
259 # =====================================
260 section "389. SET -- POSITIONAL PARAMETERS"
261 # =====================================
262
263 result=$("$SHELL_BIN" -c 'set -- a b c; echo $1 $2 $3' 2>&1)
264 if [ "$result" = "a b c" ]; then
265 pass "set -- sets positional parameters"
266 else
267 fail "set -- sets positional parameters" "a b c" "$result"
268 fi
269
270 result=$("$SHELL_BIN" -c 'set -- a b c d e; echo $#' 2>&1)
271 if [ "$result" = "5" ]; then
272 pass "set -- updates \$#"
273 else
274 fail "set -- updates \$#" "5" "$result"
275 fi
276
277 result=$("$SHELL_BIN" -c 'set -- a b c; set --; echo $#' 2>&1)
278 if [ "$result" = "0" ]; then
279 pass "set -- clears positional parameters"
280 else
281 fail "set -- clears positional parameters" "0" "$result"
282 fi
283
284 result=$("$SHELL_BIN" -c 'set -- "a b" c; echo $1' 2>&1)
285 if [ "$result" = "a b" ]; then
286 pass "set -- preserves quoted args"
287 else
288 fail "set -- preserves quoted args" "a b" "$result"
289 fi
290
291 # =====================================
292 section "390. READ BUILTIN BASIC"
293 # =====================================
294
295 result=$("$SHELL_BIN" -c 'echo "hello" | { read x; echo $x; }' 2>&1)
296 if [ "$result" = "hello" ]; then
297 pass "read basic input"
298 else
299 fail "read basic input" "hello" "$result"
300 fi
301
302 result=$("$SHELL_BIN" -c 'echo "one two three" | { read a b c; echo "$a|$b|$c"; }' 2>&1)
303 if [ "$result" = "one|two|three" ]; then
304 pass "read multiple variables"
305 else
306 fail "read multiple variables" "one|two|three" "$result"
307 fi
308
309 result=$("$SHELL_BIN" -c 'echo "one two three four" | { read a b; echo "$a|$b"; }' 2>&1)
310 if [ "$result" = "one|two three four" ]; then
311 pass "read remaining into last variable"
312 else
313 fail "read remaining into last variable" "one|two three four" "$result"
314 fi
315
316 # =====================================
317 section "391. READ WITH IFS"
318 # =====================================
319
320 result=$("$SHELL_BIN" -c 'echo "a:b:c" | { IFS=: read x y z; echo "$x|$y|$z"; }' 2>&1)
321 if [ "$result" = "a|b|c" ]; then
322 pass "read with custom IFS"
323 else
324 fail "read with custom IFS" "a|b|c" "$result"
325 fi
326
327 result=$("$SHELL_BIN" -c 'echo "a::c" | { IFS=: read x y z; echo "$x|$y|$z"; }' 2>&1)
328 if [ "$result" = "a||c" ]; then
329 pass "read with empty field"
330 else
331 fail "read with empty field" "a||c" "$result"
332 fi
333
334 result=$("$SHELL_BIN" -c 'echo " a b " | { read x y; echo ">$x|$y<"; }' 2>&1)
335 if [ "$result" = ">a|b<" ]; then
336 pass "read strips leading/trailing whitespace"
337 else
338 fail "read strips leading/trailing whitespace" ">a|b<" "$result"
339 fi
340
341 # =====================================
342 section "392. READ -r (RAW MODE)"
343 # =====================================
344
345 result=$("$SHELL_BIN" -c 'echo "hello\\nworld" | { read -r x; echo "$x"; }' 2>&1)
346 if [ "$result" = 'hello\nworld' ]; then
347 pass "read -r preserves backslashes"
348 else
349 fail "read -r preserves backslashes" 'hello\nworld' "$result"
350 fi
351
352 # POSIX: read without -r joins lines ending with backslash (line continuation)
353 # printf with single quotes produces: line<backslash><newline>continued<newline>
354 # read joins: line + continued = linecontinued
355 result=$("$SHELL_BIN" -c 'printf '"'"'line\\\ncontinued\n'"'"' | { read x; echo "$x"; }' 2>&1)
356 if [ "$result" = "linecontinued" ]; then
357 pass "read without -r joins continued lines"
358 else
359 fail "read without -r joins continued lines" "linecontinued" "$result"
360 fi
361
362 # =====================================
363 section "393. READ EXIT STATUS"
364 # =====================================
365
366 result=$("$SHELL_BIN" -c 'echo "test" | { read x; echo $?; }' 2>&1)
367 if [ "$result" = "0" ]; then
368 pass "read returns 0 on success"
369 else
370 fail "read returns 0 on success" "0" "$result"
371 fi
372
373 result=$("$SHELL_BIN" -c 'echo -n "" | { read x; echo $?; }' 2>&1)
374 if [ "$result" = "1" ]; then
375 pass "read returns non-zero on EOF"
376 else
377 fail "read returns non-zero on EOF" "1" "$result"
378 fi
379
380 # =====================================
381 section "394. SHIFT BUILTIN"
382 # =====================================
383
384 result=$("$SHELL_BIN" -c 'set -- a b c d; shift; echo $1 $2 $3' 2>&1)
385 if [ "$result" = "b c d" ]; then
386 pass "shift removes first parameter"
387 else
388 fail "shift removes first parameter" "b c d" "$result"
389 fi
390
391 result=$("$SHELL_BIN" -c 'set -- a b c d e; shift 2; echo $1 $2 $3' 2>&1)
392 if [ "$result" = "c d e" ]; then
393 pass "shift n removes n parameters"
394 else
395 fail "shift n removes n parameters" "c d e" "$result"
396 fi
397
398 result=$("$SHELL_BIN" -c 'set -- a b c; shift; echo $#' 2>&1)
399 if [ "$result" = "2" ]; then
400 pass "shift updates \$#"
401 else
402 fail "shift updates \$#" "2" "$result"
403 fi
404
405 result=$("$SHELL_BIN" -c 'set -- a; shift 5 2>/dev/null; echo $?' 2>&1)
406 if [ "$result" = "1" ]; then
407 pass "shift beyond count returns error"
408 else
409 fail "shift beyond count returns error" "1" "$result"
410 fi
411
412 # =====================================
413 section "395. SPECIAL PARAMETERS"
414 # =====================================
415
416 result=$("$SHELL_BIN" -c 'set -- a b c; echo "$@"' 2>&1)
417 if [ "$result" = "a b c" ]; then
418 pass "\$@ expands all positional parameters"
419 else
420 fail "\$@ expands all positional parameters" "a b c" "$result"
421 fi
422
423 result=$("$SHELL_BIN" -c 'set -- a b c; echo "$*"' 2>&1)
424 if [ "$result" = "a b c" ]; then
425 pass "\$* expands all positional parameters"
426 else
427 fail "\$* expands all positional parameters" "a b c" "$result"
428 fi
429
430 result=$("$SHELL_BIN" -c 'set -- a b c; for x in "$@"; do echo "[$x]"; done' 2>&1)
431 expected=$(printf "[a]\n[b]\n[c]")
432 if [ "$result" = "$expected" ]; then
433 pass "\"\$@\" preserves separate arguments"
434 else
435 fail "\"\$@\" preserves separate arguments" "$expected" "$result"
436 fi
437
438 result=$("$SHELL_BIN" -c 'set -- "a b" c; for x in "$@"; do echo "[$x]"; done' 2>&1)
439 expected=$(printf "[a b]\n[c]")
440 if [ "$result" = "$expected" ]; then
441 pass "\"\$@\" preserves quoted arguments"
442 else
443 fail "\"\$@\" preserves quoted arguments" "$expected" "$result"
444 fi
445
446 result=$("$SHELL_BIN" -c 'echo $$' 2>&1)
447 if echo "$result" | grep -qE '^[0-9]+$' && [ "$result" -gt 0 ]; then
448 pass "\$\$ returns PID"
449 else
450 fail "\$\$ returns PID" "numeric PID" "$result"
451 fi
452
453 result=$("$SHELL_BIN" -c 'echo $?' 2>&1)
454 if [ "$result" = "0" ]; then
455 pass "\$? returns exit status"
456 else
457 fail "\$? returns exit status" "0" "$result"
458 fi
459
460 # =====================================
461 section "396. IFS WORD SPLITTING"
462 # =====================================
463
464 result=$("$SHELL_BIN" -c 'x="a:b:c"; IFS=:; for w in $x; do echo "[$w]"; done' 2>&1)
465 expected=$(printf "[a]\n[b]\n[c]")
466 if [ "$result" = "$expected" ]; then
467 pass "IFS changes word splitting"
468 else
469 fail "IFS changes word splitting" "$expected" "$result"
470 fi
471
472 result=$("$SHELL_BIN" -c 'x="a::c"; IFS=:; set -- $x; echo $#' 2>&1)
473 if [ "$result" = "3" ]; then
474 pass "IFS empty fields create arguments"
475 else
476 fail "IFS empty fields create arguments" "3" "$result"
477 fi
478
479 result=$("$SHELL_BIN" -c 'IFS=:; x="a:b:c"; echo $x' 2>&1)
480 if [ "$result" = "a b c" ]; then
481 pass "IFS affects echo output"
482 else
483 fail "IFS affects echo output" "a b c" "$result"
484 fi
485
486 result=$("$SHELL_BIN" -c 'IFS=""; x="a b c"; for w in $x; do echo "[$w]"; done' 2>&1)
487 if [ "$result" = "[a b c]" ]; then
488 pass "Empty IFS prevents splitting"
489 else
490 fail "Empty IFS prevents splitting" "[a b c]" "$result"
491 fi
492
493 result=$("$SHELL_BIN" -c 'unset IFS; x="a b"; echo $x' 2>&1)
494 if [ "$result" = "a b" ]; then
495 pass "unset IFS uses default"
496 else
497 fail "unset IFS uses default" "a b" "$result"
498 fi
499
500 result=$("$SHELL_BIN" -c 'IFS=",;"; x="a,b;c"; set -- $x; echo $1 $2 $3' 2>&1)
501 if [ "$result" = "a b c" ]; then
502 pass "IFS with multiple characters"
503 else
504 fail "IFS with multiple characters" "a b c" "$result"
505 fi
506
507 # =====================================
508 section "397. ALIAS BUILTIN"
509 # =====================================
510
511 result=$("$SHELL_BIN" -c 'alias ll="ls -la"; alias ll' 2>&1)
512 if echo "$result" | grep -q "ls -la\|ll="; then
513 pass "alias defines and shows alias"
514 else
515 fail "alias defines and shows alias" "ls -la" "$result"
516 fi
517
518 # Note: Aliases defined in -c mode aren't expanded in the same command line
519 # This matches bash behavior - aliases are expanded at parse time
520 result=$("$SHELL_BIN" -c 'alias greeting="echo hello"; alias greeting' 2>&1)
521 if echo "$result" | grep -q "echo hello"; then
522 pass "alias defines correctly (matches bash)"
523 else
524 fail "alias defines correctly (matches bash)" "echo hello" "$result"
525 fi
526
527 result=$("$SHELL_BIN" -c 'alias x="echo a"; alias y="echo b"; alias | wc -l' 2>&1)
528 if [ "$result" -ge 2 ] 2>/dev/null; then
529 pass "alias lists all aliases"
530 else
531 fail "alias lists all aliases" "at least 2" "$result"
532 fi
533
534 result=$("$SHELL_BIN" -c 'alias greet="echo hi"; unalias greet; greet 2>/dev/null; echo $?' 2>&1)
535 if echo "$result" | grep -qE "127|1"; then
536 pass "unalias removes alias"
537 else
538 fail "unalias removes alias" "non-zero exit" "$result"
539 fi
540
541 # Note: Test redefinition by checking alias output (execution won't work in same line)
542 result=$("$SHELL_BIN" -c 'alias foo="echo one"; alias foo="echo two"; alias foo' 2>&1)
543 if echo "$result" | grep -q "echo two"; then
544 pass "alias can be redefined"
545 else
546 fail "alias can be redefined" "echo two" "$result"
547 fi
548
549 # =====================================
550 section "398. EXPORT BUILTIN"
551 # =====================================
552
553 result=$("$SHELL_BIN" -c 'export TESTVAR=hello; echo $TESTVAR' 2>&1)
554 if [ "$result" = "hello" ]; then
555 pass "export sets and exports variable"
556 else
557 fail "export sets and exports variable" "hello" "$result"
558 fi
559
560 result=$("$SHELL_BIN" -c 'MYVAR=world; export MYVAR; echo $MYVAR' 2>&1)
561 if [ "$result" = "world" ]; then
562 pass "export existing variable"
563 else
564 fail "export existing variable" "world" "$result"
565 fi
566
567 result=$("$SHELL_BIN" -c 'export X=1 Y=2 Z=3; echo $X $Y $Z' 2>&1)
568 if [ "$result" = "1 2 3" ]; then
569 pass "export multiple variables"
570 else
571 fail "export multiple variables" "1 2 3" "$result"
572 fi
573
574 result=$("$SHELL_BIN" -c 'export SUBTEST=value; sh -c "echo \$SUBTEST"' 2>&1)
575 if [ "$result" = "value" ]; then
576 pass "exported var visible in subshell"
577 else
578 fail "exported var visible in subshell" "value" "$result"
579 fi
580
581 # =====================================
582 section "399. READONLY BUILTIN"
583 # =====================================
584
585 result=$("$SHELL_BIN" -c 'readonly CONST=42; echo $CONST' 2>&1)
586 if [ "$result" = "42" ]; then
587 pass "readonly sets variable"
588 else
589 fail "readonly sets variable" "42" "$result"
590 fi
591
592 result=$("$SHELL_BIN" -c 'readonly RO=1; RO=2 2>&1; echo $RO' 2>&1)
593 if echo "$result" | grep -q "1"; then
594 pass "readonly prevents modification"
595 else
596 fail "readonly prevents modification" "1" "$result"
597 fi
598
599 result=$("$SHELL_BIN" -c 'readonly A=1 B=2; echo $A $B' 2>&1)
600 if [ "$result" = "1 2" ]; then
601 pass "readonly multiple variables"
602 else
603 fail "readonly multiple variables" "1 2" "$result"
604 fi
605
606 # =====================================
607 section "400. UNSET BUILTIN"
608 # =====================================
609
610 result=$("$SHELL_BIN" -c 'x=hello; unset x; echo "${x:-unset}"' 2>&1)
611 if [ "$result" = "unset" ]; then
612 pass "unset removes variable"
613 else
614 fail "unset removes variable" "unset" "$result"
615 fi
616
617 result=$("$SHELL_BIN" -c 'x=1; y=2; unset x y; echo "${x:-a} ${y:-b}"' 2>&1)
618 if [ "$result" = "a b" ]; then
619 pass "unset multiple variables"
620 else
621 fail "unset multiple variables" "a b" "$result"
622 fi
623
624 result=$("$SHELL_BIN" -c 'f() { echo hi; }; unset -f f; type f 2>/dev/null; echo $?' 2>&1)
625 if echo "$result" | grep -qE "1|127"; then
626 pass "unset -f removes function"
627 else
628 fail "unset -f removes function" "non-zero exit" "$result"
629 fi
630
631 # =====================================
632 section "401. GETOPTS BUILTIN"
633 # =====================================
634
635 # Basic option parsing
636 result=$("$SHELL_BIN" -c 'set -- -a; getopts "ab" opt; echo $opt' 2>&1)
637 if [ "$result" = "a" ]; then
638 pass "getopts parses simple option"
639 else
640 fail "getopts parses simple option" "a" "$result"
641 fi
642
643 # Option with argument
644 result=$("$SHELL_BIN" -c 'set -- -a value; getopts "a:" opt; echo "$OPTARG"' 2>&1)
645 if [ "$result" = "value" ]; then
646 pass "getopts sets OPTARG"
647 else
648 fail "getopts sets OPTARG" "value" "$result"
649 fi
650
651 # OPTIND increment
652 result=$("$SHELL_BIN" -c 'set -- -a -b; getopts "ab" opt; getopts "ab" opt; echo $OPTIND' 2>&1)
653 if [ "$result" = "3" ]; then
654 pass "getopts increments OPTIND"
655 else
656 fail "getopts increments OPTIND" "3" "$result"
657 fi
658
659 # Multiple options in loop
660 result=$("$SHELL_BIN" -c 'set -- -a -b -c; while getopts "abc" opt; do echo -n $opt; done' 2>&1)
661 if [ "$result" = "abc" ]; then
662 pass "getopts in while loop"
663 else
664 fail "getopts in while loop" "abc" "$result"
665 fi
666
667 # Invalid option handling
668 result=$("$SHELL_BIN" -c 'set -- -z; getopts "ab" opt 2>/dev/null; echo $opt' 2>&1)
669 if [ "$result" = "?" ]; then
670 pass "getopts returns ? for invalid option"
671 else
672 fail "getopts returns ? for invalid option" "?" "$result"
673 fi
674
675 # Silent mode with leading colon
676 result=$("$SHELL_BIN" -c 'set -- -a; getopts ":a:b" opt; echo $opt' 2>&1)
677 if [ "$result" = ":" ] || [ "$result" = "a" ]; then
678 pass "getopts silent mode with colon"
679 else
680 fail "getopts silent mode with colon" ": or a" "$result"
681 fi
682
683 # Reset OPTIND
684 result=$("$SHELL_BIN" -c 'set -- -a; getopts "a" opt; OPTIND=1; getopts "a" opt; echo $opt' 2>&1)
685 if [ "$result" = "a" ]; then
686 pass "OPTIND reset allows reparse"
687 else
688 fail "OPTIND reset allows reparse" "a" "$result"
689 fi
690
691 # =====================================
692 section "402. TYPE BUILTIN"
693 # =====================================
694
695 # type finds commands
696 result=$("$SHELL_BIN" -c 'type echo' 2>&1)
697 if echo "$result" | grep -qE "echo|builtin|/"; then
698 pass "type finds echo"
699 else
700 fail "type finds echo" "echo info" "$result"
701 fi
702
703 # type returns error for unknown
704 result=$("$SHELL_BIN" -c 'type nonexistent_xyz_cmd 2>/dev/null; echo $?' 2>&1)
705 if [ "$result" != "0" ]; then
706 pass "type returns error for unknown command"
707 else
708 fail "type returns error for unknown command" "non-zero" "$result"
709 fi
710
711 # type finds functions
712 result=$("$SHELL_BIN" -c 'myfunc() { :; }; type myfunc' 2>&1)
713 if echo "$result" | grep -qE "myfunc|function"; then
714 pass "type finds functions"
715 else
716 fail "type finds functions" "function info" "$result"
717 fi
718
719 # type finds aliases
720 result=$("$SHELL_BIN" -c 'alias myalias=echo; type myalias' 2>&1)
721 if echo "$result" | grep -qE "myalias|alias"; then
722 pass "type finds aliases"
723 else
724 fail "type finds aliases" "alias info" "$result"
725 fi
726
727 # type finds builtins
728 result=$("$SHELL_BIN" -c 'type cd' 2>&1)
729 if echo "$result" | grep -qE "cd|builtin"; then
730 pass "type identifies builtins"
731 else
732 fail "type identifies builtins" "builtin info" "$result"
733 fi
734
735 # =====================================
736 section "403. COMMAND BUILTIN"
737 # =====================================
738
739 # command -v finds commands
740 result=$("$SHELL_BIN" -c 'command -v echo' 2>&1)
741 if [ -n "$result" ]; then
742 pass "command -v finds echo"
743 else
744 fail "command -v finds echo" "non-empty" "$result"
745 fi
746
747 # command -v returns empty for unknown
748 result=$("$SHELL_BIN" -c 'command -v nonexistent_xyz_cmd; echo $?' 2>&1)
749 if [ "$result" != "0" ]; then
750 pass "command -v returns error for unknown"
751 else
752 fail "command -v returns error for unknown" "non-zero exit" "$result"
753 fi
754
755 # command bypasses functions
756 result=$("$SHELL_BIN" -c 'echo() { printf "FUNC"; }; command echo hello' 2>&1)
757 if [ "$result" = "hello" ]; then
758 pass "command bypasses function"
759 else
760 fail "command bypasses function" "hello" "$result"
761 fi
762
763 # command bypasses aliases
764 result=$("$SHELL_BIN" -c 'alias echo="printf ALIAS"; command echo hello' 2>&1)
765 if [ "$result" = "hello" ]; then
766 pass "command bypasses alias"
767 else
768 fail "command bypasses alias" "hello" "$result"
769 fi
770
771 # =====================================
772 section "404. TRAP BUILTIN EDGE CASES"
773 # =====================================
774
775 # trap with empty action
776 result=$("$SHELL_BIN" -c 'trap "" INT; trap' 2>&1)
777 if echo "$result" | grep -qE "INT|''"; then
778 pass "trap with empty action (ignore signal)"
779 else
780 fail "trap with empty action (ignore signal)" "INT trap" "$result"
781 fi
782
783 # trap - removes trap
784 result=$("$SHELL_BIN" -c 'trap "echo x" EXIT; trap - EXIT; trap' 2>&1)
785 if ! echo "$result" | grep -q "EXIT"; then
786 pass "trap - removes trap"
787 else
788 fail "trap - removes trap" "no EXIT" "$result"
789 fi
790
791 # Multiple traps
792 result=$("$SHELL_BIN" -c 'trap "echo INT" INT; trap "echo TERM" TERM; trap | wc -l' 2>&1)
793 if [ "$result" -ge 2 ] 2>/dev/null; then
794 pass "Multiple traps can be set"
795 else
796 fail "Multiple traps can be set" ">=2 lines" "$result"
797 fi
798
799 # trap with no args lists traps
800 result=$("$SHELL_BIN" -c 'trap "echo x" INT; trap' 2>&1)
801 if [ -n "$result" ]; then
802 pass "trap with no args lists traps"
803 else
804 fail "trap with no args lists traps" "non-empty" "$result"
805 fi
806
807 # EXIT trap runs on exit
808 result=$("$SHELL_BIN" -c 'trap "echo EXITING" EXIT; exit 0' 2>&1)
809 if [ "$result" = "EXITING" ]; then
810 pass "EXIT trap executes on exit"
811 else
812 fail "EXIT trap executes on exit" "EXITING" "$result"
813 fi
814
815 # =====================================
816 section "405. HASH BUILTIN"
817 # =====================================
818
819 # hash without args
820 result=$("$SHELL_BIN" -c 'hash 2>&1; echo $?' 2>&1)
821 # hash returns 0 even if empty
822 pass "hash builtin exists"
823
824 # hash -r clears cache
825 result=$("$SHELL_BIN" -c 'ls >/dev/null 2>&1; hash -r; echo $?' 2>&1)
826 if [ "$result" = "0" ]; then
827 pass "hash -r clears cache"
828 else
829 fail "hash -r clears cache" "0" "$result"
830 fi
831
832 # =====================================
833 section "406. WAIT BUILTIN EDGE CASES"
834 # =====================================
835
836 # wait with no args
837 result=$("$SHELL_BIN" -c 'sleep 0.1 & sleep 0.1 & wait; echo done' 2>&1)
838 if [ "$result" = "done" ]; then
839 pass "wait with no args waits for all"
840 else
841 fail "wait with no args waits for all" "done" "$result"
842 fi
843
844 # wait with specific PID
845 result=$("$SHELL_BIN" -c 'sleep 0.1 & pid=$!; wait $pid; echo $?' 2>&1)
846 if [ "$result" = "0" ]; then
847 pass "wait with PID"
848 else
849 fail "wait with PID" "0" "$result"
850 fi
851
852 # wait preserves exit status
853 result=$("$SHELL_BIN" -c '(exit 42) & pid=$!; wait $pid; echo $?' 2>&1)
854 if [ "$result" = "42" ]; then
855 pass "wait preserves exit status"
856 else
857 fail "wait preserves exit status" "42" "$result"
858 fi
859
860 # =====================================
861 section "407. ULIMIT BUILTIN"
862 # =====================================
863
864 # ulimit -a shows limits
865 result=$("$SHELL_BIN" -c 'ulimit -a 2>&1' | head -5)
866 if [ -n "$result" ]; then
867 pass "ulimit -a shows limits"
868 else
869 fail "ulimit -a shows limits" "non-empty" "$result"
870 fi
871
872 # ulimit shows soft limit
873 result=$("$SHELL_BIN" -c 'ulimit 2>&1' | head -1)
874 if [ -n "$result" ]; then
875 pass "ulimit shows default limit"
876 else
877 fail "ulimit shows default limit" "non-empty" "$result"
878 fi
879
880 # =====================================
881 section "408. READ BUILTIN"
882 # =====================================
883
884 # read single variable
885 result=$("$SHELL_BIN" -c 'echo "hello" | { read x; echo $x; }' 2>&1)
886 if [ "$result" = "hello" ]; then
887 pass "read single variable"
888 else
889 fail "read single variable" "hello" "$result"
890 fi
891
892 # read multiple variables
893 result=$("$SHELL_BIN" -c 'echo "a b c" | { read x y z; echo "$x:$y:$z"; }' 2>&1)
894 if [ "$result" = "a:b:c" ]; then
895 pass "read multiple variables"
896 else
897 fail "read multiple variables" "a:b:c" "$result"
898 fi
899
900 # read with extra words
901 result=$("$SHELL_BIN" -c 'echo "a b c d e" | { read x y; echo "$x|$y"; }' 2>&1)
902 if [ "$result" = "a|b c d e" ]; then
903 pass "read extra words to last var"
904 else
905 fail "read extra words to last var" "a|b c d e" "$result"
906 fi
907
908 # =====================================
909 section "409. PRINTF BUILTIN"
910 # =====================================
911
912 # printf basic
913 result=$("$SHELL_BIN" -c 'printf "hello\n"' 2>&1)
914 if [ "$result" = "hello" ]; then
915 pass "printf basic"
916 else
917 fail "printf basic" "hello" "$result"
918 fi
919
920 # printf with format
921 result=$("$SHELL_BIN" -c 'printf "%s world\n" "hello"' 2>&1)
922 if [ "$result" = "hello world" ]; then
923 pass "printf with %s"
924 else
925 fail "printf with %s" "hello world" "$result"
926 fi
927
928 # printf with number
929 result=$("$SHELL_BIN" -c 'printf "%d\n" 42' 2>&1)
930 if [ "$result" = "42" ]; then
931 pass "printf with %d"
932 else
933 fail "printf with %d" "42" "$result"
934 fi
935
936 # printf width
937 result=$("$SHELL_BIN" -c 'printf "%5d\n" 42' 2>&1)
938 if [ "$result" = " 42" ]; then
939 pass "printf with width"
940 else
941 fail "printf with width" " 42" "$result"
942 fi
943
944 # =====================================
945 section "410. ECHO BUILTIN"
946 # =====================================
947
948 # echo basic
949 result=$("$SHELL_BIN" -c 'echo hello' 2>&1)
950 if [ "$result" = "hello" ]; then
951 pass "echo basic"
952 else
953 fail "echo basic" "hello" "$result"
954 fi
955
956 # echo multiple args
957 result=$("$SHELL_BIN" -c 'echo a b c' 2>&1)
958 if [ "$result" = "a b c" ]; then
959 pass "echo multiple args"
960 else
961 fail "echo multiple args" "a b c" "$result"
962 fi
963
964 # echo with quotes
965 result=$("$SHELL_BIN" -c 'echo "hello world"' 2>&1)
966 if [ "$result" = "hello world" ]; then
967 pass "echo with quotes"
968 else
969 fail "echo with quotes" "hello world" "$result"
970 fi
971
972 # =====================================
973 section "411. KILL BUILTIN"
974 # =====================================
975
976 # kill -l lists signals
977 result=$("$SHELL_BIN" -c 'kill -l 2>&1 | head -1')
978 if [ -n "$result" ]; then
979 pass "kill -l lists signals"
980 else
981 fail "kill -l lists signals" "non-empty" "$result"
982 fi
983
984 # =====================================
985 section "412. SET OPTIONS"
986 # =====================================
987
988 # set -e (errexit)
989 result=$("$SHELL_BIN" -c 'set -e; true; echo reached' 2>&1)
990 if [ "$result" = "reached" ]; then
991 pass "set -e continues on success"
992 else
993 fail "set -e continues on success" "reached" "$result"
994 fi
995
996 # set -x (xtrace)
997 result=$("$SHELL_BIN" -c 'set -x; echo test 2>&1' | grep -c test)
998 if [ "$result" -ge 1 ]; then
999 pass "set -x traces commands"
1000 else
1001 fail "set -x traces commands"
1002 fi
1003
1004 # set -f (noglob)
1005 result=$("$SHELL_BIN" -c 'set -f; echo *' 2>&1)
1006 if [ "$result" = "*" ]; then
1007 pass "set -f disables glob"
1008 else
1009 fail "set -f disables glob" "*" "$result"
1010 fi
1011
1012 # set +f (enable glob)
1013 result=$("$SHELL_BIN" -c 'set -f; set +f; ls /*.txt 2>/dev/null | wc -l || echo 0' 2>&1)
1014 if echo "$result" | grep -qE '^[[:space:]]*[0-9]+[[:space:]]*$'; then
1015 pass "set +f enables glob"
1016 else
1017 fail "set +f enables glob"
1018 fi
1019
1020 # =====================================
1021 section "413. UNSET BUILTIN"
1022 # =====================================
1023
1024 # unset variable
1025 result=$("$SHELL_BIN" -c 'x=value; unset x; echo ${x:-empty}' 2>&1)
1026 if [ "$result" = "empty" ]; then
1027 pass "unset removes variable"
1028 else
1029 fail "unset removes variable" "empty" "$result"
1030 fi
1031
1032 # unset function
1033 result=$("$SHELL_BIN" -c 'f() { echo hi; }; unset -f f; f 2>/dev/null || echo gone' 2>&1)
1034 if [ "$result" = "gone" ]; then
1035 pass "unset -f removes function"
1036 else
1037 fail "unset -f removes function" "gone" "$result"
1038 fi
1039
1040 # unset readonly fails
1041 result=$("$SHELL_BIN" -c 'readonly x=1; unset x 2>/dev/null; echo $?' 2>&1)
1042 if [ "$result" != "0" ]; then
1043 pass "unset readonly fails"
1044 else
1045 fail "unset readonly fails" "non-zero" "$result"
1046 fi
1047
1048 # =====================================
1049 section "414. SHIFT BUILTIN"
1050 # =====================================
1051
1052 # basic shift
1053 result=$("$SHELL_BIN" -c 'set -- a b c; shift; echo $1' 2>&1)
1054 if [ "$result" = "b" ]; then
1055 pass "shift removes first arg"
1056 else
1057 fail "shift removes first arg" "b" "$result"
1058 fi
1059
1060 # shift with count
1061 result=$("$SHELL_BIN" -c 'set -- a b c d e; shift 3; echo $1' 2>&1)
1062 if [ "$result" = "d" ]; then
1063 pass "shift with count"
1064 else
1065 fail "shift with count" "d" "$result"
1066 fi
1067
1068 # shift updates $#
1069 result=$("$SHELL_BIN" -c 'set -- a b c; shift; echo $#' 2>&1)
1070 if [ "$result" = "2" ]; then
1071 pass "shift updates arg count"
1072 else
1073 fail "shift updates arg count" "2" "$result"
1074 fi
1075
1076 # =====================================
1077 section "415. TRUE AND FALSE BUILTINS"
1078 # =====================================
1079
1080 # true returns 0
1081 result=$("$SHELL_BIN" -c 'true; echo $?' 2>&1)
1082 if [ "$result" = "0" ]; then
1083 pass "true returns 0"
1084 else
1085 fail "true returns 0" "0" "$result"
1086 fi
1087
1088 # false returns 1
1089 result=$("$SHELL_BIN" -c 'false; echo $?' 2>&1)
1090 if [ "$result" = "1" ]; then
1091 pass "false returns 1"
1092 else
1093 fail "false returns 1" "1" "$result"
1094 fi
1095
1096 # true in conditional
1097 result=$("$SHELL_BIN" -c 'if true; then echo yes; fi' 2>&1)
1098 if [ "$result" = "yes" ]; then
1099 pass "true in if condition"
1100 else
1101 fail "true in if condition" "yes" "$result"
1102 fi
1103
1104 # false in conditional
1105 result=$("$SHELL_BIN" -c 'if false; then echo no; else echo yes; fi' 2>&1)
1106 if [ "$result" = "yes" ]; then
1107 pass "false in if condition"
1108 else
1109 fail "false in if condition" "yes" "$result"
1110 fi
1111
1112 # =====================================
1113 section "416. COLON BUILTIN"
1114 # =====================================
1115
1116 # colon returns 0
1117 result=$("$SHELL_BIN" -c ':; echo $?' 2>&1)
1118 if [ "$result" = "0" ]; then
1119 pass "colon returns 0"
1120 else
1121 fail "colon returns 0" "0" "$result"
1122 fi
1123
1124 # colon with args (ignored)
1125 result=$("$SHELL_BIN" -c ': arg1 arg2 arg3; echo $?' 2>&1)
1126 if [ "$result" = "0" ]; then
1127 pass "colon ignores args"
1128 else
1129 fail "colon ignores args" "0" "$result"
1130 fi
1131
1132 # colon in loop condition
1133 result=$("$SHELL_BIN" -c 'i=0; while :; do i=$((i+1)); [ $i -ge 3 ] && break; done; echo $i' 2>&1)
1134 if [ "$result" = "3" ]; then
1135 pass "colon as infinite loop condition"
1136 else
1137 fail "colon as infinite loop condition" "3" "$result"
1138 fi
1139
1140 # =====================================
1141 section "417. PWD BUILTIN"
1142 # =====================================
1143
1144 # pwd returns current dir
1145 result=$("$SHELL_BIN" -c 'pwd | grep -c "^/"' 2>&1)
1146 if [ "$result" = "1" ]; then
1147 pass "pwd returns absolute path"
1148 else
1149 fail "pwd returns absolute path" "1" "$result"
1150 fi
1151
1152 # pwd after cd
1153 result=$("$SHELL_BIN" -c 'cd /tmp && pwd' 2>&1)
1154 if [ "$result" = "/tmp" ]; then
1155 pass "pwd after cd"
1156 else
1157 fail "pwd after cd" "/tmp" "$result"
1158 fi
1159
1160 # =====================================
1161 section "418. CD BUILTIN"
1162 # =====================================
1163
1164 # cd to absolute path
1165 result=$("$SHELL_BIN" -c 'cd /tmp && pwd' 2>&1)
1166 if [ "$result" = "/tmp" ]; then
1167 pass "cd to absolute path"
1168 else
1169 fail "cd to absolute path" "/tmp" "$result"
1170 fi
1171
1172 # cd - returns to OLDPWD
1173 result=$("$SHELL_BIN" -c 'cd /tmp; cd /; cd - 2>&1' 2>&1)
1174 if echo "$result" | grep -q "tmp"; then
1175 pass "cd - returns to OLDPWD"
1176 else
1177 fail "cd - returns to OLDPWD"
1178 fi
1179
1180 # cd nonexistent fails
1181 result=$("$SHELL_BIN" -c 'cd /nonexistent_dir_xyz 2>/dev/null; echo $?' 2>&1)
1182 if [ "$result" != "0" ]; then
1183 pass "cd nonexistent fails"
1184 else
1185 fail "cd nonexistent fails" "non-zero" "$result"
1186 fi
1187
1188 # =====================================
1189 section "419. TIMES BUILTIN"
1190 # =====================================
1191
1192 # times outputs something
1193 result=$("$SHELL_BIN" -c 'times 2>&1 | head -1 || echo skipped')
1194 if [ -n "$result" ]; then
1195 pass "times outputs timing info"
1196 else
1197 fail "times outputs timing info" "non-empty" "$result"
1198 fi
1199
1200 # =====================================
1201 section "420. EXIT BUILTIN"
1202 # =====================================
1203
1204 # exit with code
1205 result=$("$SHELL_BIN" -c 'exit 42' 2>&1; echo $?)
1206 if [ "$result" = "42" ]; then
1207 pass "exit with code"
1208 else
1209 fail "exit with code" "42" "$result"
1210 fi
1211
1212 # exit default 0
1213 result=$("$SHELL_BIN" -c 'exit' 2>&1; echo $?)
1214 if [ "$result" = "0" ]; then
1215 pass "exit default 0"
1216 else
1217 fail "exit default 0" "0" "$result"
1218 fi
1219
1220 # exit from subshell
1221 result=$("$SHELL_BIN" -c '(exit 7); echo $?' 2>&1)
1222 if [ "$result" = "7" ]; then
1223 pass "exit from subshell"
1224 else
1225 fail "exit from subshell" "7" "$result"
1226 fi
1227
1228 # =====================================
1229 # Summary
1230 # =====================================
1231 printf "\n"
1232 printf "${BLUE}==========================================\n"
1233 printf "POSIX Shell Options and Read Summary\n"
1234 printf "==========================================${NC}\n"
1235 printf "Passed: ${GREEN}%d${NC}\n" "$PASSED"
1236 printf "Failed: ${RED}%d${NC}\n" "$FAILED"
1237 printf "Skipped: ${YELLOW}%d${NC}\n" "$SKIPPED"
1238 printf "Total: %d\n" "$((PASSED + FAILED + SKIPPED))"
1239
1240 if [ -n "$FAILED_TESTS_LIST" ]; then
1241 printf "\n${RED}Failed tests:${NC}\n"
1242 printf "%b" "$FAILED_TESTS_LIST"
1243 fi
1244
1245 if [ "$FAILED" -gt 0 ]; then
1246 exit 1
1247 fi
1248 exit 0