1######################################################################## 2# # 3# This software is part of the ast package # 4# Copyright (c) 1982-2012 AT&T Intellectual Property # 5# and is licensed under the # 6# Eclipse Public License, Version 1.0 # 7# by AT&T Intellectual Property # 8# # 9# A copy of the License is available at # 10# http://www.eclipse.org/org/documents/epl-v10.html # 11# (with md5 checksum b35adb5213ca9657e911e9befb180842) # 12# # 13# Information and Software Systems Research # 14# AT&T Research # 15# Florham Park NJ # 16# # 17# David Korn <dgk@research.att.com> # 18# # 19######################################################################## 20function err_exit 21{ 22 print -u$Error_fd -n "\t" 23 print -u$Error_fd -r ${Command}[$1]: "${@:2}" 24 (( Errors+=1 )) 25} 26alias err_exit='err_exit $LINENO' 27 28Command=${0##*/} 29integer Errors=0 Error_fd=2 30 31tmp=$(mktemp -dt) || { err_exit mktemp -dt failed; exit 1; } 32trap "cd /; rm -rf $tmp" EXIT 33 34builtin getconf 35bincat=$(PATH=$(getconf PATH) whence -p cat) 36 37z=() 38z.foo=( [one]=hello [two]=(x=3 y=4) [three]=hi) 39z.bar[0]=hello 40z.bar[2]=world 41z.bar[1]=(x=4 y=5) 42val='( 43 typeset -a bar=( 44 [0]=hello 45 [2]=world 46 [1]=( 47 x=4 48 y=5 49 ) 50 ) 51 typeset -A foo=( 52 [one]=hello 53 [three]=hi 54 [two]=( 55 x=3 56 y=4 57 ) 58 ) 59)' 60[[ $z == "$val" ]] || err_exit 'compound variable with mixed arrays not working' 61z.bar[1]=yesyes 62[[ ${z.bar[1]} == yesyes ]] || err_exit 'reassign of index array compound variable fails' 63z.bar[1]=(x=12 y=5) 64[[ ${z.bar[1]} == $'(\n\tx=12\n\ty=5\n)' ]] || err_exit 'reassign array simple to compound variable fails' 65eval val="$z" 66( 67 z.foo[three]=good 68 [[ ${z.foo[three]} == good ]] || err_exit 'associative array assignment in subshell not working' 69) 70[[ $z == "$val" ]] || err_exit 'compound variable changes after associative array assignment' 71eval val="$z" 72( 73 z.foo[two]=ok 74 [[ ${z.foo[two]} == ok ]] || err_exit 'associative array assignment to compound variable in subshell not working' 75 z.bar[1]=yes 76 [[ ${z.bar[1]} == yes ]] || err_exit 'index array assignment to compound variable in subshell not working' 77) 78[[ $z == "$val" ]] || err_exit 'compound variable changes after associative array assignment' 79 80x=( 81 foo=( qqq=abc rrr=def) 82 bar=( zzz=no rst=fed) 83) 84eval val="$x" 85( 86 unset x.foo 87 [[ ${x.foo.qqq} ]] && err_exit 'x.foo.qqq should be unset' 88 x.foo=good 89 [[ ${x.foo} == good ]] || err_exit 'x.foo should be good' 90) 91[[ $x == "$val" ]] || err_exit 'compound variable changes after unset leaves' 92unset l 93( 94 l=( a=1 b="BE" ) 95) 96[[ ${l+foo} != foo ]] || err_exit 'l should be unset' 97 98Error_fd=9 99eval "exec $Error_fd>&2 2>/dev/null" 100 101TEST_notfound=notfound 102while whence $TEST_notfound >/dev/null 2>&1 103do TEST_notfound=notfound-$RANDOM 104done 105 106 107integer BS=1024 nb=64 ss=60 bs no 108for bs in $BS 1 109do $SHELL -c ' 110 { 111 sleep '$ss' 112 kill -KILL $$ 113 } & 114 set -- $(printf %.'$(($BS*$nb))'c x | dd bs='$bs') 115 print ${#1} 116 kill $! 117 ' > $tmp/sub 2>/dev/null 118 no=$(<$tmp/sub) 119 (( no == (BS * nb) )) || err_exit "shell hangs on command substitution output size >= $BS*$nb with write size $bs -- expected $((BS*nb)), got ${no:-0}" 120done 121# this time with redirection on the trailing command 122for bs in $BS 1 123do $SHELL -c ' 124 { 125 sleep 2 126 sleep '$ss' 127 kill -KILL $$ 128 } & 129 set -- $(printf %.'$(($BS*$nb))'c x | dd bs='$bs' 2>/dev/null) 130 print ${#1} 131 kill $! 132 ' > $tmp/sub 2>/dev/null 133 no=$(<$tmp/sub) 134 (( no == (BS * nb) )) || err_exit "shell hangs on command substitution output size >= $BS*$nb with write size $bs and trailing redirection -- expected $((BS*nb)), got ${no:-0}" 135done 136 137# exercise command substitutuion trailing newline logic w.r.t. pipe vs. tmp file io 138 139set -- \ 140 'post-line print' \ 141 '$TEST_unset; ($TEST_fork; print 1); print' \ 142 1 \ 143 'pre-line print' \ 144 '$TEST_unset; ($TEST_fork; print); print 1' \ 145 $'\n1' \ 146 'multiple pre-line print' \ 147 '$TEST_unset; ($TEST_fork; print); print; ($TEST_fork; print 1); print' \ 148 $'\n\n1' \ 149 'multiple post-line print' \ 150 '$TEST_unset; ($TEST_fork; print 1); print; ($TEST_fork; print); print' \ 151 1 \ 152 'intermediate print' \ 153 '$TEST_unset; ($TEST_fork; print 1); print; ($TEST_fork; print 2); print' \ 154 $'1\n\n2' \ 155 'simple variable' \ 156 '$TEST_unset; ($TEST_fork; l=2; print "$l"); print $l' \ 157 2 \ 158 'compound variable' \ 159 '$TEST_unset; ($TEST_fork; l=(a=2 b="BE"); print "$l"); print $l' \ 160 $'(\n\ta=2\n\tb=BE\n)' \ 161 162export TEST_fork TEST_unset 163 164while (( $# >= 3 )) 165do txt=$1 166 cmd=$2 167 exp=$3 168 shift 3 169 for TEST_unset in '' 'unset var' 170 do for TEST_fork in '' 'ulimit -c 0' 171 do for TEST_shell in "eval" "$SHELL -c" 172 do if ! got=$($TEST_shell "$cmd") 173 then err_exit "${TEST_shell/*-c/\$SHELL -c} ${TEST_unset:+unset }${TEST_fork:+fork }$txt print failed" 174 elif [[ "$got" != "$exp" ]] 175 then EXP=$(printf %q "$exp") 176 GOT=$(printf %q "$got") 177 err_exit "${TEST_shell/*-c/\$SHELL -c} ${TEST_unset:+unset }${TEST_fork:+fork }$txt command substitution failed -- expected $EXP, got $GOT" 178 fi 179 done 180 done 181 done 182done 183 184r=$( ($SHELL -c ' 185 { 186 sleep 32 187 kill -KILL $$ 188 } & 189 for v in $(set | sed "s/=.*//") 190 do command unset $v 191 done 192 typeset -Z5 I 193 for ((I = 0; I < 1024; I++)) 194 do eval A$I=1234567890 195 done 196 a=$(set 2>&1) 197 print ok 198 kill -KILL $! 199') 2>/dev/null) 200[[ $r == ok ]] || err_exit "large subshell command substitution hangs" 201 202for TEST_command in '' $TEST_notfound 203do for TEST_exec in '' 'exec' 204 do for TEST_fork in '' 'ulimit -c 0;' 205 do for TEST_redirect in '' '>/dev/null' 206 do for TEST_substitute in '' ': $' 207 do 208 209 TEST_test="$TEST_substitute($TEST_fork $TEST_exec $TEST_command $TEST_redirect)" 210 [[ $TEST_test == '('*([[:space:]])')' ]] && continue 211 r=$($SHELL -c ' 212 { 213 sleep 2 214 kill -KILL $$ 215 } & 216 '"$TEST_test"' 217 kill $! 218 print ok 219 ') 220 [[ $r == ok ]] || err_exit "shell hangs on $TEST_test" 221 222 done 223 done 224 done 225 done 226done 227 228$SHELL -c '( autoload xxxxx);print -n' || err_exit 'autoloaded functions in subshells can cause failure' 229foo=$($SHELL <<- ++EOF++ 230 (trap 'print bar' EXIT;print -n foo) 231 ++EOF++ 232) 233[[ $foo == foobar ]] || err_exit 'trap on exit when last commands is subshell is not triggered' 234 235err=$( 236 $SHELL 2>&1 <<- \EOF 237 date=$(whence -p date) 238 function foo 239 { 240 x=$( $date > /dev/null 2>&1 ;:) 241 } 242 # consume almost all fds to push the test to the fd limit # 243 integer max=$(ulimit --nofile) 244 (( max -= 6 )) 245 for ((i=20; i < max; i++)) 246 do exec {i}>&1 247 done 248 for ((i=0; i < 20; i++)) 249 do y=$(foo) 250 done 251 EOF 252) || { 253 err=${err%%$'\n'*} 254 err=${err#*:} 255 err=${err##[[:space:]]} 256 err_exit "nested command substitution with redirections failed -- $err" 257} 258 259exp=0 260$SHELL -c $' 261 function foobar 262 { 263 print "hello world" 264 } 265 [[ $(getopts \'[+?X\ffoobar\fX]\' v --man 2>&1) == *"Xhello worldX"* ]] 266 exit '$exp$' 267' 268got=$? 269[[ $got == $exp ]] || err_exit "getopts --man runtime callout with nonzero exit terminates shell -- expected '$exp', got '$got'" 270exp=ok 271got=$($SHELL -c $' 272 function foobar 273 { 274 print "hello world" 275 } 276 [[ $(getopts \'[+?X\ffoobar\fX]\' v --man 2>&1) == *"Xhello worldX"* ]] 277 print '$exp$' 278') 279[[ $got == $exp ]] || err_exit "getopts --man runtime callout with nonzero exit terminates shell -- expected '$exp', got '$got'" 280 281# command substitution variations # 282set -- \ 283 '$(' ')' \ 284 '${ ' '; }' \ 285 '$(ulimit -c 0; ' ')' \ 286 '$( (' ') )' \ 287 '${ (' '); }' \ 288 '`' '`' \ 289 '`(' ')`' \ 290 '`ulimit -c 0; ' '`' \ 291 # end of table # 292exp=ok 293testcase[1]=' 294 if %sexpr "NOMATCH" : ".*Z" >/dev/null%s 295 then print error 296 else print ok 297 fi 298 exit %s 299' 300testcase[2]=' 301 function bar 302 { 303 pipeout=%1$sprintf Ok | tr O o%2$s 304 print $pipeout 305 return 0 306 } 307 foo=%1$sbar%2$s || foo="exit status $?" 308 print $foo 309 exit %3$s 310' 311while (( $# >= 2 )) 312do for ((TEST=1; TEST<=${#testcase[@]}; TEST++)) 313 do body=${testcase[TEST]} 314 for code in 0 2 315 do got=${ printf "$body" "$1" "$2" "$code" | $SHELL 2>&1 } 316 status=$? 317 if (( status != code )) 318 then err_exit "test $TEST '$1...$2 exit $code' failed -- exit status $status, expected $code" 319 elif [[ $got != $exp ]] 320 then err_exit "test $TEST '$1...$2 exit $code' failed -- got '$got', expected '$exp'" 321 fi 322 done 323 done 324 shift 2 325done 326 327# the next tests loop on all combinations of 328# { SUB CAT INS TST APP } X { file-sizes } 329# where the file size starts at 1Ki and doubles up to and including 1Mi 330# 331# the tests and timeouts are done in async subshells to prevent 332# the test harness from hanging 333 334SUB=( 335 ( BEG='$( ' END=' )' ) 336 ( BEG='${ ' END='; }' ) 337) 338CAT=( cat $bincat ) 339INS=( "" "builtin cat; " "builtin -d cat $bincat; " ": > /dev/null; " ) 340APP=( "" "; :" ) 341TST=( 342 ( CMD='print foo | $cat' EXP=3 ) 343 ( CMD='$cat < $tmp/lin' ) 344 ( CMD='cat $tmp/lin | $cat' ) 345 ( CMD='read v < $tmp/buf; print $v' LIM=4*1024 ) 346 ( CMD='cat $tmp/buf | read v; print $v' LIM=4*1024 ) 347) 348 349if cat /dev/fd/3 3</dev/null >/dev/null 2>&1 || whence mkfifo > /dev/null 350then T=${#TST[@]} 351 TST[T].CMD='$cat <(print foo)' 352 TST[T].EXP=3 353fi 354 355# prime the two data files to 512 bytes each 356# $tmp/lin has newlines every 16 bytes and $tmp/buf has no newlines 357# the outer loop doubles the file size at top 358 359buf=$'1234567890abcdef' 360lin=$'\n1234567890abcde' 361for ((i=0; i<5; i++)) 362do buf=$buf$buf 363 lin=$lin$lin 364done 365print -n "$buf" > $tmp/buf 366print -n "$lin" > $tmp/lin 367 368unset SKIP 369for ((n=1024; n<=1024*1024; n*=2)) 370do cat $tmp/buf $tmp/buf > $tmp/tmp 371 mv $tmp/tmp $tmp/buf 372 cat $tmp/lin $tmp/lin > $tmp/tmp 373 mv $tmp/tmp $tmp/lin 374 for ((S=0; S<${#SUB[@]}; S++)) 375 do for ((C=0; C<${#CAT[@]}; C++)) 376 do cat=${CAT[C]} 377 for ((I=0; I<${#INS[@]}; I++)) 378 do for ((A=0; A<${#APP[@]}; A++)) 379 do for ((T=0; T<${#TST[@]}; T++)) 380 do #undent...# 381 382 if [[ ! ${SKIP[S][C][I][A][T]} ]] 383 then eval "{ x=${SUB[S].BEG}${INS[I]}${TST[T].CMD}${APP[A]}${SUB[S].END}; print \${#x}; } >\$tmp/out &" 384 m=$! 385 { sleep 4; kill -9 $m; } & 386 k=$! 387 wait $m 388 h=$? 389 kill -9 $k 390 wait $k 391 got=$(<$tmp/out) 392 if [[ ! $got ]] && (( h )) 393 then got=HUNG 394 fi 395 if [[ ${TST[T].EXP} ]] 396 then exp=${TST[T].EXP} 397 else exp=$n 398 fi 399 if [[ $got != $exp ]] 400 then # on failure skip similar tests on larger files sizes # 401 SKIP[S][C][I][A][T]=1 402 siz=$(printf $'%#i' $exp) 403 cmd=${TST[T].CMD//\$cat/$cat} 404 cmd=${cmd//\$tmp\/buf/$siz.buf} 405 cmd=${cmd//\$tmp\/lin/$siz.lin} 406 err_exit "'x=${SUB[S].BEG}${INS[I]}${cmd}${APP[A]}${SUB[S].END} && print \${#x}' failed -- expected '$exp', got '$got'" 407 elif [[ ${TST[T].EXP} ]] || (( TST[T].LIM >= n )) 408 then SKIP[S][C][I][A][T]=1 409 fi 410 fi 411 412 #...indent# 413 done 414 done 415 done 416 done 417 done 418done 419 420# specifics -- there's more? 421 422{ 423 cmd='{ exec 5>/dev/null; print "$(eval ls -d . 2>&1 1>&5)"; } >$tmp/out &' 424 eval $cmd 425 m=$! 426 { sleep 4; kill -9 $m; } & 427 k=$! 428 wait $m 429 h=$? 430 kill -9 $k 431 wait $k 432 got=$(<$tmp/out) 433} 2>/dev/null 434exp='' 435if [[ ! $got ]] && (( h )) 436then got=HUNG 437fi 438if [[ $got != $exp ]] 439then err_exit "eval '$cmd' failed -- expected '$exp', got '$got'" 440fi 441 442float t1=$SECONDS 443sleep=$(whence -p sleep) 444if [[ $sleep ]] 445then 446 $SHELL -c "( $sleep 5 </dev/null >/dev/null 2>&1 & );exit 0" | cat 447 (( (SECONDS-t1) > 4 )) && err_exit '/bin/sleep& in subshell hanging' 448 ((t1=SECONDS)) 449fi 450$SHELL -c '( sleep 5 </dev/null >/dev/null 2>&1 & );exit 0' | cat 451(( (SECONDS-t1) > 4 )) && err_exit 'sleep& in subshell hanging' 452 453exp=HOME=$HOME 454( HOME=/bin/sh ) 455got=$(env | grep ^HOME=) 456[[ $got == "$exp" ]] || err_exit "( HOME=/bin/sh ) cleanup failed -- expected '$exp', got '$got'" 457 458cmd='echo $((case x in x)echo ok;esac);:)' 459exp=ok 460got=$($SHELL -c "$cmd" 2>&1) 461[[ $got == "$exp" ]] || err_exit "'$cmd' failed -- expected '$exp', got '$got'" 462 463cmd='eval "for i in 1 2; do eval /bin/echo x; done"' 464exp=$'x\nx' 465got=$($SHELL -c "$cmd") 466if [[ $got != "$exp" ]] 467then EXP=$(printf %q "$exp") 468 GOT=$(printf %q "$got") 469 err_exit "'$cmd' failed -- expected $EXP, got $GOT" 470fi 471 472( 473$SHELL -c 'sleep 20 & pid=$!; { x=$( ( seq 60000 ) );kill -9 $pid;}&;wait $pid' 474) 2> /dev/null 475(( $? )) || err_exit 'nested command substitution with large output hangs' 476 477(.sh.foo=foobar) 478[[ ${.sh.foo} == foobar ]] && err_exit '.sh subvariables in subshells remain set' 479[[ $($SHELL -c 'print 1 | : "$(/bin/cat <(/bin/cat))"') ]] && err_exit 'process substitution not working correctly in subshells' 480 481# config hang bug 482integer i 483for ((i=1; i < 1000; i++)) 484do typeset foo$i=$i 485done 486{ 487 : $( (ac_space=' '; set | grep ac_space) 2>&1) 488} < /dev/null | cat > /dev/null & 489sleep 1.5 490if kill -KILL $! 2> /dev/null 491then err_exit 'process timed out with hung comsub' 492fi 493wait $! 2> /dev/null 494(( $? > 128 )) && err_exit 'incorrect exit status with comsub' 495 496$SHELL 2> /dev/null -c '[[ ${ print foo },${ print bar } == foo,bar ]]' || err_exit '${ print foo },${ print bar } not working' 497$SHELL 2> /dev/null -c '[[ ${ print foo; },${ print bar } == foo,bar ]]' || err_exit '${ print foo; },${ print bar } not working' 498 499src=$'true 2>&1\n: $(true | true)\n: $(true | true)\n: $(true | true)\n'$(whence -p true) 500exp=ok 501got=$( $SHELL -c "(eval '$src'); echo $exp" ) 502[[ $got == "$exp" ]] || err_exit 'subshell eval of pipeline clobbers stdout' 503 504x=$( { time $SHELL -c date >| /dev/null;} 2>&1) 505[[ $x == *real*user*sys* ]] || err_exit 'time { ...;} 2>&1 in $(...) fails' 506 507x=$($SHELL -c '( function fx { export X=123; } ; fx; ); echo $X') 508[[ $x == 123 ]] && err_exit 'global variables set from with functions inside a 509subshell can leave side effects in parent shell' 510 511date=$(whence -p date) 512err() { return $1; } 513( err 12 ) & pid=$! 514: $( $date) 515wait $pid 516[[ $? == 12 ]] || err_exit 'exit status from subshells not being preserved' 517 518if cat /dev/fd/3 3</dev/null >/dev/null 2>&1 || whence mkfifo > /dev/null 519then x="$(sed 's/^/Hello /' <(print "Fred" | sort))" 520 [[ $x == 'Hello Fred' ]] || err_exit "process substitution of pipeline in command substitution not working" 521fi 522 523{ 524$SHELL <<- \EOF 525 function foo 526 { 527 integer i 528 print -u2 foobar 529 for ((i=0; i < 8000; i++)) 530 do print abcdefghijk 531 done 532 print -u2 done 533 } 534 out=$(eval "foo | cat" 2>&1) 535 (( ${#out} == 96011 )) || err_exit "\${#out} is ${#out} should be 96011" 536EOF 537} & pid=$! 538$SHELL -c "{ sleep 4 && kill $pid ;}" 2> /dev/null 539(( $? == 0 )) && err_exit 'process has hung' 540 541{ 542x=$( $SHELL <<- \EOF 543 function func1 { typeset IFS; : $(func2); print END ;} 544 function func2 { IFS="BAR"; } 545 func1 546 func1 547EOF 548) 549} 2> /dev/null 550[[ $x == $'END\nEND' ]] || err_exit 'bug in save/restore of IFS in subshell' 551 552true=$(whence -p true) 553date=$(whence -p date) 554tmpf=$tmp/foo 555function fun1 556{ 557 $true 558 cd - >/dev/null 2>&1 559 print -u2 -- "$($date) SUCCESS" 560} 561 562print -n $(fun1 2> $tmpf) 563[[ $(< $tmpf) == *SUCCESS ]] || err_exit 'standard error output lost with command substitution' 564 565 566tmpfile=$tmp/foo 567cat > $tmpfile <<-\EOF 568 $SHELL -c 'function g { IFS= ;};function f { typeset IFS;(g);: $V;};f;f' 569 EOF 570$SHELL 2> /dev/null "$tmpfile" || err_exit 'IFS in subshell causes core dump' 571 572unset i 573if [[ -d /dev/fd ]] 574then integer i 575 for ((i=11; i < 29; i++)) 576 do if ! [[ -r /dev/fd/$i || -w /dev/fd/$i ]] 577 then a=$($SHELL -c "[[ -r /dev/fd/$i || -w /dev/fd/$i ]]") 578 (( $? )) || err_exit "file descriptor $i not close on exec" 579 fi 580 done 581fi 582 583trap USR1 USR1 584trap ERR ERR 585[[ $(trap -p USR1) == USR1 ]] || err_exit 'trap -p USR1 in subshell not working' 586[[ $(trap -p ERR) == ERR ]] || err_exit 'trap -p ERR in subshell not working' 587[[ $(trap -p) == *USR* ]] || err_exit 'trap -p in subshell does not contain USR' 588[[ $(trap -p) == *ERR* ]] || err_exit 'trap -p in subshell does not contain ERR' 589trap - USR1 ERR 590 591( PATH=/bin:/usr/bin 592dot=$(cat <<-EOF 593 $(ls -d .) 594 EOF 595) ) & sleep 1 596if kill -0 $! 2> /dev/null 597then err_exit 'command substitution containg here-doc with command substitution fails' 598fi 599 600printf=$(whence -p printf) 601[[ $( { trap "echo foobar" EXIT; ( $printf ""); } & wait) == foobar ]] || err_exit 'exit trap not being invoked' 602 603$SHELL 2> /dev/null -c '( PATH=/bin; set -o restricted) ; exit 0' || err_exit 'restoring PATH when a subshell enables restricted exits not working' 604 605$SHELL <<- \EOF 606 wc=$(whence wc) head=$(whence head) 607 print > /dev/null $( ( $head -c 1 /dev/zero | ( $wc -c) 3>&1 ) 3>&1) & 608 pid=$! 609 sleep 2 610 kill -9 $! 2> /dev/null && err_exit '/dev/zero in command substitution hangs' 611 wait $! 612EOF 613 614for f in /dev/stdout /dev/fd/1 615do if [[ -e $f ]] 616 then $SHELL -c "x=\$(command -p tee $f </dev/null 2>/dev/null)" || err_exit "$f in command substitution fails" 617 fi 618done 619 620# ======================================== 621# Test that closing file descriptors don't affect capturing the output of a 622# subshell. Regression test for issue #198. 623tmpfile=$(mktemp) 624expected='return value' 625 626function get_value { 627 case=$1 628 (( case >= 1 )) && exec 3< $tmpfile 629 (( case >= 2 )) && exec 4< $tmpfile 630 (( case >= 3 )) && exec 6< $tmpfile 631 632 # To trigger the bug we have to spawn an external command. Why is a 633 # mystery but not really relevant. 634 $(whence -p true) 635 636 (( case >= 1 )) && exec 3<&- 637 (( case >= 2 )) && exec 4<&- 638 (( case >= 3 )) && exec 6<&- 639 640 print $expected 641} 642 643actual=$(get_value 0) 644if [[ $actual != $expected ]] 645then 646 err_exit -u2 "failed to capture subshell output when closing fd: case 0" 647fi 648 649actual=$(get_value 1) 650if [[ $actual != $expected ]] 651then 652 err_exit -u2 "failed to capture subshell output when closing fd: case 1" 653fi 654 655actual=$(get_value 2) 656if [[ $actual != $expected ]] 657then 658 err_exit -u2 "failed to capture subshell output when closing fd: case 2" 659fi 660 661actual=$(get_value 3) 662if [[ $actual != $expected ]] 663then 664 err_exit -u2 "failed to capture subshell output when closing fd: case 3" 665fi 666 667rm $tmpfile 668 669exit $((Errors<125?Errors:125)) 670