View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -3,7 +3,7 @@
# Arrays decay upon assignment (without splicing) and equality. This will not
# be true in Oil -- arrays will be first class.
### Assignment Causes Array Decay
#### Assignment Causes Array Decay
set -- x y z
argv.py "[$@]"
var="[$@]"
@@ -13,14 +13,14 @@ argv.py "$var"
['[x y z]']
## END
### Array Decay with IFS
#### Array Decay with IFS
IFS=x
set -- x y z
var="[$@]"
argv.py "$var"
# stdout: ['[x y z]']
## stdout: ['[x y z]']
### User arrays decay
#### User arrays decay
declare -a a b
a=(x y z)
b="${a[@]}" # this collapses to a string
@@ -35,26 +35,26 @@ argv.py "${c[@]}"
['x', 'YYY', 'z']
## END
### $a gives first element of array
#### $a gives first element of array
a=(1 '2 3')
echo $a
# stdout: 1
## stdout: 1
### Assign to array index without initialization
#### Assign to array index without initialization
a[5]=5
a[6]=6
echo "${a[@]}" ${#a[@]}
# stdout: 5 6 2
## stdout: 5 6 2
### a[40] grows array
#### a[40] grows array
a=(1 2 3)
a[1]=5
a[40]=30 # out of order
a[10]=20
echo "${a[@]}" "${#a[@]}" # length is 1
# stdout: 1 5 3 20 30 5
## stdout: 1 5 3 20 30 5
### array decays to string when comparing with [[ a = b ]]
#### array decays to string when comparing with [[ a = b ]]
a=('1 2' '3 4')
s='1 2 3 4' # length 2, length 4
echo ${#a[@]} ${#s}
@@ -64,13 +64,13 @@ echo ${#a[@]} ${#s}
EQUAL
## END
### Increment array variables
#### Increment array variables
a=(1 2)
(( a++ )) # doesn't make sense
echo "${a[@]}"
# stdout: 2 2
## stdout: 2 2
### Apply vectorized operations on ${a[*]}
#### Apply vectorized operations on ${a[*]}
a=('-x-' 'y-y' '-z-')
# This does the prefix stripping FIRST, and then it joins.
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -1,18 +1,18 @@
#!/usr/bin/env bash
### Env value doesn't persist
#### Env value doesn't persist
FOO=foo printenv.py FOO
echo [$FOO]
# STDOUT:
## STDOUT:
foo
[]
## END
### Env value with equals
#### Env value with equals
FOO=foo=foo printenv.py FOO
## stdout: foo=foo
### Env binding can use preceding bindings, but not subsequent ones
#### Env binding can use preceding bindings, but not subsequent ones
# This means that for ASSIGNMENT_WORD, on the RHS you invoke the parser again!
# Could be any kind of quoted string.
FOO="foo" BAR="[$FOO][$BAZ]" BAZ=baz printenv.py FOO BAR BAZ
@@ -26,26 +26,26 @@ foo
baz
## END
### Env value with two quotes
#### Env value with two quotes
FOO='foo'"adjacent" printenv.py FOO
# stdout: fooadjacent
## stdout: fooadjacent
### Env value with escaped <
#### Env value with escaped <
FOO=foo\<foo printenv.py FOO
## stdout: foo<foo
### FOO=foo echo [foo]
#### FOO=foo echo [foo]
FOO=foo echo "[$foo]"
## stdout: []
### FOO=foo func
#### FOO=foo func
func() {
echo "[$FOO]"
}
FOO=foo func
## stdout: [foo]
### Multiple temporary envs on the stack
#### Multiple temporary envs on the stack
g() {
echo "$F" "$G1" "$G2"
echo '--- g() ---'
@@ -109,12 +109,12 @@ None
None
## END
### Escaped = in command name
#### Escaped = in command name
# foo=bar is in the 'spec/bin' dir.
foo\=bar
## stdout: HI
### Env binding not allowed before compound command
#### Env binding not allowed before compound command
# bash gives exit code 2 for syntax error, because of 'do'.
# dash gives 0 because there is stuff after for? Should really give an error.
# mksh gives acceptable error of 1.
@@ -123,77 +123,77 @@ FOO=bar for i in a b; do printenv.py $FOO; done
## OK mksh status: 1
## status: 2
### Trying to run keyword 'for'
#### Trying to run keyword 'for'
FOO=bar for
## status: 127
### Empty env binding
#### Empty env binding
EMPTY= printenv.py EMPTY
## stdout:
### Assignment doesn't do word splitting
#### Assignment doesn't do word splitting
words='one two'
a=$words
argv.py "$a"
## stdout: ['one two']
### Assignment doesn't do glob expansion
#### Assignment doesn't do glob expansion
touch _tmp/z.Z _tmp/zz.Z
a=_tmp/*.Z
argv.py "$a"
# stdout: ['_tmp/*.Z']
## stdout: ['_tmp/*.Z']
### Env binding in readonly/declare disallowed
#### Env binding in readonly/declare disallowed
# I'm disallowing this in the oil shell, because it doesn't work in bash!
# (v=None vs v=foo)
# assert status 2 for parse error, but allow stdout v=None/status 0 for
# existing implementations.
FOO=foo readonly v=$(printenv.py FOO)
echo "v=$v"
# OK bash/dash/mksh stdout: v=None
# OK bash/dash/mksh status: 0
# status: 2
## OK bash/dash/mksh stdout: v=None
## OK bash/dash/mksh status: 0
## status: 2
### local -a
#### local -a
# nixpkgs setup.sh uses this (issue #26)
f() {
local -a array=(x y z)
argv.py "${array[@]}"
}
f
# stdout: ['x', 'y', 'z']
# N-I dash stdout-json: ""
# N-I dash status: 2
# N-I mksh stdout-json: ""
# N-I mksh status: 1
## stdout: ['x', 'y', 'z']
## N-I dash stdout-json: ""
## N-I dash status: 2
## N-I mksh stdout-json: ""
## N-I mksh status: 1
### declare -a
#### declare -a
# nixpkgs setup.sh uses this (issue #26)
declare -a array=(x y z)
argv.py "${array[@]}"
# stdout: ['x', 'y', 'z']
# N-I dash stdout-json: ""
# N-I dash status: 2
# N-I mksh stdout-json: ""
# N-I mksh status: 1
## stdout: ['x', 'y', 'z']
## N-I dash stdout-json: ""
## N-I dash status: 2
## N-I mksh stdout-json: ""
## N-I mksh status: 1
### typeset -a a[1]=a a[3]=c
#### typeset -a a[1]=a a[3]=c
# declare works the same way in bash, but not mksh.
# spaces are NOT allowed here.
typeset -a a[1*1]=x a[1+2]=z
argv.py "${a[@]}"
# stdout: ['x', 'z']
# N-I dash stdout-json: ""
# N-I dash status: 2
## stdout: ['x', 'z']
## N-I dash stdout-json: ""
## N-I dash status: 2
### indexed LHS without spaces is allowed
#### indexed LHS without spaces is allowed
a[1 * 1]=x a[ 1 + 2 ]=z
argv.py "${a[@]}"
# stdout: ['x', 'z']
# N-I dash stdout-json: ""
# N-I dash status: 2
## stdout: ['x', 'z']
## N-I dash stdout-json: ""
## N-I dash status: 2
### declare -f
#### declare -f
func2=x # var names are NOT found
declare -f myfunc func2
echo $?
@@ -217,7 +217,7 @@ echo $?
127
## END
### declare -p
#### declare -p
var1() { echo func; } # function names are NOT found.
declare -p var1 var2 >/dev/null
echo $?
@@ -239,7 +239,7 @@ echo $?
127
## END
### typeset -f
#### typeset -f
# mksh implement typeset but not declare
typeset -f myfunc func2
echo $?
@@ -263,7 +263,7 @@ echo $?
127
## END
### typeset -p
#### typeset -p
var1() { echo func; } # function names are NOT found.
typeset -p var1 var2 >/dev/null
echo $?
@@ -291,7 +291,7 @@ echo $?
127
## END
### typeset -r makes a string readonly
#### typeset -r makes a string readonly
typeset -r s1='12'
typeset -r s2='34'
@@ -332,7 +332,7 @@ status=0
status=0
## END
### typeset -ar makes it readonly
#### typeset -ar makes it readonly
typeset -a -r array1=(1 2)
typeset -ar array2=(3 4)
@@ -362,16 +362,16 @@ status=1
status=1
status=1
## END
# N-I dash status: 2
# N-I dash stdout-json: ""
# N-I mksh status: 1
# N-I mksh stdout-json: ""
## N-I dash status: 2
## N-I dash stdout-json: ""
## N-I mksh status: 1
## N-I mksh stdout-json: ""
### typeset -x makes it exported
#### typeset -x makes it exported
typeset -rx PYTHONPATH=lib/
printenv.py PYTHONPATH
## STDOUT:
lib/
## END
# N-I dash stdout: None
## N-I dash stdout: None
View
@@ -7,48 +7,48 @@
# - zsh allows $a[$k], not just ${a[$k]}
### TODO: SETUP should be shared
#### TODO: SETUP should be shared
typeset -A a
a=(aa b foo bar a+1 c)
### retrieve key
#### retrieve key
typeset -A a
a=(aa b foo bar a+1 c)
echo ${a[aa]}
# stdout: b
## stdout: b
### set key
#### set key
typeset -A a
a=(aa b foo bar a+1 c)
a[X]=XX
argv.py "${a[@]}"
# What order is this?
# stdout: ['bar', 'b', 'c', 'XX']
## stdout: ['bar', 'b', 'c', 'XX']
### iterate over keys
#### iterate over keys
typeset -A assoc
assoc=(k1 v1 k2 v2 k3 v3)
for k in "${(@k)assoc}"; do
echo "$k: $assoc[$k]"
done
# stdout-json: "k1: v1\nk2: v2\nk3: v3\n"
## stdout-json: "k1: v1\nk2: v2\nk3: v3\n"
### iterate over both keys and values
#### iterate over both keys and values
typeset -A assoc
assoc=(k1 v1 k2 v2 k3 v3)
for k v ("${(@kv)assoc}"); do
echo "$k: $v"
done
# stdout-json: "k1: v1\nk2: v2\nk3: v3\n"
## stdout-json: "k1: v1\nk2: v2\nk3: v3\n"
### get length
#### get length
typeset -A assoc
assoc=(k1 v1 k2 v2 k3 v3)
echo ${#assoc} ${#assoc[k1]}
# stdout: 3 2
## stdout: 3 2
### index by integer does not work
#### index by integer does not work
typeset -A assoc
assoc=(k1 v1 k2 v2 k3 v3)
argv.py "${assoc[1]}"
# stdout: ['']
## stdout: ['']
View
@@ -14,55 +14,55 @@
# http://www.gnu.org/software/bash/manual/html_node/Arrays.html
# TODO: Need a SETUP section.
### TODO: SETUP should be share
#### TODO: SETUP should be share
declare -A a
a=([aa]=b [foo]=bar ['a+1']=c)
### retrieve indices with !
#### retrieve indices with !
declare -A a
a=([aa]=b [foo]=bar ['a+1']=c)
argv.py "${!a[@]}"
# Is this invalid on associative arrays? Makes no sense.
# stdout: ['aa', 'foo', 'a+1']
## stdout: ['aa', 'foo', 'a+1']
### $a gives nothing
#### $a gives nothing
declare -A a
a=([aa]=b [foo]=bar ['a+1']=c)
echo "${a}"
# stdout-json: "\n"
## stdout-json: "\n"
### length of dict does not work
#### length of dict does not work
declare -A a
a=([aa]=b [foo]=bar ['a+1']=c)
echo "${#a}"
# stdout: 0
## stdout: 0
### index by number doesn't work
#### index by number doesn't work
declare -A a
a=([aa]=b [foo]=bar ['a+1']=c)
echo 0 "${a[0]}" 1 "${a[1]}" 2 "${a[2]}"
# stdout-json: "0 1 2 \n"
## stdout-json: "0 1 2 \n"
### index by key name
#### index by key name
declare -A a
a=([aa]=b [foo]=bar ['a+1']=c)
echo "${a[aa]}" "${a[foo]}" "${a['a+1']}"
# WTF: Why do we get bar bar c?
# stdout-json: "b bar c\n"
## stdout-json: "b bar c\n"
### index by quoted string
#### index by quoted string
declare -A a
a=([aa]=b [foo]=bar ['a+1']=c)
echo "${a['a+1']}"
# stdout: c
## stdout: c
### index by unquoted string
#### index by unquoted string
declare -A a
a=([aa]=b [foo]=bar ['a+1']=c)
echo "${a[a+1]}"
# stdout: c
## stdout: c
### index by unquoted string as arithmetic
#### index by unquoted string as arithmetic
# For assoc arrays, unquoted string is just raw.
# For regular arrays, unquoted string is an arithmetic expression!
# How do I parse this?
@@ -73,30 +73,30 @@ a=1
echo "${assoc[a]}"
echo "${assoc[a+1]}" # This works
echo "${array[a+1]}"
# stdout-json: "\nc\n7\n"
## stdout-json: "\nc\n7\n"
### WTF index by key name
#### WTF index by key name
declare -A a
a=([xx]=bb [cc]=dd)
echo "${a[xx]}" "${a[cc]}"
# stdout-json: "bb dd\n"
## stdout-json: "bb dd\n"
### Array stored in associative array gets converted to string
#### Array stored in associative array gets converted to string
array=('1 2' 3)
declare -A d
d[a]="${array[@]}"
argv.py "${d[a]}"
# stdout: ['1 2 3']
## stdout: ['1 2 3']
### Can't initialize assoc array with indexed array
#### Can't initialize assoc array with indexed array
declare -A A=(1 2 3)
# status: 1
# BUG bash status: 0
## status: 1
## BUG bash status: 0
### Initializing indexed array with with assoc array drops the constants
#### Initializing indexed array with with assoc array drops the constants
declare -a a=([xx]=1 [yy]=2 [zz]=3)
#declare -a a=(1 2 3)
echo "${a[@]}"
#echo "${!a[@]}"
# N-I mksh stdout-json: ""
# BUG bash stdout-json: "3\n"
## N-I mksh stdout-json: ""
## BUG bash stdout-json: "3\n"
View
@@ -10,63 +10,63 @@
# bg
# %1 -- current job
### wait with nothing to wait for
#### wait with nothing to wait for
wait
# status: 0
## status: 0
### wait -n with nothing to wait for
#### wait -n with nothing to wait for
# The 127 is STILL overloaded. Copying bash for now.
wait -n
# status: 127
# OK dash status: 2
# OK mksh status: 1
## status: 127
## OK dash status: 2
## OK mksh status: 1
### wait with invalid job ID
#### wait with invalid job ID
wait %nonexistent
# status: 127
# OK dash status: 2
## status: 127
## OK dash status: 2
### wait with invalid PID
#### wait with invalid PID
wait %nonexistent
# status: 127
# OK dash status: 2
## status: 127
## OK dash status: 2
### Builtin in background
#### Builtin in background
echo async &
wait
# stdout: async
## stdout: async
### External command in background
#### External command in background
sleep 0.01 &
wait
# stdout-json: ""
## stdout-json: ""
### Pipeline in Background
#### Pipeline in Background
echo hi | { exit 99; } &
wait $!
echo status=$?
# stdout: status=99
## stdout: status=99
### Wait sets PIPESTATUS
#### Wait sets PIPESTATUS
{ echo hi; exit 55; } | { exit 99; } &
echo "pipestatus=${PIPESTATUS[@]}"
wait $!
echo status=$?
echo "pipestatus=${PIPESTATUS[@]}"
# stdout-json: "pipestatus=\nstatus=99\npipestatus=55 99\n"
# BUG bash stdout-json: "pipestatus=\nstatus=99\npipestatus=0\n"
# N-I mksh stdout-json: "pipestatus=0\nstatus=99\npipestatus=0\n"
# N-I dash stdout-json: ""
# N-I dash status: 2
## stdout-json: "pipestatus=\nstatus=99\npipestatus=55 99\n"
## BUG bash stdout-json: "pipestatus=\nstatus=99\npipestatus=0\n"
## N-I mksh stdout-json: "pipestatus=0\nstatus=99\npipestatus=0\n"
## N-I dash stdout-json: ""
## N-I dash status: 2
### Brace group in background, wait all
#### Brace group in background, wait all
{ sleep 0.09; exit 9; } &
{ sleep 0.07; exit 7; } &
wait # wait for all gives 0
echo "status=$?"
# stdout: status=0
## stdout: status=0
### Wait on background process PID
#### Wait on background process PID
{ sleep 0.09; exit 9; } &
pid1=$!
{ sleep 0.07; exit 7; } &
@@ -75,9 +75,9 @@ wait $pid1
echo "status=$?"
wait $pid2
echo "status=$?"
# stdout-json: "status=9\nstatus=7\n"
## stdout-json: "status=9\nstatus=7\n"
### Wait on multiple specific IDs returns last status
#### Wait on multiple specific IDs returns last status
{ sleep 0.08; exit 8; } &
jid1=$!
{ sleep 0.09; exit 9; } &
@@ -86,32 +86,32 @@ jid2=$!
jid3=$!
wait $jid1 $jid2 $jid3 # NOTE: not using %1 %2 %3 syntax on purpose
echo "status=$?" # third job I think
# stdout: status=7
## stdout: status=7
### wait -n
#### wait -n
{ sleep 0.09; exit 9; } &
{ sleep 0.07; exit 7; } &
wait -n
echo "status=$?"
wait -n
echo "status=$?"
# stdout-json: "status=7\nstatus=9\n"
# N-I dash stdout-json: "status=2\nstatus=2\n"
# N-I mksh stdout-json: "status=1\nstatus=1\n"
## stdout-json: "status=7\nstatus=9\n"
## N-I dash stdout-json: "status=2\nstatus=2\n"
## N-I mksh stdout-json: "status=1\nstatus=1\n"
### Async for loop
#### Async for loop
for i in 1 2 3; do
echo $i
sleep 0.0$i
done &
wait
# stdout-json: "1\n2\n3\n"
# status: 0
## stdout-json: "1\n2\n3\n"
## status: 0
### Background process doesn't affect parent
#### Background process doesn't affect parent
echo ${foo=1}
echo $foo
echo ${bar=2} &
wait
echo $bar # bar is NOT SET in the parent process
# stdout-json: "1\n1\n2\n\n"
## stdout-json: "1\n1\n2\n\n"
View
@@ -5,52 +5,52 @@
# https://lobste.rs/s/xhtim1/problems_with_shells_test_builtin_what
# http://alangrow.com/blog/shell-quirk-assign-from-heredoc
### Blog Post Example
#### Blog Post Example
paths=`tr '\n' ':' | sed -e 's/:$//'`<<EOPATHS
/foo
/bar
/baz
EOPATHS
echo "$paths"
# stdout: /foo:/bar:/baz
## stdout: /foo:/bar:/baz
### Blog Post Example Fix
#### Blog Post Example Fix
paths=`tr '\n' ':' | sed -e 's/:$//'<<EOPATHS
/foo
/bar
/baz
EOPATHS`
echo "$paths"
# stdout-json: "/foo\n/bar\n/baz\n"
## stdout-json: "/foo\n/bar\n/baz\n"
### Rewrite of Blog Post Example
#### Rewrite of Blog Post Example
paths=$(tr '\n' ':' | sed -e 's/:$//' <<EOPATHS
/foo
/bar
/baz
EOPATHS
)
echo "$paths"
# stdout-json: "/foo\n/bar\n/baz\n"
## stdout-json: "/foo\n/bar\n/baz\n"
### Simpler example
#### Simpler example
foo=`cat`<<EOM
hello world
EOM
echo "$foo"
# stdout: hello world
## stdout: hello world
### ` after here doc delimiter
#### ` after here doc delimiter
foo=`cat <<EOM
hello world
EOM`
echo "$foo"
# stdout: hello world
## stdout: hello world
### ` on its own line
#### ` on its own line
foo=`cat <<EOM
hello world
EOM
`
echo "$foo"
# stdout: hello world
## stdout: hello world
View
@@ -5,57 +5,57 @@
# Fun game: try to come up with an expression that behaves differently on ALL
# FOUR shells.
### ${##}
#### ${##}
set -- $(seq 25)
echo ${##}
# stdout: 2
## stdout: 2
### ${###}
#### ${###}
set -- $(seq 25)
echo ${###}
# stdout: 25
# N-I osh stdout-json: ""
# N-I osh status: 2
## stdout: 25
## N-I osh stdout-json: ""
## N-I osh status: 2
### ${####}
#### ${####}
set -- $(seq 25)
echo ${####}
# stdout: 25
# N-I osh stdout-json: ""
# N-I osh status: 2
## stdout: 25
## N-I osh stdout-json: ""
## N-I osh status: 2
### ${##2}
#### ${##2}
set -- $(seq 25)
echo ${##2}
# stdout: 5
# N-I osh stdout-json: ""
# N-I osh status: 2
## stdout: 5
## N-I osh stdout-json: ""
## N-I osh status: 2
### ${###2}
#### ${###2}
set -- $(seq 25)
echo ${###2}
# stdout: 5
# BUG mksh stdout: 25
# N-I osh stdout-json: ""
# N-I osh status: 2
## stdout: 5
## BUG mksh stdout: 25
## N-I osh stdout-json: ""
## N-I osh status: 2
### ${1####}
#### ${1####}
set -- '####'
echo ${1####}
# stdout: ##
## stdout: ##
### ${1#'###'}
#### ${1#'###'}
set -- '####'
echo ${1#'###'}
# stdout: #
## stdout: #
### ${#1#'###'}
#### ${#1#'###'}
set -- '####'
echo ${#1#'###'}
# dash and zsh accept; mksh/bash/osh don't.
# status: 2
# stdout-json: ""
# OK dash/zsh status: 0
# OK dash stdout: 4
# OK zsh stdout: 1
# N-I bash/mksh status: 1
## status: 2
## stdout-json: ""
## OK dash/zsh status: 0
## OK dash stdout: 4
## OK zsh stdout: 1
## N-I bash/mksh status: 1
View
@@ -3,48 +3,48 @@
# Tests for the blog.
#
### -a
#### -a
[ -a ]
echo status=$?
# stdout: status=0
## stdout: status=0
### -a -a
#### -a -a
[ -a -a ]
echo status=$?
# stdout: status=1
## stdout: status=1
### -a -a -a
#### -a -a -a
[ -a -a -a ]
echo status=$?
# stdout: status=0
# BUG mksh stdout: status=2
## stdout: status=0
## BUG mksh stdout: status=2
### -a -a -a -a
#### -a -a -a -a
[ -a -a -a -a ]
echo status=$?
# stdout: status=1
# BUG bash stdout: status=2
## stdout: status=1
## BUG bash stdout: status=2
### -a -a -a -a -a
#### -a -a -a -a -a
[ -a -a -a -a -a ]
echo status=$?
# stdout: status=1
# BUG dash/zsh stdout: status=0
## stdout: status=1
## BUG dash/zsh stdout: status=0
### -a -a -a -a -a -a
#### -a -a -a -a -a -a
[ -a -a -a -a -a -a ]
echo status=$?
# stdout: status=2
# BUG dash/zsh stdout: status=1
## stdout: status=2
## BUG dash/zsh stdout: status=1
### -a -a -a -a -a -a -a
#### -a -a -a -a -a -a -a
[ -a -a -a -a -a -a -a ]
echo status=$?
# stdout: status=1
# BUG bash stdout: status=2
# BUG dash/zsh stdout: status=0
## stdout: status=1
## BUG bash stdout: status=2
## BUG dash/zsh stdout: status=0
### -a -a -a -a -a -a -a -a
#### -a -a -a -a -a -a -a -a
[ -a -a -a -a -a -a -a -a ]
echo status=$?
# stdout: status=1
## stdout: status=1
View
@@ -1,243 +1,243 @@
#!/usr/bin/env bash
### no expansion
#### no expansion
echo {foo}
# stdout: {foo}
## stdout: {foo}
### incomplete trailing expansion
#### incomplete trailing expansion
echo {a,b}_{
# stdout: a_{ b_{
# OK osh stdout: {a,b}_{
## stdout: a_{ b_{
## OK osh stdout: {a,b}_{
### partial leading expansion
#### partial leading expansion
echo }_{a,b}
# stdout: }_a }_b
# OK osh stdout: }_{a,b}
## stdout: }_a }_b
## OK osh stdout: }_{a,b}
### partial leading expansion 2
#### partial leading expansion 2
echo {x}_{a,b}
# stdout: {x}_a {x}_b
# OK osh stdout: {x}_{a,b}
## stdout: {x}_a {x}_b
## OK osh stdout: {x}_{a,b}
### } in expansion
#### } in expansion
# hm they treat this the SAME. Leftmost { is matched by first }, and then
# there is another } as the postfix.
echo {a,b}}
# stdout: a} b}
# status: 0
# OK osh stdout: {a,b}}
# OK zsh stdout-json: ""
# OK zsh status: 1
## stdout: a} b}
## status: 0
## OK osh stdout: {a,b}}
## OK zsh stdout-json: ""
## OK zsh status: 1
### single expansion
#### single expansion
echo {foo,bar}
# stdout: foo bar
## stdout: foo bar
### double expansion
#### double expansion
echo {a,b}_{c,d}
# stdout: a_c a_d b_c b_d
## stdout: a_c a_d b_c b_d
### triple expansion
#### triple expansion
echo {0,1}{0,1}{0,1}
# stdout: 000 001 010 011 100 101 110 111
## stdout: 000 001 010 011 100 101 110 111
### double expansion with single and double quotes
#### double expansion with single and double quotes
echo {'a',b}_{c,"d"}
# stdout: a_c a_d b_c b_d
## stdout: a_c a_d b_c b_d
### expansion with mixed quotes
#### expansion with mixed quotes
echo -{\X"b",'cd'}-
# stdout: -Xb- -cd-
## stdout: -Xb- -cd-
### expansion with simple var
#### expansion with simple var
a=A
echo -{$a,b}-
# stdout: -A- -b-
## stdout: -A- -b-
### double expansion with simple var -- bash bug
#### double expansion with simple var -- bash bug
# bash is inconsistent with the above
a=A
echo {$a,b}_{c,d}
# stdout: A_c A_d b_c b_d
# BUG bash stdout: b_c b_d
## stdout: A_c A_d b_c b_d
## BUG bash stdout: b_c b_d
### double expansion with braced variable
#### double expansion with braced variable
# This fixes it
a=A
echo {${a},b}_{c,d}
# stdout: A_c A_d b_c b_d
## stdout: A_c A_d b_c b_d
### double expansion with literal and simple var
#### double expansion with literal and simple var
a=A
echo {_$a,b}_{c,d}
# stdout: _A_c _A_d b_c b_d
# BUG bash stdout: _ _ b_c b_d
## stdout: _A_c _A_d b_c b_d
## BUG bash stdout: _ _ b_c b_d
### expansion with command sub
#### expansion with command sub
a=A
echo -{$(echo a),b}-
# stdout: -a- -b-
## stdout: -a- -b-
### expansion with arith sub
#### expansion with arith sub
a=A
echo -{$((1 + 2)),b}-
# stdout: -3- -b-
## stdout: -3- -b-
### double expansion with escaped literals
#### double expansion with escaped literals
a=A
echo -{\$,\[,\]}-
# stdout: -$- -[- -]-
## stdout: -$- -[- -]-
### { in expansion
#### { in expansion
# bash and mksh treat this differently. bash treats the
# first { is a prefix. I think it's harder to read, and \{{a,b} should be
# required.
echo {{a,b}
# stdout: {{a,b}
# BUG bash/zsh stdout: {a {b
## stdout: {{a,b}
## BUG bash/zsh stdout: {a {b
### quoted { in expansion
#### quoted { in expansion
echo \{{a,b}
# stdout: {a {b
## stdout: {a {b
### Empty expansion
#### Empty expansion
echo a{X,,Y}b
# stdout: aXb ab aYb
## stdout: aXb ab aYb
### Empty alternative
#### Empty alternative
# zsh and mksh don't do word elision, probably because they do brace expansion
# AFTER variable substitution.
argv.py {X,,Y,}
# stdout: ['X', 'Y']
# OK mksh/zsh stdout: ['X', '', 'Y', '']
# status: 0
## stdout: ['X', 'Y']
## OK mksh/zsh stdout: ['X', '', 'Y', '']
## status: 0
### Empty alternative with empty string suffix
#### Empty alternative with empty string suffix
# zsh and mksh don't do word elision, probably because they do brace expansion
# AFTER variable substitution.
argv.py {X,,Y,}''
# stdout: ['X', '', 'Y', '']
# status: 0
## stdout: ['X', '', 'Y', '']
## status: 0
### nested brace expansion
#### nested brace expansion
echo -{A,={a,b}=,B}-
# stdout: -A- -=a=- -=b=- -B-
## stdout: -A- -=a=- -=b=- -B-
### triple nested brace expansion
#### triple nested brace expansion
echo -{A,={a,.{x,y}.,b}=,B}-
# stdout: -A- -=a=- -=.x.=- -=.y.=- -=b=- -B-
## stdout: -A- -=a=- -=.x.=- -=.y.=- -=b=- -B-
### nested and double brace expansion
#### nested and double brace expansion
echo -{A,={a,b}{c,d}=,B}-
# stdout: -A- -=ac=- -=ad=- -=bc=- -=bd=- -B-
## stdout: -A- -=ac=- -=ad=- -=bc=- -=bd=- -B-
### expansion on RHS of assignment
#### expansion on RHS of assignment
# I think bash's behavior is more consistent. No splitting either.
v={X,Y}
echo $v
# stdout: {X,Y}
# BUG mksh stdout: X Y
## stdout: {X,Y}
## BUG mksh stdout: X Y
### no expansion with RHS assignment
#### no expansion with RHS assignment
{v,x}=X
# status: 127
# stdout-json: ""
# OK zsh status: 1
## status: 127
## stdout-json: ""
## OK zsh status: 1
### Tilde expansion
#### Tilde expansion
HOME=/home/foo
echo ~
HOME=/home/bar
echo ~
# stdout-json: "/home/foo\n/home/bar\n"
## stdout-json: "/home/foo\n/home/bar\n"
### Tilde expansion with brace expansion
#### Tilde expansion with brace expansion
# The brace expansion happens FIRST. After that, the second token has tilde
# FIRST, so it gets expanded. The first token has an unexpanded tilde, because
# it's not in the leading position.
# NOTE: mksh gives different behavior! So it probably doesn't matter that
# much...
HOME=/home/bob
echo {foo~,~}/bar
# stdout: foo~/bar /home/bob/bar
# OK mksh stdout: foo~/bar ~/bar
## stdout: foo~/bar /home/bob/bar
## OK mksh stdout: foo~/bar ~/bar
### Two kinds of tilde expansion
#### Two kinds of tilde expansion
# ~/foo and ~bar
HOME=/home/bob
echo ~{/src,root}
# stdout: /home/bob/src /root
# OK mksh stdout: ~/src ~root
## stdout: /home/bob/src /root
## OK mksh stdout: ~/src ~root
### Tilde expansion come before var expansion
#### Tilde expansion come before var expansion
HOME=/home/bob
foo=~
echo $foo
foo='~'
echo $foo
# In the second instance, we expand into a literal ~, and since var expansion
# comes after tilde expansion, it is NOT tried again.
# stdout-json: "/home/bob\n~\n"
## stdout-json: "/home/bob\n~\n"
### Number range expansion
#### Number range expansion
echo -{1..8..3}-
# stdout: -1- -4- -7-
# N-I mksh stdout: -{1..8..3}-
## stdout: -1- -4- -7-
## N-I mksh stdout: -{1..8..3}-
### Ascending number range expansion with negative step
#### Ascending number range expansion with negative step
echo -{1..8..-3}-
# stdout: -1- -4- -7-
# OK zsh stdout: -7- -4- -1-
# N-I mksh stdout: -{1..8..-3}-
## stdout: -1- -4- -7-
## OK zsh stdout: -7- -4- -1-
## N-I mksh stdout: -{1..8..-3}-
### Descending number range expansion
#### Descending number range expansion
echo -{8..1..3}-
# stdout: -8- -5- -2-
# N-I mksh stdout: -{8..1..3}-
## stdout: -8- -5- -2-
## N-I mksh stdout: -{8..1..3}-
### Descending number range expansion with negative step
#### Descending number range expansion with negative step
echo -{8..1..-3}-
# stdout: -8- -5- -2-
# OK zsh stdout: -2- -5- -8-
# N-I mksh stdout: -{8..1..-3}-
## stdout: -8- -5- -2-
## OK zsh stdout: -2- -5- -8-
## N-I mksh stdout: -{8..1..-3}-
### Char range expansion
#### Char range expansion
echo -{a..e}-
# stdout: -a- -b- -c- -d- -e-
# N-I mksh stdout: -{a..e}-
## stdout: -a- -b- -c- -d- -e-
## N-I mksh stdout: -{a..e}-
### Char range expansion with step
#### Char range expansion with step
echo -{a..e..2}- -{a..e..-2}-
# stdout: -a- -c- -e- -a- -c- -e-
# N-I mksh/zsh stdout: -{a..e..2}- -{a..e..-2}-
## stdout: -a- -c- -e- -a- -c- -e-
## N-I mksh/zsh stdout: -{a..e..2}- -{a..e..-2}-
### Descending char range expansion
#### Descending char range expansion
echo -{e..a..2}- -{e..a..-2}-
# stdout: -e- -c- -a- -e- -c- -a-
# N-I mksh/zsh stdout: -{e..a..2}- -{e..a..-2}-
## stdout: -e- -c- -a- -e- -c- -a-
## N-I mksh/zsh stdout: -{e..a..2}- -{e..a..-2}-
### Fixed width number range expansion
#### Fixed width number range expansion
echo -{01..03}-
# stdout: -01- -02- -03-
# N-I mksh stdout: -{01..03}-
## stdout: -01- -02- -03-
## N-I mksh stdout: -{01..03}-
### Inconsistent fixed width number range expansion
#### Inconsistent fixed width number range expansion
# zsh uses the first one, bash uses the max width?
echo -{01..003}-
# stdout: -001- -002- -003-
# OK zsh stdout: -01- -02- -03-
# N-I mksh stdout: -{01..003}-
## stdout: -001- -002- -003-
## OK zsh stdout: -01- -02- -03-
## N-I mksh stdout: -{01..003}-
### Inconsistent fixed width number range expansion
#### Inconsistent fixed width number range expansion
# zsh uses the first width, bash uses the max width?
echo -{01..3}-
# stdout: -01- -02- -03-
# N-I mksh stdout: -{01..3}-
## stdout: -01- -02- -03-
## N-I mksh stdout: -{01..3}-
### Side effect in expansion
#### Side effect in expansion
# bash is the only one that does it first. I guess since this is
# non-POSIX anyway, follow bash?
i=0
echo {a,b,c}-$((i++))
# stdout: a-0 b-1 c-2
# OK mksh/zsh stdout: a-0 b-0 c-0
## stdout: a-0 b-1 c-2
## OK mksh/zsh stdout: a-0 b-0 c-0
View
@@ -1,13 +1,13 @@
#!/usr/bin/env bash
### echo keyword
#### echo keyword
echo done
# stdout: done
## stdout: done
### if/else
#### if/else
if false; then
echo THEN
else
echo ELSE
fi
# stdout: ELSE
## stdout: ELSE
View
@@ -4,36 +4,36 @@
#
# NOTE: Aliases don't work in batch mode! Interactive only.
### type -t builtin -> function
#### type -t builtin -> function
f() { echo hi; }
type -t f
# stdout-json: "function\n"
## stdout-json: "function\n"
### type -t builtin -> builtin
#### type -t builtin -> builtin
type -t echo read : [ declare local break continue
# stdout-json: "builtin\nbuiltin\nbuiltin\nbuiltin\nbuiltin\nbuiltin\nbuiltin\nbuiltin\n"
## stdout-json: "builtin\nbuiltin\nbuiltin\nbuiltin\nbuiltin\nbuiltin\nbuiltin\nbuiltin\n"
### type -t builtin -> keyword
#### type -t builtin -> keyword
type -t for time ! fi do {
# stdout-json: "keyword\nkeyword\nkeyword\nkeyword\nkeyword\nkeyword\n"
## stdout-json: "keyword\nkeyword\nkeyword\nkeyword\nkeyword\nkeyword\n"
### type -t builtin -> file
#### type -t builtin -> file
type -t find xargs
# stdout-json: "file\nfile\n"
## stdout-json: "file\nfile\n"
### type -t builtin -> not found
#### type -t builtin -> not found
type -t echo ZZZ find =
echo status=$?
# stdout-json: "builtin\nfile\nstatus=1\n"
## stdout-json: "builtin\nfile\nstatus=1\n"
### help
#### help
help
help help
# status: 0
## status: 0
### bad help topic
#### bad help topic
help ZZZ 2>$TMP/err.txt
echo "help=$?"
cat $TMP/err.txt | grep -i 'no help topics' >/dev/null
echo "grep=$?"
# stdout-json: "help=1\ngrep=0\n"
## stdout-json: "help=1\ngrep=0\n"
View
@@ -1,14 +1,14 @@
#!/bin/bash
### dirs builtin
#### dirs builtin
cd /
dirs
# status: 0
## status: 0
## STDOUT:
/
## END
### dirs -c to clear the stack
#### dirs -c to clear the stack
set -o errexit
cd /
pushd /tmp >/dev/null # zsh pushd doesn't print anything, but bash does
@@ -25,7 +25,7 @@ dirs
/tmp
## END
### dirs -v to print numbered stack, one entry per line
#### dirs -v to print numbered stack, one entry per line
set -o errexit
cd /
pushd /tmp >/dev/null
@@ -48,7 +48,7 @@ dirs -v
# zsh uses tabs
## OK zsh stdout-json: "--\n0\t/tmp\n1\t/\n--\n0\t/lib\n1\t/tmp\n2\t/\n"
### dirs -p to print one entry per line
#### dirs -p to print one entry per line
set -o errexit
cd /
pushd /tmp >/dev/null
@@ -67,7 +67,7 @@ dirs -p
/
## END
### dirs -l to print in long format, no tilde prefix
#### dirs -l to print in long format, no tilde prefix
# Can't use the OSH test harness for this because
# /home/<username> may be included in a path.
cd /
@@ -76,39 +76,39 @@ mkdir -p $HOME/oil_test
pushd $HOME/oil_test >/dev/null
dirs
dirs -l
# status: 0
## status: 0
## STDOUT:
~/oil_test /
/tmp/oil_test /
## END
### dirs to print using tilde-prefix format
#### dirs to print using tilde-prefix format
cd /
HOME=/tmp
mkdir -p $HOME/oil_test
pushd $HOME/oil_test >/dev/null
dirs
# stdout: ~/oil_test /
# status: 0
## stdout: ~/oil_test /
## status: 0
### dirs test converting true home directory to tilde
#### dirs test converting true home directory to tilde
cd /
HOME=/tmp
mkdir -p $HOME/oil_test/$HOME
pushd $HOME/oil_test/$HOME >/dev/null
dirs
# stdout: ~/oil_test/tmp /
# status: 0
## stdout: ~/oil_test/tmp /
## status: 0
### dirs don't convert to tilde when $HOME is substring
#### dirs don't convert to tilde when $HOME is substring
cd /
mkdir -p /tmp/oil_test
mkdir -p /tmp/oil_tests
HOME=/tmp/oil_test
pushd /tmp/oil_tests
dirs
### dirs tilde test when $HOME is exactly $PWD
#### dirs tilde test when $HOME is exactly $PWD
cd /
mkdir -p /tmp/oil_test
HOME=/tmp/oil_test
@@ -122,16 +122,16 @@ dirs
~ /
## END
### dirs test of path alias `..`
#### dirs test of path alias `..`
cd /tmp
pushd .. >/dev/null
dirs
# stdout: / /tmp
# status: 0
## stdout: / /tmp
## status: 0
### dirs test of path alias `.`
#### dirs test of path alias `.`
cd /tmp
pushd . >/dev/null
dirs
# stdout: /tmp /tmp
# status: 0
## stdout: /tmp /tmp
## status: 0
View
@@ -1,31 +1,31 @@
#!/usr/bin/env bash
### Eval
#### Eval
eval "a=3"
echo $a
# stdout: 3
## stdout: 3
### Source
#### Source
lib=$TMP/spec-test-lib.sh
echo 'LIBVAR=libvar' > $lib
. $lib # dash doesn't have source
echo $LIBVAR
# stdout: libvar
## stdout: libvar
### Source nonexistent
#### Source nonexistent
source /nonexistent/path
echo status=$?
# stdout: status=1
# OK dash/zsh stdout: status=127
## stdout: status=1
## OK dash/zsh stdout: status=127
### Source with no arguments
#### Source with no arguments
source
echo status=$?
# stdout: status=1
# OK bash stdout: status=2
# OK dash stdout: status=127
## stdout: status=1
## OK bash stdout: status=2
## OK dash stdout: status=127
### Source with arguments
#### Source with arguments
. spec/testdata/show-argv.sh foo bar # dash doesn't have source
## STDOUT:
show-argv: foo bar
@@ -34,7 +34,7 @@ show-argv: foo bar
show-argv:
## END
### Source from a function, mutating argv and defining a local var
#### Source from a function, mutating argv and defining a local var
f() {
. spec/testdata/source-argv.sh # no argv
. spec/testdata/source-argv.sh args to src # new argv
View
@@ -4,37 +4,37 @@
#
# NOTE: Aliases don't work in batch mode! Interactive only.
### getopts empty
#### getopts empty
set --
getopts 'a:' opt
echo "status=$? opt=$opt OPTARG=$OPTARG"
# stdout: status=1 opt=? OPTARG=
## stdout: status=1 opt=? OPTARG=
### getopts sees unknown arg
#### getopts sees unknown arg
set -- -Z
getopts 'a:' opt
echo "status=$? opt=$opt OPTARG=$OPTARG"
# stdout: status=0 opt=? OPTARG=
## stdout: status=0 opt=? OPTARG=
### getopts three invocations
#### getopts three invocations
set -- -h -c foo
getopts 'hc:' opt
echo status=$? opt=$opt
getopts 'hc:' opt
echo status=$? opt=$opt
getopts 'hc:' opt
echo status=$? opt=$opt
# stdout-json: "status=0 opt=h\nstatus=0 opt=c\nstatus=1 opt=?\n"
## stdout-json: "status=0 opt=h\nstatus=0 opt=c\nstatus=1 opt=?\n"
### getopts resets OPTARG
#### getopts resets OPTARG
set -- -c foo -h
getopts 'hc:' opt
echo status=$? opt=$opt OPTARG=$OPTARG
getopts 'hc:' opt
echo status=$? opt=$opt OPTARG=$OPTARG
# stdout-json: "status=0 opt=c OPTARG=foo\nstatus=0 opt=h OPTARG=\n"
## stdout-json: "status=0 opt=c OPTARG=foo\nstatus=0 opt=h OPTARG=\n"
### Basic getopts invocation
#### Basic getopts invocation
set -- -h -c foo x y z
FLAG_h=0
FLAG_c=''
@@ -46,9 +46,9 @@ while getopts "hc:" opt; do
done
shift $(( OPTIND - 1 ))
echo h=$FLAG_h c=$FLAG_c optind=$OPTIND argv=$@
# stdout: h=1 c=foo optind=4 argv=x y z
## stdout: h=1 c=foo optind=4 argv=x y z
### getopts with invalid flag
#### getopts with invalid flag
set -- -h -x
while getopts "hc:" opt; do
case $opt in
@@ -58,10 +58,10 @@ while getopts "hc:" opt; do
esac
done
echo status=$?
# stdout: ERROR 3
# status: 2
## stdout: ERROR 3
## status: 2
### getopts missing required argument
#### getopts missing required argument
set -- -h -c
while getopts "hc:" opt; do
case $opt in
@@ -71,10 +71,10 @@ while getopts "hc:" opt; do
esac
done
echo status=$?
# stdout: ERROR 3
# status: 2
## stdout: ERROR 3
## status: 2
### getopts doesn't look for flags after args
#### getopts doesn't look for flags after args
set -- x -h -c y
FLAG_h=0
FLAG_c=''
@@ -86,9 +86,9 @@ while getopts "hc:" opt; do
done
shift $(( OPTIND - 1 ))
echo h=$FLAG_h c=$FLAG_c optind=$OPTIND argv=$@
# stdout: h=0 c= optind=1 argv=x -h -c y
## stdout: h=0 c= optind=1 argv=x -h -c y
### getopts with explicit args
#### getopts with explicit args
# NOTE: Alpine doesn't appear to use this
FLAG_h=0
FLAG_c=''
@@ -100,13 +100,13 @@ while getopts "hc:" opt -h -c foo x y z; do
esac
done
echo h=$FLAG_h c=$FLAG_c optind=$OPTIND argv=$@
# stdout: h=1 c=foo optind=4 argv=
## stdout: h=1 c=foo optind=4 argv=
### OPTIND
#### OPTIND
echo $OPTIND
# stdout: 1
## stdout: 1
### OPTIND after multiple getopts with same spec
#### OPTIND after multiple getopts with same spec
while getopts "hc:" opt; do
echo '-'
done
@@ -123,10 +123,10 @@ while getopts "hc:" opt; do
echo '-'
done
echo $OPTIND
# stdout-json: "1\n-\n-\n4\n1\n"
# BUG mksh/osh stdout-json: "1\n-\n-\n4\n4\n"
## stdout-json: "1\n-\n-\n4\n1\n"
## BUG mksh/osh stdout-json: "1\n-\n-\n4\n4\n"
### OPTIND after multiple getopts with different spec
#### OPTIND after multiple getopts with different spec
# Wow this is poorly specified! A fundamental design problem with the global
# variable OPTIND.
set -- -a
@@ -146,11 +146,11 @@ while getopts "f:" opt; do
echo '_'
done
echo $OPTIND
# stdout-json: ".\n2\n-\n-\n5\n2\n"
# BUG ash/dash stdout-json: ".\n2\n-\n-\n-\n5\n_\n2\n"
# BUG mksh/osh stdout-json: ".\n2\n-\n-\n5\n5\n"
## stdout-json: ".\n2\n-\n-\n5\n2\n"
## BUG ash/dash stdout-json: ".\n2\n-\n-\n-\n5\n_\n2\n"
## BUG mksh/osh stdout-json: ".\n2\n-\n-\n5\n5\n"
### OPTIND narrowed down
#### OPTIND narrowed down
FLAG_a=
FLAG_b=
FLAG_c=
@@ -176,11 +176,11 @@ while getopts "cde:" opt; do
done
echo a=$FLAG_a b=$FLAG_b c=$FLAG_c d=$FLAG_d e=$FLAG_e
# stdout: a=1 b= c=1 d=1 e=E
# BUG bash/mksh/osh stdout: a=1 b= c= d=1 e=E
## stdout: a=1 b= c=1 d=1 e=E
## BUG bash/mksh/osh stdout: a=1 b= c= d=1 e=E
### Getopts parses the function's arguments
#### Getopts parses the function's arguments
# NOTE: GLOBALS are set, not locals! Bad interface.
FLAG_h=0
FLAG_c=''
@@ -195,5 +195,5 @@ myfunc() {
set -- -h -c foo x y z
myfunc -c bar
echo h=$FLAG_h c=$FLAG_c opt=$opt optind=$OPTIND argv=$@
# stdout: h=0 c=bar opt=? optind=3 argv=-h -c foo x y z
## stdout: h=0 c=bar opt=? optind=3 argv=-h -c foo x y z
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -1,10 +1,10 @@
#!/usr/bin/env bash
### zero args: [ ]
#### zero args: [ ]
[ ] || echo false
# stdout: false
## stdout: false
### one arg: [ x ] where x is one of '=' '!' '(' ']'
#### one arg: [ x ] where x is one of '=' '!' '(' ']'
[ = ]
echo status=$?
[ ] ]
@@ -13,23 +13,23 @@ echo status=$?
echo status=$?
[ '(' ]
echo status=$?
# stdout-json: "status=0\nstatus=0\nstatus=0\nstatus=0\n"
## stdout-json: "status=0\nstatus=0\nstatus=0\nstatus=0\n"
### one arg: empty string is false. Equivalent to -n.
#### one arg: empty string is false. Equivalent to -n.
test 'a' && echo true
test '' || echo false
# stdout-json: "true\nfalse\n"
## stdout-json: "true\nfalse\n"
### -a as unary operator (alias of -e)
#### -a as unary operator (alias of -e)
# NOT IMPLEMENTED FOR OSH, but could be later. See comment in core/id_kind.py.
[ -a / ]
echo status=$?
[ -a /nonexistent ]
echo status=$?
# stdout-json: "status=0\nstatus=1\n"
# N-I dash stdout-json: "status=2\nstatus=2\n"
## stdout-json: "status=0\nstatus=1\n"
## N-I dash stdout-json: "status=2\nstatus=2\n"
### two args: -z with = ! ( ]
#### two args: -z with = ! ( ]
[ -z = ]
echo status=$?
[ -z ] ]
@@ -38,9 +38,9 @@ echo status=$?
echo status=$?
[ -z '(' ]
echo status=$?
# stdout-json: "status=1\nstatus=1\nstatus=1\nstatus=1\n"
## stdout-json: "status=1\nstatus=1\nstatus=1\nstatus=1\n"
### three args
#### three args
[ foo = '' ]
echo status=$?
[ foo -a '' ]
@@ -51,16 +51,16 @@ echo status=$?
echo status=$?
[ \( foo \) ]
echo status=$?
# stdout-json: "status=1\nstatus=1\nstatus=0\nstatus=0\nstatus=0\n"
## stdout-json: "status=1\nstatus=1\nstatus=0\nstatus=0\nstatus=0\n"
### four args
#### four args
[ ! foo = foo ]
echo status=$?
[ \( -z foo \) ]
echo status=$?
# stdout-json: "status=1\nstatus=1\n"
## stdout-json: "status=1\nstatus=1\n"
### test with extra args is syntax error
#### test with extra args is syntax error
test -n x ]
echo status=$?
test -n x y
@@ -70,7 +70,7 @@ status=2
status=2
## END
### ] syntax errors
#### ] syntax errors
[
echo status=$?
test # not a syntax error
@@ -89,129 +89,129 @@ status=2
status=2
## END
### -n
#### -n
test -n 'a' && echo true
test -n '' || echo false
# stdout-json: "true\nfalse\n"
## stdout-json: "true\nfalse\n"
### ! -a
#### ! -a
[ -z '' -a ! -z x ]
echo status=$?
# stdout: status=0
## stdout: status=0
### -o
#### -o
[ -z x -o ! -z x ]
echo status=$?
# stdout: status=0
## stdout: status=0
### ( )
#### ( )
[ -z '' -a '(' ! -z x ')' ]
echo status=$?
# stdout: status=0
## stdout: status=0
### ( ) ! -a -o with system version of [
#### ( ) ! -a -o with system version of [
command [ --version
command [ -z '' -a '(' ! -z x ')' ] && echo true
# stdout: true
## stdout: true
### == is alias for =
#### == is alias for =
[ a = a ] && echo true
[ a == a ] && echo true
# stdout-json: "true\ntrue\n"
# BUG dash stdout-json: "true\n"
# BUG dash status: 2
## stdout-json: "true\ntrue\n"
## BUG dash stdout-json: "true\n"
## BUG dash status: 2
### == and = does not do glob
#### == and = does not do glob
[ abc = 'a*' ]
echo status=$?
[ abc == 'a*' ]
echo status=$?
# stdout-json: "status=1\nstatus=1\n"
# N-I dash stdout-json: "status=1\nstatus=2\n"
## stdout-json: "status=1\nstatus=1\n"
## N-I dash stdout-json: "status=1\nstatus=2\n"
### [ with op variable
#### [ with op variable
# OK -- parsed AFTER evaluation of vars
op='='
[ a $op a ] && echo true
[ a $op b ] || echo false
# status: 0
# stdout-json: "true\nfalse\n"
## status: 0
## stdout-json: "true\nfalse\n"
### [ with unquoted empty var
#### [ with unquoted empty var
empty=''
[ $empty = '' ] && echo true
# status: 2
## status: 2
### [ compare with literal -f
#### [ compare with literal -f
# Hm this is the same
var=-f
[ $var = -f ] && echo true
[ '-f' = $var ] && echo true
# stdout-json: "true\ntrue\n"
## stdout-json: "true\ntrue\n"
### [ '(' foo ] is runtime syntax error
#### [ '(' foo ] is runtime syntax error
[ '(' foo ]
echo status=$?
# stdout: status=2
## stdout: status=2
### -z '>' implies two token lookahead
#### -z '>' implies two token lookahead
[ -z ] && echo true # -z is operand
[ -z '>' ] || echo false # -z is operator
[ -z '>' -- ] && echo true # -z is operand
# stdout-json: "true\nfalse\ntrue\n"
## stdout-json: "true\nfalse\ntrue\n"
### operator/operand ambiguity with ]
#### operator/operand ambiguity with ]
# bash parses this as '-z' AND ']', which is true. It's a syntax error in
# dash/mksh.
[ -z -a ] ]
echo status=$?
# stdout: status=0
# OK mksh stdout: status=2
# OK dash stdout: status=2
## stdout: status=0
## OK mksh stdout: status=2
## OK dash stdout: status=2
### operator/operand ambiguity with -a
#### operator/operand ambiguity with -a
# bash parses it as '-z' AND '-a'. It's a syntax error in mksh but somehow a
# runtime error in dash.
[ -z -a -a ]
echo status=$?
# stdout: status=0
# OK mksh stdout: status=2
# OK dash stdout: status=1
## stdout: status=0
## OK mksh stdout: status=2
## OK dash stdout: status=1
### -d
#### -d
test -d $TMP
echo status=$?
test -d $TMP/__nonexistent_Z_Z__
echo status=$?
# stdout-json: "status=0\nstatus=1\n"
## stdout-json: "status=0\nstatus=1\n"
### -x
#### -x
rm -f $TMP/x
echo 'echo hi' > $TMP/x
test -x $TMP/x || echo 'no'
chmod +x $TMP/x
test -x $TMP/x && echo 'yes'
test -x $TMP/__nonexistent__ || echo 'bad'
# stdout-json: "no\nyes\nbad\n"
## stdout-json: "no\nyes\nbad\n"
### -r
#### -r
echo '1' > $TMP/testr_yes
echo '2' > $TMP/testr_no
chmod -r $TMP/testr_no # remove read permission
test -r $TMP/testr_yes && echo 'yes'
test -r $TMP/testr_no || echo 'no'
# stdout-json: "yes\nno\n"
## stdout-json: "yes\nno\n"
### -w
#### -w
rm -f $TMP/testw_*
echo '1' > $TMP/testw_yes
echo '2' > $TMP/testw_no
chmod -w $TMP/testw_no # remove write permission
test -w $TMP/testw_yes && echo 'yes'
test -w $TMP/testw_no || echo 'no'
# stdout-json: "yes\nno\n"
## stdout-json: "yes\nno\n"
### -h and -L test for symlink
#### -h and -L test for symlink
tmp=$TMP/builtin-test-1
mkdir -p $tmp
touch $tmp/zz
@@ -236,19 +236,19 @@ dangling
dangling is not file
## END
### -t 1 for stdout
#### -t 1 for stdout
# There isn't way to get a terminal in the test environment?
[ -t 1 ]
echo status=$?
## stdout: status=1
### [ -t invalid ]
#### [ -t invalid ]
[ -t invalid ]
echo status=$?
## stdout: status=2
## BUG bash stdout: status=1
### -ot and -nt
#### -ot and -nt
touch -d 2017/12/31 $TMP/x
touch -d 2018/01/01 > $TMP/y
test $TMP/x -ot $TMP/y && echo 'older'
View
@@ -1,25 +1,25 @@
#!/bin/bash
### trap -l
#### trap -l
trap -l | grep INT >/dev/null
## status: 0
## N-I dash/mksh status: 1
### trap -p
#### trap -p
trap 'echo exit' EXIT
trap -p | grep EXIT >/dev/null
## status: 0
## N-I dash/mksh status: 1
### Register invalid trap
#### Register invalid trap
trap 'foo' SIGINVALID
## status: 1
### Remove invalid trap
#### Remove invalid trap
trap - SIGINVALID
## status: 1
### SIGINT and INT are aliases
#### SIGINT and INT are aliases
trap - SIGINT
echo $?
trap - INT
@@ -33,14 +33,14 @@ echo $?
0
## END
### Invalid trap invocation
#### Invalid trap invocation
trap 'foo'
echo status=$?
## stdout: status=1
## OK bash stdout: status=2
## BUG mksh stdout: status=0
### exit 1 when trap code string is invalid
#### exit 1 when trap code string is invalid
# All shells spew warnings to stderr, but don't actually exit! Bad!
trap 'echo <' EXIT
echo status=$?
@@ -50,14 +50,14 @@ echo status=$?
## BUG dash/bash status: 0
## BUG dash/bash stdout: status=0
### trap EXIT
#### trap EXIT
cleanup() {
echo "cleanup [$@]"
}
trap 'cleanup x y z' EXIT
## stdout: cleanup [x y z]
### trap DEBUG
#### trap DEBUG
debuglog() {
echo "debuglog [$@]"
}
@@ -75,7 +75,7 @@ debuglog [x y]
2
## END
### trap RETURN
#### trap RETURN
profile() {
echo "profile [$@]"
}
@@ -117,7 +117,7 @@ g
return-helper.sh
## END
### trap ERR and disable it
#### trap ERR and disable it
err() {
echo "err [$@] $?"
}
@@ -134,7 +134,7 @@ err [x y] 1
2
3
## END
# N-I dash STDOUT:
## N-I dash STDOUT:
1
2
3
View
@@ -4,64 +4,64 @@
#
# Also see assign.test.sh.
### Export sets a global variable
#### Export sets a global variable
# Even after you do export -n, it still exists.
f() { export GLOBAL=X; }
f
echo $GLOBAL
printenv.py GLOBAL
# stdout-json: "X\nX\n"
## stdout-json: "X\nX\n"
### Export sets a global variable that persists after export -n
#### Export sets a global variable that persists after export -n
f() { export GLOBAL=X; }
f
echo $GLOBAL
printenv.py GLOBAL
export -n GLOBAL
echo $GLOBAL
printenv.py GLOBAL
# stdout-json: "X\nX\nX\nNone\n"
# N-I mksh/dash stdout-json: "X\nX\n"
# N-I mksh status: 1
# N-I dash status: 2
## stdout-json: "X\nX\nX\nNone\n"
## N-I mksh/dash stdout-json: "X\nX\n"
## N-I mksh status: 1
## N-I dash status: 2
### export -n undefined is ignored
#### export -n undefined is ignored
set -o errexit
export -n undef
echo status=$?
# stdout: status=0
# N-I mksh/dash stdout-json: ""
# N-I mksh status: 1
# N-I dash status: 2
## stdout: status=0
## N-I mksh/dash stdout-json: ""
## N-I mksh status: 1
## N-I dash status: 2
### Export a global variable and unset it
#### Export a global variable and unset it
f() { export GLOBAL=X; }
f
echo $GLOBAL
printenv.py GLOBAL
unset GLOBAL
echo $GLOBAL
printenv.py GLOBAL
# stdout-json: "X\nX\n\nNone\n"
## stdout-json: "X\nX\n\nNone\n"
### Export existing global variables
#### Export existing global variables
G1=g1
G2=g2
export G1 G2
printenv.py G1 G2
# stdout-json: "g1\ng2\n"
## stdout-json: "g1\ng2\n"
### Export existing local variable
#### Export existing local variable
f() {
local L1=local1
export L1
printenv.py L1
}
f
printenv.py L1
# stdout-json: "local1\nNone\n"
## stdout-json: "local1\nNone\n"
### Export a local that shadows a global
#### Export a local that shadows a global
V=global
f() {
local V=local1
@@ -72,15 +72,15 @@ f
printenv.py V # exported local out of scope; global isn't exported yet
export V
printenv.py V # now it's exported
# stdout-json: "local1\nNone\nglobal\n"
## stdout-json: "local1\nNone\nglobal\n"
### Export a variable before defining it
#### Export a variable before defining it
export U
U=u
printenv.py U
# stdout: u
## stdout: u
### Exporting a parent func variable (dynamic scope)
#### Exporting a parent func variable (dynamic scope)
# The algorithm is to walk up the stack and export that one.
inner() {
export outer_var
@@ -96,101 +96,101 @@ outer() {
printenv.py outer_var
}
outer
# stdout-json: "before inner\nNone\ninner: X\nX\nafter inner\nX\n"
## stdout-json: "before inner\nNone\ninner: X\nX\nafter inner\nX\n"
### Dependent export setting
#### Dependent export setting
# FOO is not respected here either.
export FOO=foo v=$(printenv.py FOO)
echo "v=$v"
# stdout: v=None
## stdout: v=None
### Exporting a variable doesn't change it
#### Exporting a variable doesn't change it
old=$PATH
export PATH
new=$PATH
test "$old" = "$new" && echo "not changed"
# stdout: not changed
## stdout: not changed
### assign to readonly variable
#### assign to readonly variable
# bash doesn't abort unless errexit!
readonly foo=bar
foo=eggs
echo "status=$?" # nothing happens
# status: 1
# BUG bash stdout: status=1
# BUG bash status: 0
# OK dash/mksh status: 2
## status: 1
## BUG bash stdout: status=1
## BUG bash status: 0
## OK dash/mksh status: 2
### assign to readonly variable - errexit
#### assign to readonly variable - errexit
set -o errexit
readonly foo=bar
foo=eggs
echo "status=$?" # nothing happens
# status: 1
# OK dash/mksh status: 2
## status: 1
## OK dash/mksh status: 2
### Unset a variable
#### Unset a variable
foo=bar
echo foo=$foo
unset foo
echo foo=$foo
# stdout-json: "foo=bar\nfoo=\n"
## stdout-json: "foo=bar\nfoo=\n"
### Unset exit status
#### Unset exit status
V=123
unset V
echo status=$?
# stdout: status=0
## stdout: status=0
### Unset nonexistent variable
#### Unset nonexistent variable
unset ZZZ
echo status=$?
# stdout: status=0
## stdout: status=0
### Unset readonly variable
#### Unset readonly variable
# dash aborts the whole program. I'm also aborting the whole program because
# it's a programming error.
readonly R=foo
unset R
echo status=$?
# status: 0
# stdout: status=1
# OK dash status: 2
# OK dash stdout-json: ""
## status: 0
## stdout: status=1
## OK dash status: 2
## OK dash stdout-json: ""
### Unset a function without -f
#### Unset a function without -f
f() {
echo foo
}
f
unset f
f
# stdout: foo
# status: 127
# N-I dash/mksh status: 0
# N-I dash/mksh stdout-json: "foo\nfoo\n"
## stdout: foo
## status: 127
## N-I dash/mksh status: 0
## N-I dash/mksh stdout-json: "foo\nfoo\n"
### Unset has dynamic scope
#### Unset has dynamic scope
f() {
unset foo
}
foo=bar
echo foo=$foo
f
echo foo=$foo
# stdout-json: "foo=bar\nfoo=\n"
## stdout-json: "foo=bar\nfoo=\n"
### Unset -v
#### Unset -v
foo() {
echo "function foo"
}
foo=bar
unset -v foo
echo foo=$foo
foo
# stdout-json: "foo=\nfunction foo\n"
## stdout-json: "foo=\nfunction foo\n"
### Unset -f
#### Unset -f
foo() {
echo "function foo"
}
@@ -199,40 +199,40 @@ unset -f foo
echo foo=$foo
foo
echo status=$?
# stdout-json: "foo=bar\nstatus=127\n"
## stdout-json: "foo=bar\nstatus=127\n"
### Unset array member
#### Unset array member
a=(x y z)
unset 'a[1]'
echo "${a[@]}" len="${#a[@]}"
# stdout: x z len=2
# N-I dash status: 2
# N-I dash stdout-json: ""
## stdout: x z len=2
## N-I dash status: 2
## N-I dash stdout-json: ""
### Unset array member with expression
#### Unset array member with expression
i=1
a=(w x y z)
unset 'a[ i - 1 ]' a[i+1] # note: can't have space between a and [
echo "${a[@]}" len="${#a[@]}"
# stdout: x z len=2
# N-I dash status: 2
# N-I dash stdout-json: ""
## stdout: x z len=2
## N-I dash status: 2
## N-I dash stdout-json: ""
### Use local twice
#### Use local twice
f() {
local foo=bar
local foo
echo $foo
}
f
# stdout: bar
## stdout: bar
### Local without variable is still unset!
#### Local without variable is still unset!
set -o nounset
f() {
local foo
echo "[$foo]"
}
f
# status: 1
# OK dash status: 2
## status: 1
## OK dash status: 2
View
@@ -4,46 +4,46 @@
#
# https://www.reddit.com/r/oilshell/comments/5ykpi3/oildev_is_alive/
### : is special and prefix assignments persist after special builtins
#### : is special and prefix assignments persist after special builtins
# Bash only implements it behind the posix option
test -n "$BASH_VERSION" && set -o posix
foo=bar :
echo $foo
# stdout: bar
## stdout: bar
### true is not special
#### true is not special
foo=bar true
echo $foo
# stdout:
## stdout:
### Shift is special and the whole script exits if it returns non-zero
#### Shift is special and the whole script exits if it returns non-zero
test -n "$BASH_VERSION" && set -o posix
set -- a b
shift 3
echo status=$?
# stdout-json: ""
# status: 1
# OK dash status: 2
# BUG bash status: 0
# BUG bash stdout-json: "status=1\n"
## stdout-json: ""
## status: 1
## OK dash status: 2
## BUG bash status: 0
## BUG bash stdout-json: "status=1\n"
### Special builtins can't be redefined as functions
#### Special builtins can't be redefined as functions
# bash manual says they are 'found before' functions.
test -n "$BASH_VERSION" && set -o posix
export() {
echo 'export func'
}
export hi
echo status=$?
# status: 2
# BUG mksh status: 0
# BUG mksh stdout: status=0
## status: 2
## BUG mksh status: 0
## BUG mksh stdout: status=0
### Non-special builtins CAN be redefined as functions
#### Non-special builtins CAN be redefined as functions
test -n "$BASH_VERSION" && set -o posix
true() {
echo 'true func'
}
true hi
echo status=$?
# stdout-json: "true func\nstatus=0\n"
## stdout-json: "true func\nstatus=0\n"
View
@@ -1,39 +1,39 @@
#!/usr/bin/env bash
### exec builtin
#### exec builtin
exec echo hi
# stdout: hi
## stdout: hi
### exec builtin with redirects
#### exec builtin with redirects
exec 1>&2
echo 'to stderr'
# stdout-json: ""
# stderr: to stderr
## stdout-json: ""
## stderr: to stderr
### exec builtin with here doc
#### exec builtin with here doc
# This has in a separate file because both code and data can be read from
# stdin.
$SH spec/builtins-exec-here-doc-helper.sh
# stdout-json: "x=one\ny=two\nDONE\n"
## stdout-json: "x=one\ny=two\nDONE\n"
### cd and $PWD
#### cd and $PWD
cd /
echo $PWD
# stdout: /
## stdout: /
### $OLDPWD
#### $OLDPWD
cd /
cd $TMP
echo "old: $OLDPWD"
cd -
# stdout-json: "old: /\n/\n"
## stdout-json: "old: /\n/\n"
### pwd
#### pwd
cd /
pwd
# stdout: /
## stdout: /
### pwd -P
#### pwd -P
tmp=$TMP/builtins-pwd-1
mkdir -p $tmp
mkdir -p $tmp/symtarg
@@ -43,22 +43,22 @@ basename $(pwd -P)
cd $tmp
rmdir $tmp/symtarg
rm $tmp/symlink
# stdout: symtarg
## stdout: symtarg
### cd with no arguments
#### cd with no arguments
HOME=$TMP/home
mkdir -p $HOME
cd
test $(pwd) = "$HOME" && echo OK
# stdout: OK
## stdout: OK
### cd to nonexistent dir
#### cd to nonexistent dir
cd /nonexistent/dir
echo status=$?
# stdout: status=1
# OK dash/mksh stdout: status=2
## stdout: status=1
## OK dash/mksh stdout: status=2
### cd away from dir that was deleted
#### cd away from dir that was deleted
dir=$TMP/cd-nonexistent
mkdir -p $dir
cd $dir
@@ -71,12 +71,12 @@ cd-nonexistent
status=0
## END
### cd permits double bare dash
#### cd permits double bare dash
cd -- /
echo $PWD
# stdout: /
## stdout: /
### cd to non-symlink with -P
#### cd to non-symlink with -P
targ=$TMP/cd-symtarget
lnk=$TMP/cd-symlink
mkdir -p $targ
@@ -86,9 +86,9 @@ test $PWD = "$TMP/cd-symtarget" && echo OK
cd $TMP
rmdir $targ
rm $lnk
# stdout: OK
## stdout: OK
### cd to symlink default behavior
#### cd to symlink default behavior
targ=$TMP/cd-symtarget
lnk=$TMP/cd-symlink
mkdir -p $targ
@@ -98,9 +98,9 @@ test $PWD = "$TMP/cd-symlink" && echo OK
cd $TMP
rmdir $targ
rm $lnk
# stdout: OK
## stdout: OK
### cd to symlink with -L
#### cd to symlink with -L
targ=$TMP/cd-symtarget
lnk=$TMP/cd-symlink
mkdir -p $targ
@@ -110,9 +110,9 @@ test $PWD = "$TMP/cd-symlink" && echo OK
cd $TMP
rmdir $targ
rm $lnk
# stdout: OK
## stdout: OK
### cd to symlink with -P
#### cd to symlink with -P
targ=$TMP/cd-symtarget
lnk=$TMP/cd-symlink
mkdir -p $targ
@@ -122,43 +122,43 @@ test $PWD = "$TMP/cd-symtarget" && echo OK
cd $TMP
rmdir $targ
rm $lnk
# stdout: OK
## stdout: OK
### pushd/popd
#### pushd/popd
set -o errexit
cd /
pushd $TMP
popd
pwd
# status: 0
# N-I dash/mksh status: 127
## status: 0
## N-I dash/mksh status: 127
### Exit out of function
#### Exit out of function
f() { exit 3; }
f
exit 4
# status: 3
## status: 3
### Exit builtin with invalid arg
#### Exit builtin with invalid arg
exit invalid
# Rationale: runtime errors are 1
# status: 1
# OK dash/bash status: 2
## status: 1
## OK dash/bash status: 2
### Exit builtin with too many args
#### Exit builtin with too many args
# This is a parse error in OSH.
exit 7 8 9
echo status=$?
# status: 2
# stdout-json: ""
# BUG bash status: 0
# BUG bash stdout: status=1
# BUG dash status: 7
# BUG dash stdout-json: ""
# OK mksh status: 1
# OK mksh stdout-json: ""
### time block
## status: 2
## stdout-json: ""
## BUG bash status: 0
## BUG bash stdout: status=1
## BUG dash status: 7
## BUG dash stdout-json: ""
## OK mksh status: 1
## OK mksh stdout-json: ""
#### time block
# bash and mksh work; dash does't.
# TODO: osh needs to implement BraceGroup redirect properly.
err=_tmp/time-$(basename $SH).txt
@@ -173,60 +173,60 @@ cat $err | grep --only-matching real
# This is fiddly:
# | sed -n -E -e 's/.*(0m0\.03).*/\1/'
#
# status: 0
# stdout: real
# BUG dash status: 2
# BUG dash stdout-json: ""
## status: 0
## stdout: real
## BUG dash status: 2
## BUG dash stdout-json: ""
### time pipeline
#### time pipeline
time echo hi | wc -c
# stdout: 3
# status: 0
## stdout: 3
## status: 0
### shift
#### shift
set -- 1 2 3 4
shift
echo "$@"
shift 2
echo "$@"
# stdout-json: "2 3 4\n4\n"
# status: 0
## stdout-json: "2 3 4\n4\n"
## status: 0
### Shifting too far
#### Shifting too far
set -- 1
shift 2
# status: 1
# OK dash status: 2
## status: 1
## OK dash status: 2
### Invalid shift argument
#### Invalid shift argument
shift ZZZ
# status: 1
# OK dash status: 2
# BUG mksh status: 0
## status: 1
## OK dash status: 2
## BUG mksh status: 0
### get umask
#### get umask
umask | grep '[0-9]\+' # check for digits
# status: 0
## status: 0
### set umask in octal
#### set umask in octal
rm $TMP/umask-one $TMP/umask-two
umask 0002
echo one > $TMP/umask-one
umask 0022
echo two > $TMP/umask-two
stat -c '%a' $TMP/umask-one $TMP/umask-two
# status: 0
# stdout-json: "664\n644\n"
# stderr-json: ""
## status: 0
## stdout-json: "664\n644\n"
## stderr-json: ""
### set umask symbolically
#### set umask symbolically
umask 0002 # begin in a known state for the test
rm $TMP/umask-one $TMP/umask-two
echo one > $TMP/umask-one
umask g-w,o-w
echo two > $TMP/umask-two
stat -c '%a' $TMP/umask-one $TMP/umask-two
# status: 0
## status: 0
## STDOUT:
664
644
View
@@ -1,6 +1,6 @@
#!/bin/bash
### command -v
#### command -v
myfunc() { echo x; }
command -v echo
echo $?
@@ -28,7 +28,7 @@ for
0
## END
### command -v with multiple names
#### command -v with multiple names
# ALL FOUR SHELLS behave differently here!
#
# bash chooses to swallow the error! We agree with zsh if ANY word lookup
View
@@ -2,73 +2,73 @@
#
# Test the case statement
### Case statement
#### Case statement
case a in
a) echo A ;;
*) echo star ;;
esac
# stdout: A
## stdout: A
### Case statement with ;;&
#### Case statement with ;;&
# ;;& keeps testing conditions
# NOTE: ;& and ;;& are bash 4 only, no on Mac
case a in
a) echo A ;;&
*) echo star ;;&
*) echo star2 ;;
esac
# status: 0
# stdout-json: "A\nstar\nstar2\n"
# N-I dash stdout-json: ""
# N-I dash status: 2
## status: 0
## stdout-json: "A\nstar\nstar2\n"
## N-I dash stdout-json: ""
## N-I dash status: 2
### Case statement with ;&
#### Case statement with ;&
# ;& ignores the next condition. Why would that be useful?
case a in
a) echo A ;&
XX) echo two ;&
YY) echo three ;;
esac
# status: 0
# stdout-json: "A\ntwo\nthree\n"
# N-I dash stdout-json: ""
# N-I dash status: 2
## status: 0
## stdout-json: "A\ntwo\nthree\n"
## N-I dash stdout-json: ""
## N-I dash status: 2
### Case with empty condition
#### Case with empty condition
case $empty in
''|foo) echo match ;;
*) echo no ;;
esac
# stdout: match
## stdout: match
### Match a literal with a glob character
#### Match a literal with a glob character
x='*.py'
case "$x" in
'*.py') echo match ;;
esac
# stdout: match
## stdout: match
### Match a literal with a glob character with a dynamic pattern
#### Match a literal with a glob character with a dynamic pattern
x='b.py'
pat='[ab].py'
case "$x" in
$pat) echo match ;;
esac
# stdout: match
## stdout: match
### Quoted literal in glob pattern
#### Quoted literal in glob pattern
x='[ab].py'
pat='[ab].py'
case "$x" in
"$pat") echo match ;;
esac
# stdout: match
## stdout: match
### Multiple Patterns Match
#### Multiple Patterns Match
x=foo
result='-'
case "$x" in
f*|*o) result="$result X"
esac
echo $result
# stdout: - X
## stdout: - X
View
@@ -5,49 +5,49 @@
# TODO: Run the parser on your whole corpus, and then if there are no errors,
# you should make OSH the OK behavior, and others are OK.
### Prefix env on assignment
#### Prefix env on assignment
f() {
# NOTE: local treated like a special builtin!
E=env local v=var
echo $E $v
}
f
# status: 0
# stdout: env var
# OK bash stdout: var
# OK osh status: 2
# OK osh stdout-json: ""
## status: 0
## stdout: env var
## OK bash stdout: var
## OK osh status: 2
## OK osh stdout-json: ""
### Redirect on assignment
#### Redirect on assignment
f() {
# NOTE: local treated like a special builtin!
local E=env > _tmp/r.txt
}
rm -f _tmp/r.txt
f
test -f _tmp/r.txt && echo REDIRECTED
# status: 0
# stdout: REDIRECTED
# OK osh status: 2
# OK osh stdout-json: ""
## status: 0
## stdout: REDIRECTED
## OK osh status: 2
## OK osh stdout-json: ""
### Prefix env on control flow
#### Prefix env on control flow
for x in a b c; do
echo $x
E=env break
done
# status: 0
# stdout: a
# OK osh status: 2
# OK osh stdout-json: ""
## status: 0
## stdout: a
## OK osh status: 2
## OK osh stdout-json: ""
### Redirect on control flow
#### Redirect on control flow
rm -f _tmp/r.txt
for x in a b c; do
break > _tmp/r.txt
done
test -f _tmp/r.txt && echo REDIRECTED
# status: 0
# stdout: REDIRECTED
# OK osh status: 2
# OK osh stdout-json: ""
## status: 0
## stdout: REDIRECTED
## OK osh status: 2
## OK osh stdout-json: ""
View
@@ -1,84 +1,84 @@
#!/usr/bin/env bash
### case
#### case
foo=a; case $foo in [0-9]) echo number;; [a-z]) echo letter;; esac
# stdout: letter
## stdout: letter
### case in subshell
#### case in subshell
# Hm this subhell has to know about the closing ) and stuff like that.
# case_clause is a compound_command, which is a command. And a subshell
# takes a compound_list, which is a list of terms, which has and_ors in them
# ... which eventually boils down to a command.
echo $(foo=a; case $foo in [0-9]) echo number;; [a-z]) echo letter;; esac)
# stdout: letter
## stdout: letter
### Command sub word part
#### Command sub word part
# "The token shall not be delimited by the end of the substitution."
foo=FOO; echo $(echo $foo)bar$(echo $foo)
# stdout: FOObarFOO
## stdout: FOObarFOO
### Backtick
#### Backtick
foo=FOO; echo `echo $foo`bar`echo $foo`
# stdout: FOObarFOO
## stdout: FOObarFOO
### Backtick 2
#### Backtick 2
echo `echo -n l; echo -n s`
# stdout: ls
## stdout: ls
### Nested backticks
#### Nested backticks
# Inner `` are escaped! # Not sure how to do triple.. Seems like an unlikely
# use case. Not sure if I even want to support this!
echo X > $TMP/000000-first
echo `\`echo -n l; echo -n s\` $TMP | grep 000000-first`
# stdout: 000000-first
## stdout: 000000-first
### Making command out of command sub should work
#### Making command out of command sub should work
# Works in bash and dash!
$(echo ec)$(echo ho) split builtin
# stdout: split builtin
## stdout: split builtin
### Making keyword out of command sub should NOT work
#### Making keyword out of command sub should NOT work
# This doesn't work in bash or dash! Hm builtins are different than keywords /
# reserved words I guess.
# dash fails, but gives code 0
$(echo f)$(echo or) i in a b c; do echo $i; done
echo status=$?
# stdout-json: ""
# status: 2
# BUG dash stdout-json: "\nstatus=0\n"
# BUG dash status: 0
# OK mksh status: 1
## stdout-json: ""
## status: 2
## BUG dash stdout-json: "\nstatus=0\n"
## BUG dash status: 0
## OK mksh status: 1
### Command sub with here doc
#### Command sub with here doc
echo $(<<EOF tac
one
two
EOF
)
# stdout: two one
## stdout: two one
### Here doc with pipeline
#### Here doc with pipeline
<<EOF tac | tr '\n' 'X'
one
two
EOF
# stdout-json: "twoXoneX"
## stdout-json: "twoXoneX"
### Command Sub word split
#### Command Sub word split
argv.py $(echo 'hi there') "$(echo 'hi there')"
# stdout: ['hi', 'there', 'hi there']
## stdout: ['hi', 'there', 'hi there']
### Command Sub trailing newline removed
#### Command Sub trailing newline removed
s=$(python -c 'print "ab\ncd\n"')
argv.py "$s"
# stdout: ['ab\ncd']
## stdout: ['ab\ncd']
### Command Sub trailing whitespace not removed
#### Command Sub trailing whitespace not removed
s=$(python -c 'print "ab\ncd\n "')
argv.py "$s"
# stdout: ['ab\ncd\n ']
## stdout: ['ab\ncd\n ']
### Command Sub and exit code
#### Command Sub and exit code
# A command resets the exit code, but an assignment doesn't.
echo $(echo x; exit 33)
echo $?
@@ -90,7 +90,7 @@ x
33
## END
### Command Sub in local sets exit code
#### Command Sub in local sets exit code
# A command resets the exit code, but an assignment doesn't.
f() {
echo $(echo x; exit 33)
View
@@ -2,7 +2,7 @@
#
# Miscellaneous tests for the command language.
### Command block
#### Command block
{ which ls; }
## stdout: /bin/ls
View
@@ -3,10 +3,10 @@
# NOTE: The test harness isn't good for this test; it strips lines that start
# with #
### comment
#### comment
echo foo #comment
# stdout: foo
## stdout: foo
### not a comment without leading space x
#### not a comment without leading space x
echo foo#not_comment
# stdout: foo#not_comment
## stdout: foo#not_comment
Oops, something went wrong.