Bash · 28646 bytes Raw Blame History
1 #!/bin/sh
2 # =====================================
3 # POSIX Compliance Redirection and Pipeline Test Suite for shell
4 # =====================================
5 # Tests redirections and pipelines per IEEE Std 1003.1-2017
6 # Section: Shell Command Language - Redirection
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-redirect]"
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
29 # Check if shell binary exists
30 if [ ! -x "$SHELL_BIN" ]; then
31 printf "${RED}ERROR${NC}: shell binary not found at $SHELL_BIN\n"
32 printf "Please set SHELL_BIN or set SHELL_BIN environment variable\n"
33 exit 1
34 fi
35
36 # Test result trackers
37 pass() {
38 TEST_NUM=$((TEST_NUM + 1))
39 printf "${GREEN}✓ PASS${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s\n" "$1"
40 PASSED=$((PASSED + 1))
41 }
42
43 fail() {
44 TEST_NUM=$((TEST_NUM + 1))
45 TEST_ID="${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}"
46 printf "${RED}✗ FAIL${NC} ${TEST_ID}: %s\n" "$1"
47 FAILED_TESTS_LIST="${FAILED_TESTS_LIST} ${TEST_ID}: $1\n"
48 if [ -n "$2" ]; then
49 printf " expected: %s\n" "$2"
50 fi
51 if [ -n "$3" ]; then
52 printf " got: %s\n" "$3"
53 fi
54 FAILED=$((FAILED + 1))
55 }
56
57 skip() {
58 TEST_NUM=$((TEST_NUM + 1))
59 printf "${YELLOW}⊘ SKIP${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s - %s\n" "$1" "$2"
60 SKIPPED=$((SKIPPED + 1))
61 }
62
63 section() {
64 CURRENT_SECTION=$(echo "$1" | grep -oE '^[0-9]+' || echo "0")
65 TEST_NUM=0
66 printf "\n"
67 printf "${BLUE}==========================================\n"
68 printf "%s\n" "$1"
69 printf "==========================================${NC}\n"
70 }
71
72 TEST_DIR="/tmp/bensch_redirect_$$"
73 mkdir -p "$TEST_DIR"
74
75 cleanup() {
76 rm -rf "$TEST_DIR"
77 }
78 trap cleanup EXIT
79
80 # =====================================
81 section "428. OUTPUT REDIRECTION >"
82 # =====================================
83
84 result=$("$SHELL_BIN" -c 'echo hello > '"$TEST_DIR"'/out1.txt; cat '"$TEST_DIR"'/out1.txt' 2>&1)
85 if [ "$result" = "hello" ]; then
86 pass "> redirects stdout to file"
87 else
88 fail "> redirects stdout to file" "hello" "$result"
89 fi
90
91 result=$("$SHELL_BIN" -c 'echo first > '"$TEST_DIR"'/out2.txt; echo second > '"$TEST_DIR"'/out2.txt; cat '"$TEST_DIR"'/out2.txt' 2>&1)
92 if [ "$result" = "second" ]; then
93 pass "> overwrites existing file"
94 else
95 fail "> overwrites existing file" "second" "$result"
96 fi
97
98 result=$("$SHELL_BIN" -c 'echo "multi word" > '"$TEST_DIR"'/out3.txt; cat '"$TEST_DIR"'/out3.txt' 2>&1)
99 if [ "$result" = "multi word" ]; then
100 pass "> with quoted string"
101 else
102 fail "> with quoted string" "multi word" "$result"
103 fi
104
105 # =====================================
106 section "429. APPEND REDIRECTION >>"
107 # =====================================
108
109 result=$("$SHELL_BIN" -c 'echo first > '"$TEST_DIR"'/append.txt; echo second >> '"$TEST_DIR"'/append.txt; cat '"$TEST_DIR"'/append.txt' 2>&1)
110 expected=$(printf "first\nsecond")
111 if [ "$result" = "$expected" ]; then
112 pass ">> appends to file"
113 else
114 fail ">> appends to file" "$expected" "$result"
115 fi
116
117 result=$("$SHELL_BIN" -c 'echo line1 >> '"$TEST_DIR"'/new.txt; cat '"$TEST_DIR"'/new.txt' 2>&1)
118 if [ "$result" = "line1" ]; then
119 pass ">> creates file if not exists"
120 else
121 fail ">> creates file if not exists" "line1" "$result"
122 fi
123
124 # =====================================
125 section "430. INPUT REDIRECTION <"
126 # =====================================
127
128 echo "test input" > "$TEST_DIR/input.txt"
129
130 result=$("$SHELL_BIN" -c 'cat < '"$TEST_DIR"'/input.txt' 2>&1)
131 if [ "$result" = "test input" ]; then
132 pass "< redirects file to stdin"
133 else
134 fail "< redirects file to stdin" "test input" "$result"
135 fi
136
137 result=$("$SHELL_BIN" -c 'read line < '"$TEST_DIR"'/input.txt; echo "$line"' 2>&1)
138 if [ "$result" = "test input" ]; then
139 pass "< with read builtin"
140 else
141 fail "< with read builtin" "test input" "$result"
142 fi
143
144 # =====================================
145 section "431. STDERR REDIRECTION 2>"
146 # =====================================
147
148 result=$("$SHELL_BIN" -c 'ls /nonexistent 2> '"$TEST_DIR"'/err.txt; cat '"$TEST_DIR"'/err.txt' 2>&1)
149 if echo "$result" | grep -qi "no such\|not found\|cannot"; then
150 pass "2> redirects stderr to file"
151 else
152 fail "2> redirects stderr to file" "error message" "$result"
153 fi
154
155 result=$("$SHELL_BIN" -c 'echo stdout; ls /nonexistent 2>/dev/null' 2>&1)
156 if [ "$result" = "stdout" ]; then
157 pass "2>/dev/null suppresses stderr"
158 else
159 fail "2>/dev/null suppresses stderr" "stdout" "$result"
160 fi
161
162 # =====================================
163 section "432. REDIRECT STDERR TO STDOUT 2>&1"
164 # =====================================
165
166 result=$("$SHELL_BIN" -c '{ echo out; ls /nonexistent; } 2>&1 | head -2' 2>&1)
167 if echo "$result" | grep -q "out"; then
168 pass "2>&1 merges stderr to stdout"
169 else
170 fail "2>&1 merges stderr to stdout" "both streams" "$result"
171 fi
172
173 result=$("$SHELL_BIN" -c 'ls /nonexistent 2>&1 | cat' 2>&1)
174 if echo "$result" | grep -qi "no such\|not found\|cannot"; then
175 pass "2>&1 stderr goes through pipe"
176 else
177 fail "2>&1 stderr goes through pipe" "error in pipe" "$result"
178 fi
179
180 # =====================================
181 section "433. REDIRECT STDOUT TO STDERR 1>&2"
182 # =====================================
183
184 result=$("$SHELL_BIN" -c 'echo error >&2' 2>&1)
185 if [ "$result" = "error" ]; then
186 pass ">&2 redirects stdout to stderr"
187 else
188 fail ">&2 redirects stdout to stderr" "error" "$result"
189 fi
190
191 # Note: matches bash - POSIX redirections processed left-to-right
192 # 1>&2 copies fd2 to fd1, then 2>/dev/null redirects fd2 to null
193 # fd1 still points to original fd2 (stderr), so output appears
194 result=$("$SHELL_BIN" -c 'echo error 1>&2 2>/dev/null' 2>&1)
195 if [ "$result" = "error" ]; then
196 pass "1>&2 with stderr suppressed (matches bash)"
197 else
198 fail "1>&2 with stderr suppressed (matches bash)" "error" "$result"
199 fi
200
201 # =====================================
202 section "434. COMBINED REDIRECTIONS"
203 # =====================================
204
205 result=$("$SHELL_BIN" -c 'echo out > '"$TEST_DIR"'/combined.txt 2>&1; cat '"$TEST_DIR"'/combined.txt' 2>&1)
206 if [ "$result" = "out" ]; then
207 pass "> file 2>&1 combination"
208 else
209 fail "> file 2>&1 combination" "out" "$result"
210 fi
211
212 result=$("$SHELL_BIN" -c '{ echo out; echo err >&2; } > '"$TEST_DIR"'/both.txt 2>&1; cat '"$TEST_DIR"'/both.txt' 2>&1)
213 expected=$(printf "out\nerr")
214 if [ "$result" = "$expected" ]; then
215 pass "Both streams to same file"
216 else
217 fail "Both streams to same file" "$expected" "$result"
218 fi
219
220 # =====================================
221 section "435. NOCLOBBER WITH >|"
222 # =====================================
223
224 echo "original" > "$TEST_DIR/noclobber.txt"
225
226 result=$("$SHELL_BIN" -c 'set -C; echo new >| '"$TEST_DIR"'/noclobber.txt; cat '"$TEST_DIR"'/noclobber.txt' 2>&1)
227 if [ "$result" = "new" ]; then
228 pass ">| overrides noclobber"
229 else
230 fail ">| overrides noclobber" "new" "$result"
231 fi
232
233 # =====================================
234 section "436. SIMPLE PIPELINE"
235 # =====================================
236
237 result=$("$SHELL_BIN" -c 'echo hello | cat' 2>&1)
238 if [ "$result" = "hello" ]; then
239 pass "Simple two-stage pipeline"
240 else
241 fail "Simple two-stage pipeline" "hello" "$result"
242 fi
243
244 result=$("$SHELL_BIN" -c 'echo hello world | wc -w' 2>&1)
245 if echo "$result" | grep -q "2"; then
246 pass "Pipeline with wc"
247 else
248 fail "Pipeline with wc" "2" "$result"
249 fi
250
251 result=$("$SHELL_BIN" -c 'printf "c\na\nb\n" | sort' 2>&1)
252 expected=$(printf "a\nb\nc")
253 if [ "$result" = "$expected" ]; then
254 pass "Pipeline with sort"
255 else
256 fail "Pipeline with sort" "$expected" "$result"
257 fi
258
259 # =====================================
260 section "437. MULTI-STAGE PIPELINE"
261 # =====================================
262
263 result=$("$SHELL_BIN" -c 'echo hello | cat | cat | cat' 2>&1)
264 if [ "$result" = "hello" ]; then
265 pass "Four-stage pipeline"
266 else
267 fail "Four-stage pipeline" "hello" "$result"
268 fi
269
270 result=$("$SHELL_BIN" -c 'printf "b\na\nc\nb\na\n" | sort | uniq' 2>&1)
271 expected=$(printf "a\nb\nc")
272 if [ "$result" = "$expected" ]; then
273 pass "sort | uniq pipeline"
274 else
275 fail "sort | uniq pipeline" "$expected" "$result"
276 fi
277
278 result=$("$SHELL_BIN" -c 'echo "hello world" | tr " " "\n" | wc -l' 2>&1)
279 if echo "$result" | grep -q "2"; then
280 pass "tr | wc pipeline"
281 else
282 fail "tr | wc pipeline" "2" "$result"
283 fi
284
285 # =====================================
286 section "438. PIPELINE WITH REDIRECTION"
287 # =====================================
288
289 result=$("$SHELL_BIN" -c 'echo hello | cat > '"$TEST_DIR"'/pipe_out.txt; cat '"$TEST_DIR"'/pipe_out.txt' 2>&1)
290 if [ "$result" = "hello" ]; then
291 pass "Pipeline with output redirect"
292 else
293 fail "Pipeline with output redirect" "hello" "$result"
294 fi
295
296 echo "from file" > "$TEST_DIR/pipe_in.txt"
297 result=$("$SHELL_BIN" -c 'cat < '"$TEST_DIR"'/pipe_in.txt | tr "a-z" "A-Z"' 2>&1)
298 if [ "$result" = "FROM FILE" ]; then
299 pass "Input redirect into pipeline"
300 else
301 fail "Input redirect into pipeline" "FROM FILE" "$result"
302 fi
303
304 # =====================================
305 section "439. PIPELINE SUBSHELL"
306 # =====================================
307
308 result=$("$SHELL_BIN" -c 'echo test | { read x; echo "got: $x"; }' 2>&1)
309 if [ "$result" = "got: test" ]; then
310 pass "Pipeline to brace group"
311 else
312 fail "Pipeline to brace group" "got: test" "$result"
313 fi
314
315 result=$("$SHELL_BIN" -c 'echo test | (cat; echo done)' 2>&1)
316 expected=$(printf "test\ndone")
317 if [ "$result" = "$expected" ]; then
318 pass "Pipeline to subshell"
319 else
320 fail "Pipeline to subshell" "$expected" "$result"
321 fi
322
323 # =====================================
324 section "440. COMMAND SUBSTITUTION IN PIPELINE"
325 # =====================================
326
327 result=$("$SHELL_BIN" -c 'echo $(echo hello | tr "a-z" "A-Z")' 2>&1)
328 if [ "$result" = "HELLO" ]; then
329 pass "Command substitution with pipeline"
330 else
331 fail "Command substitution with pipeline" "HELLO" "$result"
332 fi
333
334 result=$("$SHELL_BIN" -c 'x=$(printf "a\nb\nc\n" | wc -l); echo $x' 2>&1)
335 if echo "$result" | grep -q "3"; then
336 pass "Variable from pipeline in command sub"
337 else
338 fail "Variable from pipeline in command sub" "3" "$result"
339 fi
340
341 # =====================================
342 section "441. REDIRECTION ORDER"
343 # =====================================
344
345 # The order matters: 2>&1 before > vs after
346 result=$("$SHELL_BIN" -c '{ echo out; echo err >&2; } > '"$TEST_DIR"'/order1.txt 2>&1; cat '"$TEST_DIR"'/order1.txt | wc -l' 2>&1)
347 if echo "$result" | grep -q "2"; then
348 pass "> file 2>&1 captures both"
349 else
350 fail "> file 2>&1 captures both" "2 lines" "$result"
351 fi
352
353 # =====================================
354 section "442. /dev/null REDIRECTION"
355 # =====================================
356
357 result=$("$SHELL_BIN" -c 'echo hello > /dev/null; echo done' 2>&1)
358 if [ "$result" = "done" ]; then
359 pass "> /dev/null discards output"
360 else
361 fail "> /dev/null discards output" "done" "$result"
362 fi
363
364 result=$("$SHELL_BIN" -c 'cat < /dev/null; echo empty' 2>&1)
365 if [ "$result" = "empty" ]; then
366 pass "< /dev/null provides empty input"
367 else
368 fail "< /dev/null provides empty input" "empty" "$result"
369 fi
370
371 result=$("$SHELL_BIN" -c 'ls /nonexistent 2>/dev/null; echo $?' 2>&1)
372 # Should show non-zero exit but no error output
373 if echo "$result" | grep -qE "^[12]$"; then
374 pass "2>/dev/null with failing command"
375 else
376 fail "2>/dev/null with failing command" "exit code only" "$result"
377 fi
378
379 # =====================================
380 section "443. PROCESS SUBSTITUTION STYLE"
381 # =====================================
382
383 # Note: <() is a bash extension, but we test if basic redirects work with commands
384 result=$("$SHELL_BIN" -c 'diff <(echo a) <(echo a) 2>/dev/null; echo $?' 2>&1)
385 # This may not be supported - just document if it works
386 if [ "$result" = "0" ]; then
387 pass "Process substitution <() works"
388 else
389 skip "Process substitution <() works" "bash extension"
390 fi
391
392 # =====================================
393 section "444. CLOSING FILE DESCRIPTORS"
394 # =====================================
395
396 result=$("$SHELL_BIN" -c 'echo hello >&-; echo done 2>/dev/null' 2>&1)
397 # Closing stdout then trying to echo should either error or succeed
398 if echo "$result" | grep -q "done"; then
399 pass ">&- closes stdout (recovered)"
400 else
401 pass ">&- closes stdout (caused error)"
402 fi
403
404 # =====================================
405 section "445. DUPLICATING INPUT"
406 # =====================================
407
408 result=$("$SHELL_BIN" -c 'echo test | { cat; } 0<&0' 2>&1)
409 if [ "$result" = "test" ]; then
410 pass "0<&0 duplicates stdin"
411 else
412 fail "0<&0 duplicates stdin" "test" "$result"
413 fi
414
415 # =====================================
416 section "446. PIPELINE EXIT STATUS"
417 # =====================================
418
419 # Last command determines exit status
420 result=$("$SHELL_BIN" -c 'false | true; echo $?' 2>&1)
421 if [ "$result" = "0" ]; then
422 pass "Pipeline exit is last command (false|true=0)"
423 else
424 fail "Pipeline exit is last command (false|true=0)" "0" "$result"
425 fi
426
427 result=$("$SHELL_BIN" -c 'true | false; echo $?' 2>&1)
428 if [ "$result" = "1" ]; then
429 pass "Pipeline exit is last command (true|false=1)"
430 else
431 fail "Pipeline exit is last command (true|false=1)" "1" "$result"
432 fi
433
434 # Multi-stage pipeline
435 result=$("$SHELL_BIN" -c 'echo a | cat | cat | cat; echo $?' 2>&1)
436 if echo "$result" | grep -q "0"; then
437 pass "Multi-stage pipeline success"
438 else
439 fail "Multi-stage pipeline success" "0" "$result"
440 fi
441
442 # Pipeline with negation
443 result=$("$SHELL_BIN" -c '! false | true; echo $?' 2>&1)
444 if [ "$result" = "1" ]; then
445 pass "! negates pipeline exit"
446 else
447 fail "! negates pipeline exit" "1" "$result"
448 fi
449
450 # =====================================
451 section "447. EXEC REDIRECTIONS"
452 # =====================================
453
454 # exec without command modifies shell FDs
455 result=$("$SHELL_BIN" -c '
456 exec 3>"'"$TEST_DIR"'/exec_fd3.txt"
457 echo "fd3 data" >&3
458 exec 3>&-
459 cat "'"$TEST_DIR"'/exec_fd3.txt"
460 ' 2>&1)
461 if [ "$result" = "fd3 data" ]; then
462 pass "exec opens FD 3 for writing"
463 else
464 fail "exec opens FD 3 for writing" "fd3 data" "$result"
465 fi
466
467 # exec read/write mode
468 result=$("$SHELL_BIN" -c '
469 echo "initial" > "'"$TEST_DIR"'/rw_test.txt"
470 exec 4<>"'"$TEST_DIR"'/rw_test.txt"
471 read line <&4
472 echo "read: $line"
473 exec 4>&-
474 ' 2>&1)
475 if [ "$result" = "read: initial" ]; then
476 pass "exec <> opens read-write"
477 else
478 fail "exec <> opens read-write" "read: initial" "$result"
479 fi
480
481 # =====================================
482 section "448. COMPOUND REDIRECTIONS"
483 # =====================================
484
485 # Redirect entire loop
486 result=$("$SHELL_BIN" -c '
487 for i in 1 2 3; do
488 echo $i
489 done > "'"$TEST_DIR"'/loop_out.txt"
490 cat "'"$TEST_DIR"'/loop_out.txt"
491 ' 2>&1)
492 expected=$(printf "1\n2\n3")
493 if [ "$result" = "$expected" ]; then
494 pass "Redirect entire for loop"
495 else
496 fail "Redirect entire for loop" "$expected" "$result"
497 fi
498
499 # Redirect if statement
500 result=$("$SHELL_BIN" -c '
501 if true; then
502 echo inside
503 fi > "'"$TEST_DIR"'/if_out.txt"
504 cat "'"$TEST_DIR"'/if_out.txt"
505 ' 2>&1)
506 if [ "$result" = "inside" ]; then
507 pass "Redirect entire if statement"
508 else
509 fail "Redirect entire if statement" "inside" "$result"
510 fi
511
512 # Redirect case statement
513 result=$("$SHELL_BIN" -c '
514 case "x" in
515 x) echo matched;;
516 esac > "'"$TEST_DIR"'/case_out.txt"
517 cat "'"$TEST_DIR"'/case_out.txt"
518 ' 2>&1)
519 if [ "$result" = "matched" ]; then
520 pass "Redirect entire case statement"
521 else
522 fail "Redirect entire case statement" "matched" "$result"
523 fi
524
525 # =====================================
526 section "449. INPUT REDIRECTION VARIATIONS"
527 # =====================================
528
529 # cat multiple files
530 echo "file1" > "$TEST_DIR/cat1.txt"
531 echo "file2" > "$TEST_DIR/cat2.txt"
532 result=$("$SHELL_BIN" -c 'cat "'"$TEST_DIR"'/cat1.txt" "'"$TEST_DIR"'/cat2.txt"' 2>&1)
533 expected=$(printf "file1\nfile2")
534 if [ "$result" = "$expected" ]; then
535 pass "cat multiple files"
536 else
537 fail "cat multiple files" "$expected" "$result"
538 fi
539
540 # Input from file
541 echo "from file" > "$TEST_DIR/input.txt"
542 result=$("$SHELL_BIN" -c 'cat < "'"$TEST_DIR"'/input.txt"' 2>&1)
543 if [ "$result" = "from file" ]; then
544 pass "< redirects stdin from file"
545 else
546 fail "< redirects stdin from file" "from file" "$result"
547 fi
548
549 # =====================================
550 section "450. OUTPUT APPEND BEHAVIOR"
551 # =====================================
552
553 # >> creates file if not exists
554 rm -f "$TEST_DIR/append_new.txt"
555 result=$("$SHELL_BIN" -c 'echo first >> "'"$TEST_DIR"'/append_new.txt"; cat "'"$TEST_DIR"'/append_new.txt"' 2>&1)
556 if [ "$result" = "first" ]; then
557 pass ">> creates file if not exists"
558 else
559 fail ">> creates file if not exists" "first" "$result"
560 fi
561
562 # >> appends to existing
563 echo "line1" > "$TEST_DIR/append_exist.txt"
564 result=$("$SHELL_BIN" -c 'echo line2 >> "'"$TEST_DIR"'/append_exist.txt"; cat "'"$TEST_DIR"'/append_exist.txt"' 2>&1)
565 expected=$(printf "line1\nline2")
566 if [ "$result" = "$expected" ]; then
567 pass ">> appends to existing file"
568 else
569 fail ">> appends to existing file" "$expected" "$result"
570 fi
571
572 # Multiple appends
573 rm -f "$TEST_DIR/multi_append.txt"
574 result=$("$SHELL_BIN" -c '
575 echo a >> "'"$TEST_DIR"'/multi_append.txt"
576 echo b >> "'"$TEST_DIR"'/multi_append.txt"
577 echo c >> "'"$TEST_DIR"'/multi_append.txt"
578 cat "'"$TEST_DIR"'/multi_append.txt"
579 ' 2>&1)
580 expected=$(printf "a\nb\nc")
581 if [ "$result" = "$expected" ]; then
582 pass "Multiple sequential appends"
583 else
584 fail "Multiple sequential appends" "$expected" "$result"
585 fi
586
587 # =====================================
588 section "451. REDIRECT WITH ASSIGNMENTS"
589 # =====================================
590
591 # Assignment with redirect
592 result=$("$SHELL_BIN" -c 'x=$(cat < /dev/null); echo "empty:[$x]"' 2>&1)
593 if [ "$result" = "empty:[]" ]; then
594 pass "Assignment from empty file"
595 else
596 fail "Assignment from empty file" "empty:[]" "$result"
597 fi
598
599 # Assignment captures command output despite redirects
600 result=$("$SHELL_BIN" -c 'x=$(echo hello 2>/dev/null); echo $x' 2>&1)
601 if [ "$result" = "hello" ]; then
602 pass "Assignment captures stdout"
603 else
604 fail "Assignment captures stdout" "hello" "$result"
605 fi
606
607 # =====================================
608 section "452. HEREDOC VARIATIONS"
609 # =====================================
610
611 # Basic heredoc
612 result=$("$SHELL_BIN" -c 'cat <<END
613 hello
614 END' 2>&1)
615 if [ "$result" = "hello" ]; then
616 pass "Basic heredoc"
617 else
618 fail "Basic heredoc" "hello" "$result"
619 fi
620
621 # Heredoc with variable expansion
622 result=$("$SHELL_BIN" -c 'X=world; cat <<END
623 hello $X
624 END' 2>&1)
625 if [ "$result" = "hello world" ]; then
626 pass "Heredoc with variable expansion"
627 else
628 fail "Heredoc with variable expansion" "hello world" "$result"
629 fi
630
631 # Quoted heredoc prevents expansion
632 result=$("$SHELL_BIN" -c 'cat <<'\''END'\''
633 $VAR
634 END' 2>&1)
635 if [ "$result" = '$VAR' ]; then
636 pass "Quoted heredoc prevents expansion"
637 else
638 fail "Quoted heredoc prevents expansion" "\$VAR" "$result"
639 fi
640
641 # =====================================
642 section "453. PIPELINE VARIATIONS"
643 # =====================================
644
645 # Long pipeline
646 result=$("$SHELL_BIN" -c 'echo test | cat | cat | cat | cat' 2>&1)
647 if [ "$result" = "test" ]; then
648 pass "Long pipeline with multiple cats"
649 else
650 fail "Long pipeline with multiple cats" "test" "$result"
651 fi
652
653 # Pipeline with head
654 result=$("$SHELL_BIN" -c 'printf "a\nb\nc\n" | head -1' 2>&1)
655 if [ "$result" = "a" ]; then
656 pass "Pipeline with head"
657 else
658 fail "Pipeline with head" "a" "$result"
659 fi
660
661 # Pipeline with tail
662 result=$("$SHELL_BIN" -c 'printf "a\nb\nc\n" | tail -1' 2>&1)
663 if [ "$result" = "c" ]; then
664 pass "Pipeline with tail"
665 else
666 fail "Pipeline with tail" "c" "$result"
667 fi
668
669 # Pipeline with sort
670 result=$("$SHELL_BIN" -c 'printf "c\na\nb\n" | sort | head -1' 2>&1)
671 if [ "$result" = "a" ]; then
672 pass "Pipeline with sort"
673 else
674 fail "Pipeline with sort" "a" "$result"
675 fi
676
677 # =====================================
678 section "454. FILE DESCRIPTOR OPERATIONS"
679 # =====================================
680
681 # Dup stdout to stderr
682 result=$("$SHELL_BIN" -c 'echo test >&2' 2>&1)
683 if [ "$result" = "test" ]; then
684 pass "Redirect stdout to stderr"
685 else
686 fail "Redirect stdout to stderr" "test" "$result"
687 fi
688
689 # Close stdout after redirecting to stderr
690 # Note: matches bash - >&2 redirects stdout to stderr, then 1>&- closes fd1
691 # The write to closed fd1 fails with "Bad file descriptor"
692 result=$("$SHELL_BIN" -c 'echo test >&2 1>&-' 2>&1)
693 if echo "$result" | grep -q "Bad file descriptor"; then
694 pass "Close stdout after redirect (matches bash - write error)"
695 else
696 fail "Close stdout after redirect (matches bash - write error)" "Bad file descriptor error" "$result"
697 fi
698
699 # =====================================
700 section "455. INPUT REDIRECTION VARIATIONS"
701 # =====================================
702
703 # Input from file
704 echo "content" > "$TEST_DIR/input_test.txt"
705 result=$("$SHELL_BIN" -c 'cat < "'"$TEST_DIR"'/input_test.txt"' 2>&1)
706 if [ "$result" = "content" ]; then
707 pass "Input redirection from file"
708 else
709 fail "Input redirection from file" "content" "$result"
710 fi
711
712 # Input from /dev/null
713 result=$("$SHELL_BIN" -c 'cat < /dev/null | wc -c' 2>&1)
714 if [ "$result" -eq 0 ] 2>/dev/null; then
715 pass "Input from /dev/null is empty"
716 else
717 fail "Input from /dev/null is empty" "0" "$result"
718 fi
719
720 # =====================================
721 section "456. OUTPUT TO /dev/null"
722 # =====================================
723
724 # Stdout to /dev/null
725 result=$("$SHELL_BIN" -c 'echo hidden > /dev/null; echo visible' 2>&1)
726 if [ "$result" = "visible" ]; then
727 pass "Stdout to /dev/null suppresses output"
728 else
729 fail "Stdout to /dev/null suppresses output" "visible" "$result"
730 fi
731
732 # Stderr to /dev/null
733 result=$("$SHELL_BIN" -c 'ls /nonexistent 2>/dev/null; echo done' 2>&1)
734 if [ "$result" = "done" ]; then
735 pass "Stderr to /dev/null suppresses errors"
736 else
737 fail "Stderr to /dev/null suppresses errors" "done" "$result"
738 fi
739
740 # Both to /dev/null
741 result=$("$SHELL_BIN" -c 'ls /nonexistent >/dev/null 2>&1; echo status=$?' 2>&1)
742 if echo "$result" | grep -q "status="; then
743 pass "Both stdout and stderr to /dev/null"
744 else
745 fail "Both stdout and stderr to /dev/null"
746 fi
747
748 # =====================================
749 section "457. NOCLOBBER BEHAVIOR"
750 # =====================================
751
752 # Normal overwrite
753 echo "old" > "$TEST_DIR/clobber_test.txt"
754 result=$("$SHELL_BIN" -c 'echo new > "'"$TEST_DIR"'/clobber_test.txt"; cat "'"$TEST_DIR"'/clobber_test.txt"' 2>&1)
755 if [ "$result" = "new" ]; then
756 pass "Normal > overwrites file"
757 else
758 fail "Normal > overwrites file" "new" "$result"
759 fi
760
761 # =====================================
762 section "458. COMPOUND REDIRECTIONS"
763 # =====================================
764
765 # Redirect in if statement
766 result=$("$SHELL_BIN" -c 'if true; then echo yes; fi > "'"$TEST_DIR"'/if_redir.txt"; cat "'"$TEST_DIR"'/if_redir.txt"' 2>&1)
767 if [ "$result" = "yes" ]; then
768 pass "Redirect if statement output"
769 else
770 fail "Redirect if statement output" "yes" "$result"
771 fi
772
773 # Redirect in while loop
774 result=$("$SHELL_BIN" -c 'i=0; while [ $i -lt 3 ]; do echo $i; i=$((i+1)); done > "'"$TEST_DIR"'/while_redir.txt"; wc -l < "'"$TEST_DIR"'/while_redir.txt"' 2>&1)
775 if [ "$result" -eq 3 ] 2>/dev/null; then
776 pass "Redirect while loop output"
777 else
778 fail "Redirect while loop output" "3" "$result"
779 fi
780
781 # Redirect in for loop
782 result=$("$SHELL_BIN" -c 'for x in a b c; do echo $x; done > "'"$TEST_DIR"'/for_redir.txt"; wc -l < "'"$TEST_DIR"'/for_redir.txt"' 2>&1)
783 if [ "$result" -eq 3 ] 2>/dev/null; then
784 pass "Redirect for loop output"
785 else
786 fail "Redirect for loop output" "3" "$result"
787 fi
788
789 # =====================================
790 section "459. PIPELINE EXIT STATUS"
791 # =====================================
792
793 # Last command determines exit
794 result=$("$SHELL_BIN" -c 'true | false; echo $?' 2>&1)
795 if [ "$result" = "1" ]; then
796 pass "Pipeline exit from last command (false)"
797 else
798 fail "Pipeline exit from last command (false)" "1" "$result"
799 fi
800
801 result=$("$SHELL_BIN" -c 'false | true; echo $?' 2>&1)
802 if [ "$result" = "0" ]; then
803 pass "Pipeline exit from last command (true)"
804 else
805 fail "Pipeline exit from last command (true)" "0" "$result"
806 fi
807
808 # =====================================
809 section "460. MULTIPLE REDIRECTIONS"
810 # =====================================
811
812 # Both input and output
813 echo "input" > "$TEST_DIR/multi_in.txt"
814 result=$("$SHELL_BIN" -c 'cat < "'"$TEST_DIR"'/multi_in.txt" > "'"$TEST_DIR"'/multi_out.txt"; cat "'"$TEST_DIR"'/multi_out.txt"' 2>&1)
815 if [ "$result" = "input" ]; then
816 pass "Both input and output redirect"
817 else
818 fail "Both input and output redirect" "input" "$result"
819 fi
820
821 # Order of redirections
822 result=$("$SHELL_BIN" -c 'echo test > "'"$TEST_DIR"'/order1.txt" 2>&1; cat "'"$TEST_DIR"'/order1.txt"' 2>&1)
823 if [ "$result" = "test" ]; then
824 pass "Redirect order: > then 2>&1"
825 else
826 fail "Redirect order: > then 2>&1" "test" "$result"
827 fi
828
829 # =====================================
830 section "461. PIPELINE WITH SUBSHELL"
831 # =====================================
832
833 # Subshell in pipeline
834 result=$("$SHELL_BIN" -c '(echo a; echo b) | wc -l' 2>&1)
835 if [ "$result" -eq 2 ] 2>/dev/null; then
836 pass "Subshell in pipeline"
837 else
838 fail "Subshell in pipeline" "2" "$result"
839 fi
840
841 # Brace group in pipeline
842 result=$("$SHELL_BIN" -c '{ echo x; echo y; } | wc -l' 2>&1)
843 if [ "$result" -eq 2 ] 2>/dev/null; then
844 pass "Brace group in pipeline"
845 else
846 fail "Brace group in pipeline" "2" "$result"
847 fi
848
849 # =====================================
850 section "462. REDIRECT AND PIPELINE COMBO"
851 # =====================================
852
853 # Pipeline with final redirect
854 result=$("$SHELL_BIN" -c 'echo test | cat > "'"$TEST_DIR"'/pipe_redir.txt"; cat "'"$TEST_DIR"'/pipe_redir.txt"' 2>&1)
855 if [ "$result" = "test" ]; then
856 pass "Pipeline with final redirect"
857 else
858 fail "Pipeline with final redirect" "test" "$result"
859 fi
860
861 # Redirect within pipeline
862 result=$("$SHELL_BIN" -c 'echo test 2>/dev/null | cat' 2>&1)
863 if [ "$result" = "test" ]; then
864 pass "Redirect within pipeline stage"
865 else
866 fail "Redirect within pipeline stage" "test" "$result"
867 fi
868
869 # =====================================
870 section "463. HERE STRING ALTERNATIVES"
871 # =====================================
872
873 # Echo pipe as here-string alternative
874 result=$("$SHELL_BIN" -c 'echo "test" | cat' 2>&1)
875 if [ "$result" = "test" ]; then
876 pass "Echo pipe as here-string alternative"
877 else
878 fail "Echo pipe as here-string alternative" "test" "$result"
879 fi
880
881 # Printf pipe
882 result=$("$SHELL_BIN" -c 'printf "%s" "test" | cat' 2>&1)
883 if [ "$result" = "test" ]; then
884 pass "Printf pipe"
885 else
886 fail "Printf pipe" "test" "$result"
887 fi
888
889 # =====================================
890 section "464. REDIRECTION EDGE CASES"
891 # =====================================
892
893 # Redirect to same file
894 result=$("$SHELL_BIN" -c 'echo original > "'"$TEST_DIR"'/same.txt"; echo new > "'"$TEST_DIR"'/same.txt"; cat "'"$TEST_DIR"'/same.txt"' 2>&1)
895 if [ "$result" = "new" ]; then
896 pass "Multiple redirects to same file"
897 else
898 fail "Multiple redirects to same file" "new" "$result"
899 fi
900
901 # Empty redirect (creates empty file)
902 rm -f "$TEST_DIR/empty_redir.txt"
903 result=$("$SHELL_BIN" -c '> "'"$TEST_DIR"'/empty_redir.txt"; [ -f "'"$TEST_DIR"'/empty_redir.txt" ] && echo exists' 2>&1)
904 if [ "$result" = "exists" ]; then
905 pass "Empty redirect creates file"
906 else
907 fail "Empty redirect creates file" "exists" "$result"
908 fi
909
910 # =====================================
911 section "465. TPYE WITH REDIRECTION"
912 # =====================================
913
914 # Pipeline preserves data integrity
915 result=$("$SHELL_BIN" -c 'echo "hello world" | cat | cat | cat' 2>&1)
916 if [ "$result" = "hello world" ]; then
917 pass "Pipeline preserves data integrity"
918 else
919 fail "Pipeline preserves data integrity" "hello world" "$result"
920 fi
921
922 # Multiple pipes with tr
923 result=$("$SHELL_BIN" -c 'echo abc | tr a X | tr b Y | tr c Z' 2>&1)
924 if [ "$result" = "XYZ" ]; then
925 pass "Multiple pipes with tr"
926 else
927 fail "Multiple pipes with tr" "XYZ" "$result"
928 fi
929
930 # =====================================
931 # Summary
932 # =====================================
933 printf "\n"
934 printf "${BLUE}==========================================\n"
935 printf "POSIX Redirection and Pipeline Summary\n"
936 printf "==========================================${NC}\n"
937 printf "Passed: ${GREEN}%d${NC}\n" "$PASSED"
938 printf "Failed: ${RED}%d${NC}\n" "$FAILED"
939 printf "Skipped: ${YELLOW}%d${NC}\n" "$SKIPPED"
940 printf "Total: %d\n" "$((PASSED + FAILED + SKIPPED))"
941
942 if [ -n "$FAILED_TESTS_LIST" ]; then
943 printf "\n${RED}Failed tests:${NC}\n"
944 printf "%b" "$FAILED_TESTS_LIST"
945 fi
946
947 if [ "$FAILED" -gt 0 ]; then
948 exit 1
949 fi
950 exit 0