# zsh on-target self-test payload. Run as `zsh -f selftest.zsh`; prints one # ` ` line per case -- the case name is the one 16-zsh.test reports, # and every value is a single whitespace-free token so one awk pass can join the # whole output against the expected table -- then ALL-ZSH-OK once it has run to # completion. # # Covers what people choose zsh FOR rather than what every shell already does: # arrays and associative arrays, extended globbing with qualifiers, parameter # expansion flags, and the arithmetic evaluator. Nothing here forks an external # tool, and on this arm that is a hard requirement rather than a cost argument: # a forked child is never reaped (linux-in-the-browser#161), so a single shelled- # out command parks the shell and forfeits every answer after it. # # Every list result goes through an intermediate array before ${(j:...:)} joins # it. The tempting one-liner ${(j:,:)${(o)arr}} applies NEITHER flag -- the inner # expansion is flattened to one space-joined word before the outer flag sees it, # so the result reads as an unsorted, unjoined list and the case passes only # because the expected value was copied from that same broken run. emulate -L zsh setopt extended_glob # ---------------------------------------------------------------- arrays ---- # zsh arrays are 1-INDEXED (the ksh/bash divergence that breaks ported scripts # first), support negative and range subscripts, and expand with element # boundaries intact. a=(alpha beta gamma delta) print -r -- "array-count ${#a}" print -r -- "array-first ${a[1]}" print -r -- "array-last ${a[-1]}" print -r -- "array-slice ${(j:,:)a[2,3]}" a+=(epsilon) print -r -- "array-append ${a[5]}" print -r -- "array-join ${(j:-:)a}" # ${a[i]} is one element even when it holds whitespace: no word splitting. b=("two words" x) print -r -- "array-nosplit ${#b}" print -r -- "array-elemlen ${#b[1]}" # ------------------------------------------------- associative arrays ---- typeset -A h h=(one 1 two 2 three 3) print -r -- "hash-val ${h[two]}" print -r -- "hash-count ${#h}" ks=(${(ko)h}); print -r -- "hash-keys ${(j:,:)ks}" vs=(${(vo)h}); print -r -- "hash-vals ${(j:,:)vs}" print -r -- "hash-has ${+h[two]}" print -r -- "hash-nohas ${+h[nine]}" h[four]=4 unset "h[one]" ks2=(${(ko)h}); print -r -- "hash-after ${(j:,:)ks2}" # ------------------------------------------- extended globbing + qualifiers ---- # A glob qualifier is the feature no other shell has: (.) plain files, (/) # directories, (N) null_glob for this expansion only, (L...) by size, (o...) # ordering -- so `ls | grep | while read` pipelines collapse into one word. # The tree these globs match is built with zsh/files' BUILTIN mkdir/rm, not the # coreutils ones. Not a stylistic choice: a forked child is never reaped on this # arm (linux-in-the-browser#161), so one `mkdir -p` here parks the shell forever # and takes the other 57 answers with it. zsh/files is linked in statically # (--disable-dynamic), so zmodload is a table lookup and no loader is involved. # If it is ever not linked in, report the cases rather than hang. # BRINTOS-WORKAROUND: linux-in-the-browser#161 remove — the builtin mkdir/rm stand in # for the coreutils ones only because a forked child is never reaped on this arm. d=${TMPDIR:-/tmp}/bst-zsh-glob.$$ globcases=(glob-txt glob-plain glob-dir glob-rec glob-neg glob-ci glob-alt glob-empty glob-largest glob-tail glob-null) if zmodload zsh/files 2>/dev/null; then mkdir -p $d/sub print -n -- 'xxxxx' > $d/a.txt print -n -- '' > $d/b.txt print -n -- 'yy' > $d/c.log print -n -- '' > $d/sub/d.txt t=($d/*.txt(N)); print -r -- "glob-txt ${#t}" p=($d/*(.N)); print -r -- "glob-plain ${#p}" q=($d/*(/N)); print -r -- "glob-dir ${#q}" r=($d/**/*.txt(N)); print -r -- "glob-rec ${#r}" n=($d/^*.txt(N)); print -r -- "glob-neg ${#n}" i=($d/(#i)A.TXT(N)); print -r -- "glob-ci ${#i}" e=($d/*.(txt|log)(N)); print -r -- "glob-alt ${#e}" z=($d/*(.NL0)); print -r -- "glob-empty ${#z}" big=($d/*(.NoL[-1])); print -r -- "glob-largest ${big[1]:t}" m=($d/*.txt(N:t)); mo=(${(o)m}); print -r -- "glob-tail ${(j:,:)mo}" no=($d/nosuch*(N)); print -r -- "glob-null ${#no}" rm -rf $d else for gc in $globcases; do print -r -- "$gc NOFILESMODULE"; done fi # ------------------------------------------------ parameter expansion flags ---- s="Hello World" su=${(U)s}; print -r -- "expand-upper ${su:gs/ //}" sl=${(L)s}; print -r -- "expand-lower ${sl:gs/ //}" sc=${(C)sl}; print -r -- "expand-cap ${sc:gs/ //}" csv="a,b,c" parts=(${(s:,:)csv}) print -r -- "expand-splitcount ${#parts}" print -r -- "expand-splitsecond ${parts[2]}" print -r -- "expand-rejoin ${(j:+:)parts}" w=(pear apple fig) wo=(${(o)w}); print -r -- "expand-sorted ${(j:,:)wo}" wO=(${(O)w}); print -r -- "expand-rsorted ${(j:,:)wO}" dup=(b a b c a) du=(${(ou)dup}); print -r -- "expand-uniq ${(j:,:)du}" num=(10 9 100) nn=(${(on)num}); print -r -- "expand-numsort ${(j:,:)nn}" cnt=42 print -r -- "expand-lpad ${(l:5::0:)cnt}" print -r -- "expand-rpad ${(r:5::.:)cnt}" print -r -- "expand-len ${#s}" path1=/usr/local/lib/libfoo.so print -r -- "expand-tail ${path1:t}" print -r -- "expand-head ${path1:h}" print -r -- "expand-root ${path1:t:r}" print -r -- "expand-ext ${path1:e}" sub=${s:gs/o/0/}; print -r -- "expand-subst ${sub:gs/ //}" q1="a b" print -r -- "expand-quote ${(q)q1}" print -r -- "expand-words ${(w)#s}" # ------------------------------------------------------------ arithmetic ---- print -r -- "arith-pow $(( 2 ** 10 ))" print -r -- "arith-mod $(( 17 % 5 ))" print -r -- "arith-float $(( 3.0 / 2 ))" print -r -- "arith-cmp $(( 5 > 3 ))" print -r -- "arith-hex $(( [#16] 255 ))" print -r -- "arith-base $(( 2#1011 ))" print -r -- "arith-tern $(( 3 > 2 ? 111 : 222 ))" integer iv=7 (( iv *= 6 )) print -r -- "arith-int $iv" typeset -F 2 fv (( fv = 1.0 / 4 )) print -r -- "arith-fp $fv" print -r -- "arith-bits $(( (1 << 8) | 3 ))" # C-style loop + arithmetic command, the constructs a POSIX shell has to fake. sum=0 for (( k = 1; k <= 5; k++ )); do (( sum += k )); done print -r -- "arith-loop $sum" # zsh/mathfunc ships as a module; with --disable-dynamic it is linked in, so # zmodload is a table lookup rather than a dlopen -- which is why it is expected # to work on an arm with no dynamic loading at all. if zmodload zsh/mathfunc 2>/dev/null; then print -r -- "arith-sqrt $(( int(sqrt(16)) ))" else print -r -- "arith-sqrt NOMODULE" fi print -r -- ALL-ZSH-OK