Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
att
GitHub Repository: att/ast
Path: blob/master/src/cmd/ksh93/tests/subshell.sh
1810 views
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 <[email protected]> #
18
# #
19
########################################################################
20
function err_exit
21
{
22
print -u$Error_fd -n "\t"
23
print -u$Error_fd -r ${Command}[$1]: "${@:2}"
24
(( Errors+=1 ))
25
}
26
alias err_exit='err_exit $LINENO'
27
28
Command=${0##*/}
29
integer Errors=0 Error_fd=2
30
31
tmp=$(mktemp -dt) || { err_exit mktemp -dt failed; exit 1; }
32
trap "cd /; rm -rf $tmp" EXIT
33
34
builtin getconf
35
bincat=$(PATH=$(getconf PATH) whence -p cat)
36
37
z=()
38
z.foo=( [one]=hello [two]=(x=3 y=4) [three]=hi)
39
z.bar[0]=hello
40
z.bar[2]=world
41
z.bar[1]=(x=4 y=5)
42
val='(
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'
61
z.bar[1]=yesyes
62
[[ ${z.bar[1]} == yesyes ]] || err_exit 'reassign of index array compound variable fails'
63
z.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'
65
eval 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'
71
eval 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
80
x=(
81
foo=( qqq=abc rrr=def)
82
bar=( zzz=no rst=fed)
83
)
84
eval 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'
92
unset l
93
(
94
l=( a=1 b="BE" )
95
)
96
[[ ${l+foo} != foo ]] || err_exit 'l should be unset'
97
98
Error_fd=9
99
eval "exec $Error_fd>&2 2>/dev/null"
100
101
TEST_notfound=notfound
102
while whence $TEST_notfound >/dev/null 2>&1
103
do TEST_notfound=notfound-$RANDOM
104
done
105
106
107
integer BS=1024 nb=64 ss=60 bs no
108
for bs in $BS 1
109
do $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}"
120
done
121
# this time with redirection on the trailing command
122
for bs in $BS 1
123
do $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}"
135
done
136
137
# exercise command substitutuion trailing newline logic w.r.t. pipe vs. tmp file io
138
139
set -- \
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
162
export TEST_fork TEST_unset
163
164
while (( $# >= 3 ))
165
do 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
182
done
183
184
r=$( ($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
202
for TEST_command in '' $TEST_notfound
203
do 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
226
done
227
228
$SHELL -c '( autoload xxxxx);print -n' || err_exit 'autoloaded functions in subshells can cause failure'
229
foo=$($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
235
err=$(
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
259
exp=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
'
268
got=$?
269
[[ $got == $exp ]] || err_exit "getopts --man runtime callout with nonzero exit terminates shell -- expected '$exp', got '$got'"
270
exp=ok
271
got=$($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 #
282
set -- \
283
'$(' ')' \
284
'${ ' '; }' \
285
'$(ulimit -c 0; ' ')' \
286
'$( (' ') )' \
287
'${ (' '); }' \
288
'`' '`' \
289
'`(' ')`' \
290
'`ulimit -c 0; ' '`' \
291
# end of table #
292
exp=ok
293
testcase[1]='
294
if %sexpr "NOMATCH" : ".*Z" >/dev/null%s
295
then print error
296
else print ok
297
fi
298
exit %s
299
'
300
testcase[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
'
311
while (( $# >= 2 ))
312
do 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
325
done
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
334
SUB=(
335
( BEG='$( ' END=' )' )
336
( BEG='${ ' END='; }' )
337
)
338
CAT=( cat $bincat )
339
INS=( "" "builtin cat; " "builtin -d cat $bincat; " ": > /dev/null; " )
340
APP=( "" "; :" )
341
TST=(
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
349
if cat /dev/fd/3 3</dev/null >/dev/null 2>&1 || whence mkfifo > /dev/null
350
then T=${#TST[@]}
351
TST[T].CMD='$cat <(print foo)'
352
TST[T].EXP=3
353
fi
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
359
buf=$'1234567890abcdef'
360
lin=$'\n1234567890abcde'
361
for ((i=0; i<5; i++))
362
do buf=$buf$buf
363
lin=$lin$lin
364
done
365
print -n "$buf" > $tmp/buf
366
print -n "$lin" > $tmp/lin
367
368
unset SKIP
369
for ((n=1024; n<=1024*1024; n*=2))
370
do 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
418
done
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
434
exp=''
435
if [[ ! $got ]] && (( h ))
436
then got=HUNG
437
fi
438
if [[ $got != $exp ]]
439
then err_exit "eval '$cmd' failed -- expected '$exp', got '$got'"
440
fi
441
442
float t1=$SECONDS
443
sleep=$(whence -p sleep)
444
if [[ $sleep ]]
445
then
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))
449
fi
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
453
exp=HOME=$HOME
454
( HOME=/bin/sh )
455
got=$(env | grep ^HOME=)
456
[[ $got == "$exp" ]] || err_exit "( HOME=/bin/sh ) cleanup failed -- expected '$exp', got '$got'"
457
458
cmd='echo $((case x in x)echo ok;esac);:)'
459
exp=ok
460
got=$($SHELL -c "$cmd" 2>&1)
461
[[ $got == "$exp" ]] || err_exit "'$cmd' failed -- expected '$exp', got '$got'"
462
463
cmd='eval "for i in 1 2; do eval /bin/echo x; done"'
464
exp=$'x\nx'
465
got=$($SHELL -c "$cmd")
466
if [[ $got != "$exp" ]]
467
then EXP=$(printf %q "$exp")
468
GOT=$(printf %q "$got")
469
err_exit "'$cmd' failed -- expected $EXP, got $GOT"
470
fi
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
482
integer i
483
for ((i=1; i < 1000; i++))
484
do typeset foo$i=$i
485
done
486
{
487
: $( (ac_space=' '; set | grep ac_space) 2>&1)
488
} < /dev/null | cat > /dev/null &
489
sleep 1.5
490
if kill -KILL $! 2> /dev/null
491
then err_exit 'process timed out with hung comsub'
492
fi
493
wait $! 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
499
src=$'true 2>&1\n: $(true | true)\n: $(true | true)\n: $(true | true)\n'$(whence -p true)
500
exp=ok
501
got=$( $SHELL -c "(eval '$src'); echo $exp" )
502
[[ $got == "$exp" ]] || err_exit 'subshell eval of pipeline clobbers stdout'
503
504
x=$( { time $SHELL -c date >| /dev/null;} 2>&1)
505
[[ $x == *real*user*sys* ]] || err_exit 'time { ...;} 2>&1 in $(...) fails'
506
507
x=$($SHELL -c '( function fx { export X=123; } ; fx; ); echo $X')
508
[[ $x == 123 ]] && err_exit 'global variables set from with functions inside a
509
subshell can leave side effects in parent shell'
510
511
date=$(whence -p date)
512
err() { return $1; }
513
( err 12 ) & pid=$!
514
: $( $date)
515
wait $pid
516
[[ $? == 12 ]] || err_exit 'exit status from subshells not being preserved'
517
518
if cat /dev/fd/3 3</dev/null >/dev/null 2>&1 || whence mkfifo > /dev/null
519
then x="$(sed 's/^/Hello /' <(print "Fred" | sort))"
520
[[ $x == 'Hello Fred' ]] || err_exit "process substitution of pipeline in command substitution not working"
521
fi
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"
536
EOF
537
} & pid=$!
538
$SHELL -c "{ sleep 4 && kill $pid ;}" 2> /dev/null
539
(( $? == 0 )) && err_exit 'process has hung'
540
541
{
542
x=$( $SHELL <<- \EOF
543
function func1 { typeset IFS; : $(func2); print END ;}
544
function func2 { IFS="BAR"; }
545
func1
546
func1
547
EOF
548
)
549
} 2> /dev/null
550
[[ $x == $'END\nEND' ]] || err_exit 'bug in save/restore of IFS in subshell'
551
552
true=$(whence -p true)
553
date=$(whence -p date)
554
tmpf=$tmp/foo
555
function fun1
556
{
557
$true
558
cd - >/dev/null 2>&1
559
print -u2 -- "$($date) SUCCESS"
560
}
561
562
print -n $(fun1 2> $tmpf)
563
[[ $(< $tmpf) == *SUCCESS ]] || err_exit 'standard error output lost with command substitution'
564
565
566
tmpfile=$tmp/foo
567
cat > $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
572
unset i
573
if [[ -d /dev/fd ]]
574
then 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
581
fi
582
583
trap USR1 USR1
584
trap 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'
589
trap - USR1 ERR
590
591
( PATH=/bin:/usr/bin
592
dot=$(cat <<-EOF
593
$(ls -d .)
594
EOF
595
) ) & sleep 1
596
if kill -0 $! 2> /dev/null
597
then err_exit 'command substitution containg here-doc with command substitution fails'
598
fi
599
600
printf=$(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 $!
612
EOF
613
614
for f in /dev/stdout /dev/fd/1
615
do 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
618
done
619
620
exit $((Errors<125?Errors:125))
621
622