# Tests for "nofork" command substitution. %prep mkdir nofork.tmp touch nofork.tmp/file{1,2}.txt purr() { print -r -- "$@" } purl() { print -rl -- "$@" } %test REPLY=OUTER purr ${| REPLY=INNER } $REPLY 0:Basic substitution and REPLY scoping >INNER OUTER reply=(x OUTER x) purl ${{reply} reply=(\{ INNER \})} $reply 0:Basic substitution, brace quoting, and array result >{ >INNER >} >{ >INNER >} () { setopt localoptions ignorebraces purl ${{reply} reply=({ INNER })} $reply } 0:Basic substitution, ignorebraces, and array result >{ >INNER >} >{ >INNER >} purr ${| REPLY=first}:${| REPLY=second}:$REPLY 0:re-scoping of REPLY in one statement >first:second:OUTER purr BEGIN${| printf -v REPLY '%s\n' one two three ; }END 0:Adjacent words >BEGINone >two >three >END purr "BEGIN${| printf -v REPLY '%s\n' one two three }END" 0:Adjacent words and quoting, part 1 >BEGINone >two >three >END purr BEGIN"${| printf -v REPLY '%s\n' one two three }"END 0:Adjacent words and quoting, part 2 >BEGINone >two >three >END purr BEGIN"${| printf -v REPLY '%s\n'\ one two three }"END 0:Embedded newlines >BEGINone >two >three >END purr BEGIN"${| printf -v REPLY $'%s\n' one two three }"END 0:Embedded newlines and $'...' >BEGINone >two >three >END purl ${| print -v REPLY one word here; setopt shwordsplit } purl ${| print -v REPLY three words here } purl "and ${| print -v REPLY one word here }" unsetopt shwordsplit 0:test word splitting on result F:setting option inside is too late for that substitution >one word here >three >words >here >and one word here ( cd nofork.tmp setopt globsubst purr ${| REPLY=f* } purr ${| REPLY=f? }* unsetopt globsubst purr ${| REPLY=f* } purr ${| REPLY=f? }* ) 1:globsubst on result >file1.txt file2.txt >file1.txt file2.txt >f* ?(eval):8: no matches found: f?* purr ${| REPLY=$'trailing newlines remain\n\n' } 0:newline removal should not occur, part 1 >trailing newlines remain > > purr ${ echo $'one trailing newline\nremoved\n\n\n' } 0:newline removal in ${ ... }, zsh mode >one trailing newline >removed > > > () { emulate -L ksh purl ${ echo $'all trailing newlines\nremoved\n\n\n' } purr "${ echo $'all trailing newlines\nremoved\n\n\n' }" } 0:newline removal in ${ ... }, emulation mode, shwordsplit >all >trailing >newlines >removed >all trailing newlines >removed purr "${ echo $'no trailing newlines\nremoved\n\n\n' }" 0:newline removal should not occur, part 2 >no trailing newlines >removed > > > > () { purr ${| REPLY=$* ; shift 2 } purr $* } these are arguments 0:access to context $argv >these are arguments >arguments purr ${:-${| REPLY=${REPLY:-buried}}} purr ${:-"${| REPLY=${REPLY:-more buried}}"} 0:nofork inside parameter scope >buried >more buried : ${(e):-'${| REPLY=oops'} 1:unclosed braces are sometimes a bad substitution F:This seems silly, but see A01grammar ${(e):-'${'} test ?(eval):1: bad substitution purr ${| REPLY=oops 1:other times lack of closing brace is merely unexpected F:Why not use this error in the previous case as well? ?(eval):1: closing brace expected # Next tests check that the PS2 stack is properly managed on error purr ${| REPLY=${REPLY:-buried}}} 1:unbalanced braces, part 0 ?(eval):1: parse error near `}' purr ${:-${| REPLY=${REPLY:-buried}} 1:unbalanced braces, part 1 ?(eval):1: closing brace expected purr ${:-"${| REPLY=${REPLY:-more buried}"} 1:unbalanced braces, part 2 ?(eval):1: unmatched " purr ${:-"${| REPLY=${REPLY:-more buried"}}} 1:unbalanced braces, part 3 ?(eval):1: unmatched " purr ${:-"${| REPLY=${REPLY:-more buried}}}" 1:unbalanced braces, part 4 ?(eval):1: closing brace expected # Same tests with leading space (future-proofing) purr ${ purr ${REPLY:-buried}}} 1:unbalanced braces, part 0+ ?(eval):1: parse error near `}' purr ${:-${ purr ${REPLY:-buried}} 1:unbalanced braces, part 1+ ?(eval):1: closing brace expected purr ${:-"${ purr ${REPLY:-more buried}"} 1:unbalanced braces, part 2+ ?(eval):1: unmatched " purr ${:-"${ purr ${REPLY:-more buried"}}} 1:unbalanced braces, part 3+ ?(eval):1: unmatched " purr ${:-"${ purr ${REPLY:-more buried}}}" 1:unbalanced braces, part 4+ ?(eval):1: closing brace expected purr "${ purr STDOUT }" 0:capture stdout >STDOUT > # end PS2 stack tests purr $(purr outside ${| REPLY=inside }) purr BEGIN$(purr outside ${| REPLY=inside })END purr "BEGIN$(purr outside ${| REPLY=inside })END" purr outside ${| REPLY=$(purr inside)} purr "outside ${| REPLY=$(purr inside)}" 0:mixing with forking cmdsubst >outside inside >BEGINoutside insideEND >BEGINoutside insideEND >outside inside >outside inside purr `purr outside ${| REPLY=inside }` purr "outside `purr ${| REPLY=inside }`" purr outside ${| REPLY=`purr inside`} purr "outside ${| REPLY=`purr inside`}" purr outside "`purr ${| REPLY="${:-inside}"}`" purr "outside ${| REPLY=`purr ${:-inside}`}" 0:mixing with backticks >outside inside >outside inside >outside inside >outside inside >outside inside >outside inside purr ${| REPLY=$(( 9 + 17 )) } purr $(( 9 + ${| REPLY=17 } )) 0:mixing with arithemetic >26 >26 unset reply purl ${{reply} reply=(1 2 ${| REPLY=3 } 4) } typeset -p reply 0:array behavior with global assignment >1 >2 >3 >4 >typeset -g -a reply=( 1 2 3 4 ) unset outer purr "${| outer=OUTER REPLY=INNER return 7 OUTER=NOTREACHED } $outer $?" 0:return statement inside, part 1 F:status of "print" should hide return >INNER OUTER 7 unset outer outer=${| REPLY=${| return 7}} 7:return status propages in assignment like $(...) unset outer purr "${| outer=OUTER REPLY=INNER return 7 OUTER=NOTREACHED } $outer $?" print REACHED $OUTER 0:return statement inside, part 2 >INNER OUTER 7 >REACHED unset outer purr "${| # Localoptions needed to avoid breaking test harness? # The setopt command affects surrounding context setopt localoptions errreturn outer=OUTER REPLY=INNER false OUTER=NOTREACHED } $outer $?" print REACHED $OUTER ${options[errreturn]} 0:errreturn works inside and remains outside >INNER OUTER 1 >REACHED on ( unset outer purr "${| outer=OUTER REPLY=INNER exit 7 OUTER=NOTREACHED } $outer $OUTER $?" print NOT REACHED ) 7:exit statement inside ( unset outer purr "${| setopt errexit outer=OUTER REPLY=INNER false OUTER=NOTREACHED } $outer $?" print NOT REACHED ) 1:errexit inside outer=GLOBAL purr "${| local outer=LOCAL REPLY=INNER } $outer $?" 0:local declaration inside >INNER GLOBAL 0 unset zz outer=GLOBAL purr "${{zz} local outer=LOCAL zz=NONLOCAL } $outer $?" print $zz 0:local declaration, global assignment, part 1 >NONLOCAL GLOBAL 0 >NONLOCAL unset zz outer=GLOBAL purr "${${| local outer=LOCAL zz=NONLOCAL }:-$zz} $outer $?" 0:local declaration, global assignment, part 2 (evaluation order) >NONLOCAL GLOBAL 0 : ${| fn1() { () { print -v REPLY $'Defined Function' ;} ;} } print "IN${| fn2() { () { print "${:-Second }${|fn1}" ;} ;} }OUT" fn2 0:function definition, brace nesting, quote nesting >INOUT >Second Defined Function <<-EOF ${| REPLY=$'in a here document\n' } EOF 0:here-document behavior F:Fiddly here to get EOF past the test syntax >in a here document > <<<${| REPLY="in a here string" } 0:here-string behavior >in a here string <<<"${ purr $'stdout as a here string' }" 0:another capture stdout >stdout as a here string > wrap=${| REPLY="REPLY in environment assignment" } typeset -p wrap wrap=${ purr "capture in environment assignment" } typeset -p wrap 0:assignment context >typeset -g wrap='REPLY in environment assignment' >typeset -g wrap='capture in environment assignment' # Repeat return and exit tests with stdout capture purr "${ print INNER return 7 } $?" 0:return statement inside, part 1+ F:status of "print" should hide return >INNER > 7 unset outer outer=${ return 7 } 7:return status propages in stdout capture unset outer purr "${ outer=OUTER print INNER return 7 OUTER=NOTREACHED } $outer $?" print REACHED $OUTER 0:return statement inside, part 2+ >INNER > OUTER 7 >REACHED unset outer purr "${ # Localoptions needed to avoid breaking test harness? # The setopt command affects surrounding context setopt localoptions errreturn outer=OUTER print INNER false OUTER=NOTREACHED } $outer $?" print REACHED $OUTER ${options[errreturn]} 0:errreturn works inside stdout capture >INNER > OUTER 1 >REACHED on ( unset outer purr "${ outer=OUTER print INNER exit 7 OUTER=NOTREACHED } $outer $OUTER $?" print NOT REACHED ) 7:exit statement inside stdout capture ( unset outer purr "${ setopt errexit outer=OUTER print INNER false OUTER=NOTREACHED } $outer $?" print NOT REACHED ) 1:errexit inside stdout capture setopt ignorebraces 0:dummy test to set option soon enough F:must do this before evaluating the next test block purr ${| REPLY=${REPLY:-buried}}} 0:ignored braces, part 1 >buried} # Global $REPLY still set from earlier test purr "${ purr ${REPLY:+buried}}}" 0:ignored braces, part 2 >buried >} purr ${ { echo nested ;} } 0:ignored braces, part 3 >nested purr ${ { echo nested } } DONE 1:ignored braces, part 4 ?(eval):3: parse error near `}' unsetopt ignorebraces # "break" blocks function calls in outer loop # Could use print, but that might get fixed repeat 3 do purr ${ for x in 1 2 3 4 do (( x == 3 )) && break 2 # use error output to confirm loop count print -u 2 $x done } XX done 0:break N propagates ?1 ?2 # Cannot "purr": break skips pending function calls # Use "repeat" to avoid infinite loop on failure repeat 3 do; echo ${|REPLY=x; break }; done repeat 3 do; echo ${{x} x=y; break }; done repeat 3 do; echo ${ echo z; break }; done 0:break after assignment completes the assignment >x >y >z # Subshell because error exits ( purr ${ echo ${unset?oops} } ) 1:error handling (without crashing) *?*unset: oops purr ${ .zsh.cmdsubst=error } 1:reserved parameter name (without crashing) *?*.zsh.cmdsubst: can't modify read-only parameter %clean unfunction purr purl