######################################################################## # # # This software is part of the ast package # # Copyright (c) 1982-2012 AT&T Intellectual Property # # and is licensed under the # # Eclipse Public License, Version 1.0 # # by AT&T Intellectual Property # # # # A copy of the License is available at # # http://www.eclipse.org/org/documents/epl-v10.html # # (with md5 checksum b35adb5213ca9657e911e9befb180842) # # # # Information and Software Systems Research # # AT&T Research # # Florham Park NJ # # # # David Korn # # # ######################################################################## function err_exit { print -u2 -n "\t" print -u2 -r ${Command}[$1]: "${@:2}" let Errors+=1 } alias err_exit='err_exit $LINENO' Command=${0##*/} integer Errors=0 [[ -d $tmp && -w $tmp && $tmp == "$PWD" ]] || { err\_exit "$LINENO" '$tmp not set; run this from shtests. Aborting.'; exit 1; } bincat=$(whence -p cat) binecho=$(whence -p echo) binfalse=$(whence -p false) # make an external 'sleep' command that supports fractional seconds binsleep=$tmp/.sleep.sh # hide to exclude from simple wildcard expansion cat >"$binsleep" <= 2 )) do umask 0 umask $1 g=$(umask) [[ $g == $2 ]] || err_exit "umask 0; umask $1 failed -- expected $2, got $g" shift 2 done umask u=rwx,go=rx || err_exit "umask u=rws,go=rx failed" if [[ $(umask -S) != u=rwx,g=rx,o=rx ]] then err_exit 'umask -S incorrect' fi um=$(umask -S) ( umask 0777; > foobar ) rm -f foobar > foobar [[ -r foobar ]] || err_exit 'umask not being restored after subshell' umask "$um" rm -f foobar # optimizer bug test > foobar for i in 1 2 do print foobar* rm -f foobar done > out if [[ "$( .foo if [[ $(print *) != dir ]] then err_exit 'file expansion leading . not working' fi date > dat1 || err_exit "date > dat1 failed" test -r dat1 || err_exit "dat1 is not readable" x=dat1 cat <$x > dat2 || err_exit "cat < $x > dat2 failed" cat dat1 dat2 | cat | cat | cat > dat3 || err_exit "cat pipe failed" cat > dat4 < dat5 & wait $! set -- dat* if (( $# != 5 )) then err_exit "dat* matches only $# files" fi if (command > foo\\abc) 2> /dev/null then set -- foo* if [[ $1 != 'foo\abc' ]] then err_exit 'foo* does not match foo\abc' fi fi if ( : > TT* && : > TTfoo ) 2>/dev/null then set -- TT* if (( $# < 2 )) then err_exit 'TT* not expanding when file TT* exists' fi fi cd /dev cd ~- || err_exit "cd back failed" cat > $tmp/script <<- ! #! $SHELL print -r -- \$0 ! chmod 755 $tmp/script if [[ $($tmp/script) != "$tmp/script" ]] then err_exit '$0 not correct for #! script' fi bar=foo eval foo=\$bar if [[ $foo != foo ]] then err_exit 'eval foo=\$bar not working' fi bar='foo=foo\ bar' eval $bar if [[ $foo != 'foo bar' ]] then err_exit 'eval foo=\$bar, with bar="foo\ bar" not working' fi cd /tmp cd ../../tmp || err_exit "cd ../../tmp failed" if [[ $PWD != /tmp ]] then err_exit 'cd ../../tmp is not /tmp' fi ( sleep .2; cat < $tmp/foobar & wait $! foobar=$( < $tmp/foobar) if [[ $foobar != foobar ]] then err_exit "$foobar is not foobar" fi { print foo "$binecho" bar print bam } > $tmp/foobar if [[ $( < $tmp/foobar) != $'foo\nbar\nbam' ]] then err_exit "output file pointer not shared correctly" fi cat > $tmp/foobar <<\! print foo "$binecho" bar print bam ! chmod +x $tmp/foobar if [[ $(export binecho; $tmp/foobar) != $'foo\nbar\nbam' ]] then err_exit "script not working" fi if [[ $(export binecho; $tmp/foobar | "$bincat") != $'foo\nbar\nbam' ]] then err_exit "script | cat not working" fi if [[ $(export binecho; $tmp/foobar) != $'foo\nbar\nbam' ]] then err_exit "output file pointer not shared correctly" fi rm -f $tmp/foobar x=$( (print foo) ; (print bar) ) if [[ $x != $'foo\nbar' ]] then err_exit " ( (print foo);(print bar ) failed" fi x=$( ("$binecho" foo) ; (print bar) ) if [[ $x != $'foo\nbar' ]] then err_exit " ( ("$binecho");(print bar ) failed" fi x=$( ("$binecho" foo) ; ("$binecho" bar) ) if [[ $x != $'foo\nbar' ]] then err_exit " ( ("$binecho");("$binecho" bar ) failed" fi cat > $tmp/script <<\! if [[ -p /dev/fd/0 ]] then builtin cat cat - > /dev/null [[ -p /dev/fd/0 ]] && print ok else print no fi ! chmod +x $tmp/script case $( (print) | $tmp/script;:) in ok) ;; no) err_exit "[[ -p /dev/fd/0 ]] fails for standard input pipe" ;; *) err_exit "builtin replaces standard input pipe" ;; esac print 'print $0' > $tmp/script print ". $tmp/script" > $tmp/scriptx chmod +x $tmp/scriptx if [[ $($tmp/scriptx) != $tmp/scriptx ]] then err_exit '$0 not correct for . script' fi cd $tmp || { err_exit "cd $tmp failed"; exit 1; } print ./b > ./a; print ./c > b; print ./d > c; print ./e > d; print "echo \"hello there\"" > e chmod 755 a b c d e x=$(./a) if [[ $x != "hello there" ]] then err_exit "nested scripts failed" fi x=$( (./a) | cat) if [[ $x != "hello there" ]] then err_exit "scripts in subshells fail" fi cd ~- || err_exit "cd back failed" x=$( ("$binecho" foo) 2> /dev/null ) if [[ $x != foo ]] then err_exit "subshell in command substitution fails" fi exec 9>& 1 exec 1>&- x=$(print hello) if [[ $x != hello ]] then err_exit "command substitution with stdout closed failed" fi exec >& 9 x=$(export binecho bincat; cat <<\! | $SHELL "$binecho" | "$bincat" "$binecho" hello ! ) if [[ $x != $'\n'hello ]] then err_exit "$SHELL not working when standard input is a pipe" fi x=$( ("$binecho" hello) 2> /dev/null ) if [[ $x != hello ]] then err_exit "subshell in command substitution with 1 closed fails" fi cat > $tmp/script <<- \! read line 2> /dev/null print done ! if [[ $($SHELL $tmp/script <&-) != done ]] then err_exit "executing script with 0 closed fails" fi trap '' INT cat > $tmp/script <<- \! trap 'print bad' INT kill -s INT $$ print good ! chmod +x $tmp/script if [[ $($SHELL $tmp/script) != good ]] then err_exit "traps ignored by parent not ignored" fi trap - INT cat > $tmp/script <<- \! read line "$bincat" ! if [[ $(export bincat; $SHELL $tmp/script < /dev/null ) == $'foobar\nfoobar' ]] || err_exit 'for loop optimization subshell bug' command exec 3<> /dev/null if cat /dev/fd/3 >/dev/null 2>&1 || whence mkfifo > /dev/null then [[ $($SHELL -c 'cat <(print foo)' 2> /dev/null) == foo ]] || err_exit 'process substitution not working' [[ $($SHELL -c $'tee >(grep \'1$\' > '$tmp/scriptx$') > /dev/null <<- \!!! line0 line1 line2 !!! wait cat '$tmp/scriptx 2> /dev/null) == line1 ]] || err_exit '>() process substitution fails' > $tmp/scriptx [[ $($SHELL -c $' for i in 1 do tee >(grep \'1$\' > '$tmp/scriptx$') > /dev/null <<- \!!! line0 line1 line2 !!! done wait cat '$tmp/scriptx 2>> /dev/null) == line1 ]] || err_exit '>() process substitution fails in for loop' [[ $({ $SHELL -c 'cat <(for i in x y z; do print $i; done)';} 2> /dev/null) == $'x\ny\nz' ]] || err_exit 'process substitution of compound commands not working' fi [[ $($SHELL -r 'command -p :' 2>&1) == *restricted* ]] || err_exit 'command -p not restricted' print cat > $tmp/scriptx chmod +x $tmp/scriptx [[ $($SHELL -c "print foo | $tmp/scriptx ;:" 2> /dev/null ) == foo ]] || err_exit 'piping into script fails' [[ $($SHELL -c 'X=1;print -r -- ${X:=$(expr "a(0)" : '"'a*(\([^)]\))')}'" 2> /dev/null) == 1 ]] || err_exit 'x=1;${x:=$(..."...")} failure' [[ $($SHELL -c 'print -r -- ${X:=$(expr "a(0)" : '"'a*(\([^)]\))')}'" 2> /dev/null) == 0 ]] || err_exit '${x:=$(..."...")} failure' if cat /dev/fd/3 >/dev/null 2>&1 || whence mkfifo > /dev/null then [[ $(cat <(print hello) ) == hello ]] || err_exit "process substitution not working outside for or while loop" $SHELL -c '[[ $(for i in 1;do cat <(print hello);done ) == hello ]]' 2> /dev/null|| err_exit "process substitution not working in for or while loop" fi exec 3> /dev/null print 'print foo "$@"' > $tmp/scriptx [[ $( print "($tmp/scriptx bar)" | $SHELL 2>/dev/null) == 'foo bar' ]] || err_exit 'script pipe to shell fails' print "#! $SHELL" > $tmp/scriptx print 'print -- $0' >> $tmp/scriptx chmod +x $tmp/scriptx [[ $($tmp/scriptx) == $tmp/scriptx ]] || err_exit "\$0 is $0 instead of $tmp/scriptx" cat > $tmp/scriptx <<- \EOF myfilter() { x=$(print ok | cat); print -r -- $SECONDS;} set -o pipefail sleep .3 | myfilter EOF (( $($SHELL $tmp/scriptx) > .2 )) && err_exit 'command substitution causes pipefail option to hang' exec 3<&- ( typeset -r foo=bar) 2> /dev/null || err_exit 'readonly variables set in a subshell cannot unset' $SHELL -c 'x=${ print hello;}; [[ $x == hello ]]' 2> /dev/null || err_exit '${ command;} not supported' $SHELL 2> /dev/null <<- \EOF || err_exit 'multiline ${...} command substitution not supported' x=${ print hello } [[ $x == hello ]] EOF $SHELL 2> /dev/null <<- \EOF || err_exit '${...} command substitution with side effects not supported ' y=bye x=${ y=hello print hello } [[ $y == $x ]] EOF $SHELL 2> /dev/null <<- \EOF || err_exit 'nested ${...} command substitution not supported' x=${ print ${ print hello;} $(print world) } [[ $x == 'hello world' ]] EOF $SHELL 2> /dev/null <<- \EOF || err_exit 'terminating } is not a reserved word with ${ command }' x=${ { print -n } ; print -n hello ; } ; print ' world' } [[ $x == '}hello world' ]] EOF $SHELL 2> /dev/null <<- \EOF || err_exit '${ command;}xxx not working' f() { print foo } [[ ${ f;}bar == foobar ]] EOF unset foo [[ ! ${foo[@]} ]] || err_exit '${foo[@]} is not empty when foo is unset' [[ ! ${foo[3]} ]] || err_exit '${foo[3]} is not empty when foo is unset' [[ $(print "[${ print foo }]") == '[foo]' ]] || err_exit '${...} not working when } is followed by ]' [[ $(print "${ print "[${ print foo }]" }") == '[foo]' ]] || err_exit 'nested ${...} not working when } is followed by ]' unset foo foo=$(false) > /dev/null && err_exit 'failed command substitution with redirection not returning false' expected=foreback got=$(print -n fore; (sleep .2;print back)&) [[ $got == $expected ]] || err_exit "command substitution background process output error -- got '$got', expected '$expected'" for false in false $binfalse do x=$($false) && err_exit "x=\$($false) should fail" $($false) && err_exit "\$($false) should fail" $($false) > /dev/null && err_exit "\$($false) > /dev/null should fail" done if env x-a=y >/dev/null 2>&1 then [[ $(env 'x-a=y' $SHELL -c 'env | grep x-a') == *x-a=y* ]] || err_exit 'invalid environment variables not preserved' fi set -- false for i in "$@"; do :; done || err_exit 'empty loop does not reset exit status ("$@")' false for i do :; done || err_exit 'empty loop does not reset exit status ("$@" shorthand)' false for i in; do :; done || err_exit "empty loop does not reset exit status (empty 'in' list)" false case 1 in 1) ;; esac || err_exit "empty case does not reset exit status" false case 1 in 2) echo whoa ;; esac || err_exit "non-matching case does not reset exit status" false 2>&1 || err_exit "lone redirection does not reset exit status" float s=SECONDS for i in .1 .2 do print $i done | while read sec; do ( "$binsleep" "$sec"; "$binsleep" "$sec") done (( (SECONDS-s) < .4)) && err_exit '"command | while read...done" finishing too fast' s=SECONDS set -o pipefail for ((i=0; i < 30; i++)) do print hello sleep .01 done | "$binsleep" .1 (( (SECONDS-s) < .2 )) || err_exit 'early termination not causing broken pipe' got=$({ trap 'print trap' 0; print -n | "$bincat"; } & wait "$!") [[ $got == trap ]] || err_exit "trap on exit not correctly triggered (expected 'trap', got $(printf %q "$got"))" got=$({ trap 'print trap' ERR; print -n | "$binfalse"; } & wait "$!") [[ $got == trap ]] || err_exit "trap on ERR not correctly triggered (expected 'trap', got $(printf %q "$got"))" exp= got=$( function fun { $binfalse && echo FAILED } : works if this line deleted : | fun : works if this line deleted : ) [[ $got == $exp ]] || err_exit "pipe to function with conditional fails -- expected '$exp', got '$got'" got=$( : works if this line deleted : | { $binfalse && echo FAILED; } : works if this line deleted : ) [[ $got == $exp ]] || err_exit "pipe to { ... } with conditional fails -- expected '$exp', got '$got'" got=$( : works if this line deleted : | ( $binfalse && echo FAILED ) : works if this line deleted : ) [[ $got == $exp ]] || err_exit "pipe to ( ... ) with conditional fails -- expected '$exp', got '$got'" ( $SHELL -c 'trap : DEBUG; x=( $foo); exit 0') 2> /dev/null || err_exit 'trap DEBUG fails' bintrue=$(whence -p true) set -o pipefail float start=$SECONDS end for ((i=0; i < 2; i++)) do print foo sleep .15 done | { read; $bintrue; end=$SECONDS ;} (( (SECONDS-start) < .1 )) && err_exit "pipefail not waiting for pipe to finish" set +o pipefail (( (SECONDS-end) > .2 )) && err_exit "pipefail causing $bintrue to wait for other end of pipe" { env A__z=C+SHLVL $SHELL -c : ;} 2> /dev/null || err_exit "SHLVL with wrong attribute fails" if [[ $bintrue ]] then float t0=SECONDS { time sleep .15 | $bintrue ;} 2> /dev/null (( (SECONDS-t0) < .1 )) && err_exit 'time not waiting for pipeline to complete' fi cat > $tmp/foo.sh <<- \EOF eval "cat > /dev/null < /dev/null" sleep .1 EOF float sec=SECONDS . $tmp/foo.sh | cat > /dev/null (( (SECONDS-sec) < .07 )) && err_exit '. script does not restore output redirection with eval' file=$tmp/foobar builtin cat for ((n=0; n < 1000; n++)) do > $file { sleep .001;echo $? >$file;} | cat > /dev/null if [[ ! -s $file ]] then err_exit 'output from pipe is lost with pipe to builtin' break; fi done $SHELL -c 'kill -0 123456789123456789123456789' 2> /dev/null && err_exit 'kill not catching process id overflows' [[ $($SHELL -c '{ cd..; print ok;}' 2> /dev/null) == ok ]] || err_exit 'command name ending in .. causes shell to abort' $SHELL -xc '$(LD_LIBRARY_PATH=$LD_LIBRARY_PATH exec $SHELL -c :)' > /dev/null 2>&1 || err_exit "ksh -xc '(name=value exec ksh)' fails with err=$?" $SHELL 2> /dev/null -c $'for i;\ndo :;done' || err_exit 'for i ; not vaid' # ====== # Crash on syntax error when dotting/sourcing multiple files # Ref.: https://www.mail-archive.com/ast-developers@lists.research.att.com/msg01943.html ( mkdir "$tmp/dotcrash" || exit cd "$tmp/dotcrash" || exit cat >functions.ksh <<-EOF function f1 { echo "f1" } function f2 { if [[ $1 -eq 1 ]]: # deliberate syntax error then echo "f2" fi } EOF cat >sub1.ksh <<-EOF . ./functions.ksh echo "sub1" >tmp.out EOF cat >main.ksh <<-EOF . ./sub1.ksh EOF "$SHELL" main.ksh 2>/dev/null ) || err_exit "crash when sourcing multiple files (exit status $?)" # ====== # The time keyword # A single '%' after a format specifier should not be a syntax # error (it should be treated as a literal '%'). expect='0%' actual=$( TIMEFORMAT=$'%0S%' redirect 2>&1 set +x time : ) [[ $actual == "$expect" ]] || err_exit "'%' is not treated literally when placed after a format specifier" \ "(expected $(printf %q "$expect"), got $(printf %q "$actual"))" # The locale's radix point shouldn't be ignored us=$( LC_ALL='C.UTF-8' # radix point '.' TIMEFORMAT='%1U' # catch -1.99 bug as well by getting user time redirect 2>&1 set +x time sleep 0 ) eu=$( LC_ALL='C_EU.UTF-8' # radix point ',' TIMEFORMAT='%1U' redirect 2>&1 set +x time sleep 0 ) [[ ${us:1:1} == ${eu:1:1} ]] && err_exit "The time keyword ignores the locale's radix point (both are ${eu:1:1})" # ====== # Expansion of multibyte characters after expansion of single-character names $1..$9, $?, $!, $-, etc. function exptest { print -r "$1テスト" print -r "$?テスト" print -r "$#テスト" } expect=$'fooテスト\n0テスト\n1テスト' actual=$(exptest foo) [[ $actual == "$expect" ]] || err_exit 'Corruption of multibyte char following expansion of single-char name' \ "(expected $(printf %q "$expect"), got $(printf %q "$actual"))" # ====== # ksh didn't rewrite argv correctly (rhbz#1047506) # When running a script without a #! hashbang path, ksh attempts to replace argv with the arguments # of the script. However, fixargs() didn't wipe out the rest of previous arguments after the last # \0. This caused an erroneous record in /proc//cmdline and the output of the ps command. getPsOutput() { # UNIX95=1 makes this work on HP-UX. actual=$(UNIX95=1 ps -o args= -p "$1" 2>&1) # BSD: setproctitle(3) prepends "ksh:" and ps(1) appends " (ksh)". Remove. actual=${actual#'ksh: '} actual=${actual%' (ksh)'} # Some 'ps' implementations add leading and/or trailing whitespace. Remove. while [[ $actual == [[:space:]]* ]]; do actual=${actual#?}; done while [[ $actual == *[[:space:]] ]]; do actual=${actual%?}; done } getCmdline() { if [[ $(uname -s) =~ ^UnixWare$ ]]; then # UnixWare's ps does not trust the value stored in argv[0] as a # security measure. It is still accessible within cmdline. actual=$(< "/proc/${1}/cmdline") else getPsOutput "$1" fi } if [[ ! $(uname -s) =~ ^SunOS$ ]] && getPsOutput "$$" && [[ "$SHELL $0" == "$actual"* ]] # "$SHELL $0" is how shtests invokes this script then expect='./atest 1 2' echo 'sleep 10; exit 0' >atest chmod 755 atest ./atest 1 2 & getCmdline "$!" kill "$!" [[ $actual == "$expect" ]] || err_exit "ksh didn't rewrite argv correctly" \ "(expected $(printf %q "$expect"), got $(printf %q "$actual"))" fi unset -f getPsOutput getCmdline # ====== # https://bugzilla.redhat.com/1241013 got=$(eval 'x=$(for i in test; do case $i in test) true;; esac; done)' 2>&1) \ || err_exit "case in a for loop inside a \$(comsub) caused syntax error (got $(printf %q "$got"))" got=$(eval 'x=${ for i in test; do case $i in test) true;; esac; done; }' 2>&1) \ || err_exit "case in a for loop inside a \${ comsub; } caused syntax error (got $(printf %q "$got"))" got=$(eval 'x=`for i in test; do case $i in test) true;; esac; done`' 2>&1) \ || err_exit "case in a for loop inside a \`comsub\` caused syntax error (got $(printf %q "$got"))" # ====== # Various DEBUG trap fixes: https://github.com/ksh93/ksh/issues/155 # Redirecting disabled the DEBUG trap exp=$'LINENO: 4\nfoo\nLINENO: 5\nLINENO: 6\nbar\nLINENO: 7\nbaz' got=$({ "$SHELL" -c ' PATH=/dev/null trap "echo LINENO: \$LINENO >&1" DEBUG # 3 echo foo # 4 var=$(echo) # 5 echo bar # 6 echo baz # 7 '; } 2>&1) ((!(e = $?))) && [[ $got == "$exp" ]] || err_exit 'Redirection in DEBUG trap corrupts the trap' \ "(got status $e$( ((e>128)) && print -n / && kill -l "$e"), $(printf %q "$got"))" # The DEBUG trap crashed when re-trapping inside a subshell exp=$'trap -- \': main\' EXIT\ntrap -- \': main\' ERR\ntrap -- \': main\' KEYBD\ntrap -- \': main\' DEBUG' got=$({ "$SHELL" -c ' PATH=/dev/null for sig in EXIT ERR KEYBD DEBUG do trap ": main" $sig ( trap ": LEAK : $sig" $sig ) trap trap - "$sig" done '; } 2>&1) ((!(e = $?))) && [[ $got == "$exp" ]] || err_exit 'Pseudosignal trap failed when re-trapping in subshell' \ "(got status $e$( ((e>128)) && print -n / && kill -l "$e"), $(printf %q "$got"))" # Field splitting broke upon evaluating an unquoted expansion in a DEBUG trap exp=$'a\nb\nc' got=$({ "$SHELL" -c ' PATH=/dev/null v="" trap ": \$v" DEBUG A="a b c" set -- $A printf "%s\n" "$@" '; } 2>&1) ((!(e = $?))) && [[ $got == "$exp" ]] || err_exit 'Field splitting broke after executing DEBUG trap' \ "(got status $e$( ((e>128)) && print -n / && kill -l "$e"), $(printf %q "$got"))" # The DEBUG trap had side effects on the exit status trap ':' DEBUG (exit 123) (((e=$?)==123)) || err_exit "DEBUG trap run in subshell affects exit status (expected 123, got $e)" r=$(exit 123) (((e=$?)==123)) || err_exit "DEBUG trap run in \$(comsub) affects exit status (expected 123, got $e)" r=`exit 123` (((e=$?)==123)) || err_exit "DEBUG trap run in \`comsub\` affects exit status (expected 123, got $e)" trap - DEBUG # The DEBUG trap was incorrectly inherited by subshells exp=$'Subshell\nDebug 1\nParentshell' got=$( trap 'echo Debug ${.sh.subshell}' DEBUG (echo Subshell) echo Parentshell ) trap - DEBUG # bug compat [[ $got == "$exp" ]] || err_exit "DEBUG trap inherited by subshell" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # The DEBUG trap was incorrectly inherited by ksh functions exp=$'Debug 0\nFunctionEnv\nDebug 0\nParentEnv' got=$( function myfn { echo FunctionEnv } trap 'echo Debug ${.sh.level}' DEBUG myfn echo ParentEnv ) trap - DEBUG # bug compat [[ $got == "$exp" ]] || err_exit "DEBUG trap inherited by ksh function" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # Make sure the DEBUG trap is still inherited by POSIX functions exp=$'Debug 0\nDebug 1\nFunction\nDebug 0\nNofunction' got=$( myfn() { echo Function } trap 'echo Debug ${.sh.level}' DEBUG myfn echo Nofunction ) trap - DEBUG # bug compat [[ $got == "$exp" ]] || err_exit "DEBUG trap not inherited by POSIX function" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # ====== exit $((Errors<125?Errors:125))