name: "Parameter expansion" cases: - name: "String length" stdin: | x="abc" echo "len = ${#x}" - name: "Parameter expression: default value" stdin: | value="value" empty="" declare declared unset undeclared # - echo "\${value-default} : ${value-default}" echo "\${empty-default} : ${empty-default}" echo "\${declared-default} : ${declared-default}" echo "\${undeclared-default} : ${undeclared-default}" echo "\${value-} : ${value-}" echo "\${empty-} : ${empty-}" echo "\${declared-} : ${declared-}" echo "\${undeclared-} : ${undeclared-}" # :- echo "\${value:-default} : ${value:-default}" echo "\${empty:-default} : ${empty:-default}" echo "\${declared:-default} : ${declared:-default}" echo "\${undeclared:-default} : ${undeclared:-default}" echo "\${value:-} : ${value:-}" echo "\${empty:-} : ${empty:-}" echo "\${declared:-} : ${declared:-}" echo "\${undeclared:-} : ${undeclared:-}" - name: "Parameter expression: assign default value (no colon)" stdin: | value="value" empty="" declare declared unset undeclared # = echo "\${value=default} : ${value=default}" declare -p value echo "\${empty=default} : ${empty=default}" declare -p empty echo "\${declared=default} : ${declared=default}" declare -p declared echo "\${undeclared=default} : ${undeclared=default}" declare -p undeclared - name: "Parameter expression: assign default value (no colon, empty replacement)" stdin: | value="value" empty="" declare declared unset undeclared # = echo "\${value=} : ${value=}" declare -p value echo "\${empty=} : ${empty=}" declare -p empty echo "\${declared=} : ${declared=}" declare -p declared echo "\${undeclared=} : ${undeclared=}" declare -p undeclared - name: "Parameter expression: assign default value (colon)" stdin: | value="value" empty="" declare declared unset undeclared # := echo "\${value:=default} : ${value:=default}" declare -p value echo "\${empty:=default} : ${empty:=default}" declare -p empty echo "\${declared:=default} : ${declared:=default}" declare -p declared echo "\${undeclared:=default} : ${undeclared:=default}" declare -p undeclared - name: "Parameter expression: assign default value (colon, empty replacement)" stdin: | value="value" empty="" declare declared unset undeclared # := echo "\${value:=} : ${value:=}" declare -p value echo "\${empty:=} : ${empty:=}" declare -p empty echo "\${declared:=} : ${declared:=}" declare -p declared echo "\${undeclared:=} : ${undeclared:=}" declare -p undeclared - name: "Parameter expression: alternative value" stdin: | value="value" empty="" declare declared unset undeclared # + echo "\${value+default} : ${value+default}" echo "\${empty+default} : ${empty+default}" echo "\${declared+default} : ${declared+default}" echo "\${undeclared+default} : ${undeclared+default}" echo "\${value+} : ${value+}" echo "\${empty+} : ${empty+}" echo "\${declared+} : ${declared+}" echo "\${undeclared+} : ${undeclared+}" # :+ echo "\${value:+default} : ${value:+default}" echo "\${empty:+default} : ${empty:+default}" echo "\${declared:+default} : ${declared:+default}" echo "\${undeclared:+default} : ${undeclared:+default}" echo "\${value:+} : ${value:+}" echo "\${empty:+} : ${empty:+}" echo "\${declared:+} : ${declared:+}" echo "\${undeclared:+} : ${undeclared:+}" - name: "Parameter expression: advanced alternative value" stdin: | value="value" echo ${value:+\x} echo ${value:+'x'} echo "${value:+\x}" echo "${value:+'x'}" echo "${value:+x y}" echo "${value:+z 'x' 'y' w}" echo "${value:+x'y'x}" echo "${value:+"x y"}" echo "${value:+"x\xy"}" echo "${value:+z"x\xy"z}" - name: "Parameter expression: error on condition (interactive)" ignore_stderr: true args: ["-i"] stdin: | value="value" empty="" declare declared unset undeclared # ? echo "\${value?error message} : ${value?error message}" echo " -> result: $?" echo "\${empty?error message} : ${empty?error message}" echo " -> result: $?" echo "\${declared?error message} : ${declared?error message}" echo " -> result: $?" echo "\${undeclared?error message} : ${undeclared?error message}" echo " -> result: $?" echo "\${value?} : ${value?}" echo " -> result: $?" echo "\${empty?} : ${empty?}" echo " -> result: $?" echo "\${declared?} : ${declared?}" echo " -> result: $?" echo "\${undeclared?} : ${undeclared?}" echo " -> result: $?" # :? echo "\${value:?error message} : ${value:?error message}" echo " -> result: $?" echo "\${empty:?error message} : ${empty:?error message}" echo " -> result: $?" echo "\${declared:?error message} : ${declared:?error message}" echo " -> result: $?" echo "\${undeclared:?error message} : ${undeclared:?error message}" echo " -> result: $?" echo "\${value:?} : ${value:?}" echo " -> result: $?" echo "\${empty:?} : ${empty:?}" echo " -> result: $?" echo "\${declared:?} : ${declared:?}" echo " -> result: $?" echo "\${undeclared:?} : ${undeclared:?}" echo " -> result: $?" - name: "Parameter expression: error on condition (non-interactive)" ignore_stderr: true stdin: | echo "${non_existent_var?error message}" echo "This should never execute" - name: "Parameter expression: expanded array as alternate value" stdin: | declare -a var=("abc" "def" "ghi" "") for item in "${var[@]}"; do echo "Item: '${item}'" done echo "Expression 1: '${var+"${var[@]}"}'" for item in "${var+"${var[@]}"}"; do echo " -> '${item}'" done echo "Expression 2: '${var+${var[@]}}'" for item in "${var+${var[@]}}"; do echo " -> '${item}'" done echo "Expression 3: '${var+"${var[@]}"}'" for item in ${var+"${var[@]}"}; do echo " -> '${item}'" done echo "Expression 4: '${var+${var[@]}}'" for item in ${var+${var[@]}}; do echo " -> '${item}'" done echo "Expression 5: '${var+"${var[*]}"}'" for item in "${var+"${var[*]}"}"; do echo " -> '${item}'" done echo "Expression 6: '${var+${var[*]}}'" for item in "${var+${var[*]}}"; do echo " -> '${item}'" done echo "Expression 7: '${var+"${var[*]}"}'" for item in ${var+"${var[*]}"}; do echo " -> '${item}'" done echo "Expression 8: '${var+${var[*]}}'" for item in ${var+${var[*]}}; do echo " -> '${item}'" done - name: "Parameter expression: expanded array as default value" stdin: | declare -a var=("abc" "def" "ghi" "") for item in "${var[@]}"; do echo "Item: '${item}'" done echo "Expression 1: ${nonexistent-${var[@]}}" for item in ${nonexistent-${var[@]}}; do echo " -> '${item}'" done echo "Expression 2: ${nonexistent-"${var[@]}"}" for item in ${nonexistent-"${var[@]}"}; do echo " -> '${item}'" done echo "Expression 3: ${nonexistent2=${var[@]}}" for item in ${nonexistent3=${var[@]}}; do echo " -> '${item}'" done echo "Expression 4: ${nonexistent4="${var[@]}"}" for item in ${nonexistent5-"${var[@]}"}; do echo " -> '${item}'" done - name: "Remove prefix/suffix" stdin: | var="prepre-abc-sufsuf" # Smallest suffix shopt -u nocasematch echo "\${var%}: ${var%}" echo "\${var%pre}: ${var%pre}" echo "\${var%suf}: ${var%suf}" echo "\${var%SUF}: ${var%SUF}" shopt -s nocasematch echo "\${var%SUF}(nocasematch): ${var%SUF}" # Largest suffix shopt -u nocasematch echo "\${var%%}: ${var%%}" echo "\${var%%pre}: ${var%%pre}" echo "\${var%%suf}: ${var%%suf}" echo "\${var%%SUF}: ${var%%SUF}" shopt -s nocasematch echo "\${var%%SUF}(nocasematch): ${var%%SUF}" # Smallest prefix shopt -u nocasematch echo "\${var#}: ${var#}" echo "\${var#pre}: ${var#pre}" echo "\${var#suf}: ${var#suf}" echo "\${var#PRE}: ${var#PRE}" shopt -s nocasematch echo "\${var#PRE}(nocasematch): ${var#PRE}" # Largest prefix shopt -u nocasematch echo "\${var##}: ${var##}" echo "\${var##pre}: ${var##pre}" echo "\${var##suf}: ${var##suf}" echo "\${var##PRE}: ${var##PRE}" shopt -s nocasematch echo "\${var##PRE}(nocasematch): ${var##PRE}" - name: "Indirect variable references" stdin: | var="Hello" ref="var" echo "${!ref}" echo "${!ref//l/o}" - name: "Indirect variable references with special parameters" stdin: | set a b c ref="2" echo "${!ref}" - name: "Indirect variable references with array" stdin: | arr=("element1" "element2" "element3") ref="arr[1]" echo "${!ref}" ref="arr[10]" echo "${!ref}" - name: "Variable prefix match" stdin: | var1="Hello" var2="World" echo "${!var*}" echo "${!var@}" echo "Dumping *" for i in "${!var*}"; do echo "i: $i" done echo "Dumping @" for i in "${!var@}"; do echo "i: $i" done - name: "Uppercase first character" stdin: | var="hello" shopt -u nocasematch echo "\${var^}: ${var^}" echo "\${var^h}: ${var^h}" echo "\${var^H}: ${var^H}" echo "\${var^l}: ${var^l}" echo "\${var^h*}: ${var^h*}" echo "\${var^he}: ${var^he}" echo "\${var^?}: ${var^?}" echo "\${var^*}: ${var^*}" shopt -s nocasematch echo "\${var^H}(nocasematch): ${var^H}" arr=("hello" "world") echo "\${arr^}: ${arr^}" echo "\${arr[@]^}: ${arr[@]^}" echo "\${arr[*]^}: ${arr[*]^}" - name: "Uppercase matching pattern" stdin: | var="hello" shopt -u nocasematch echo "\${var^^}: ${var^^}" echo "\${var^^l}: ${var^^l}" echo "\${var^^L}: ${var^^L}" echo "\${var^^m}: ${var^^m}" shopt -s nocasematch echo "\${var^^L}(nocasematch): ${var^^L}" arr=("hello" "world") echo "\${arr^^}: ${arr^^}" echo "\${arr[@]^^}: ${arr[@]^^}" echo "\${arr[*]^^}: ${arr[*]^^}" - name: "Lowercase first character" stdin: | var="HELLO" shopt -u nocasematch echo "\${var,}: ${var,}" echo "\${var,h}: ${var,h}" echo "\${var,H}: ${var,H}" echo "\${var,L}: ${var,L}" echo "\${var,H*}: ${var,H*}" echo "\${var,HE}: ${var,HE}" echo "\${var,?}: ${var,?}" echo "\${var,*}: ${var,*}" shopt -s nocasematch echo "\${var,h}(nocasematch): ${var,h}" arr=("HELLO" "WORLD") echo "\${arr,}: ${arr,}" echo "\${arr[@],}: ${arr[@],}" echo "\${arr[*],}: ${arr[*],}" - name: "Lowercase matching pattern" stdin: | var="HELLO" shopt -u nocasematch echo "\${var,,}: ${var,,}" echo "\${var,,M}: ${var,,M}" echo "\${var,,L}: ${var,,L}" echo "\${var,,l}: ${var,,l}" shopt -s nocasematch echo "\${var,,l}(nocasematch): ${var,,l}" arr=("HELLO" "WORLD") echo "\${arr,,}: ${arr,,}" echo "\${arr[@],,}: ${arr[@],,}" echo "\${arr[*],,}: ${arr[*],,}" - name: "Substring replacement" stdin: | var="Hello, world!" echo "\${var/world/WORLD}: ${var/world/WORLD}" arr=("world" "world") echo "\${arr/world/WORLD}: ${arr/world/WORLD}" echo "\${arr[@]/world/WORLD}: ${arr[@]/world/WORLD}" echo "\${arr[*]/world/WORLD}: ${arr[*]/world/WORLD}" - name: "Substring replacement with slashes" stdin: | var="Hello, world!" echo "\${var/world//world}: ${var/world//world}" - name: "Substring replacement with patterns" stdin: | var="a[bc]d" pattern="[bc]" echo "Replacement 1: ${var/${pattern}/}" echo "Replacement 2: ${var/"${pattern}"/}" - name: "Prefix substring replacement" stdin: | var="Hello, world!" echo "\${var/#world/WORLD}: ${var/#world/WORLD}" echo "\${var/#Hello/HELLO}: ${var/#Hello/HELLO}" arr=("world" "world") echo "\${arr/#world/WORLD}: ${arr/#world/WORLD}" echo "\${arr[@]/#world/WORLD}: ${arr[@]/#world/WORLD}" echo "\${arr[*]/#world/WORLD}: ${arr[*]/#world/WORLD}" - name: "Suffix substring replacement" stdin: | var="Hello, world!" echo "\${var/%Hello/HELLO}: ${var/%Hello/HELLO}" echo "\${var/%world!/WORLD!}: ${var/%world!/WORLD!}" arr=("world" "world") echo "\${arr/%world/WORLD}: ${arr/%world/WORLD}" echo "\${arr[@]/%world/WORLD}: ${arr[@]/%world/WORLD}" echo "\${arr[*]/%world/WORLD}: ${arr[*]/%world/WORLD}" - name: "Global substring replacement" stdin: | var="Hello, world, world!" echo "\${var//world/WORLD}: ${var//world/WORLD}" arr=("world world" "world world") echo "\${arr//world/WORLD}: ${arr//world/WORLD}" echo "\${arr[@]//world/WORLD}: ${arr[@]//world/WORLD}" echo "\${arr[*]//world/WORLD}: ${arr[*]//world/WORLD}" - name: "Global substring removal" stdin: | var="That is not all" shopt -u nocasematch echo "\${var//not }: ${var//not}" shopt -s nocasematch echo "\${var//NOT }: ${var//NOT }" - name: "Substring from offset" stdin: | var="Hello, world!" echo "\${var:0}: ${var:0}" echo "\${var:7}: ${var:7}" echo "\${var: 7 }: ${var: 7 }" echo "\${var:50}: ${var:50}" echo "\${var:-1}: ${var:-1}" echo "\${var: -1}: ${var: -1}" echo "\${var: 2 + 3 }: ${var: 2 + 3 }" - name: "Substring with length" stdin: | var="Hello, world!" echo "\${var:0:1}: ${var:0:1}" echo "\${var:0:0}: ${var:0:0}" echo "\${var:0:50}: ${var:0:50}" echo "\${var:0:-1}: ${var:0:-1}" echo "\${var:0:-3}: ${var:0:-3}" echo "\${var:7:3}: ${var:7:3}" echo "\${var:50:2}: ${var:50:2}" echo "\${var:-1:1}: ${var:-1:1}" echo "\${var:-3:1}: ${var:-3:1}" - name: "Substring on string with multi-byte chars" skip: true # TODO(expansion): succeeds on macOS, fails on Linux stdin: | var="7❌🖌️✅😂🔴⛔ABC" echo "${var:2}" | hexdump -C - name: "Substring operator on arrays" stdin: | set abcde fghij klmno pqrst uvwxy z echo "\${@:2:2}: ${@:2:2}" echo "\${@:2}: ${@:2}" myarray=(abcde fghij klmno pqrst uvwxy z) echo "\${myarray[@]:2:2}: ${myarray[@]:2:2}" echo "\${myarray[@]:2}: ${myarray[@]:2}" - name: "Substring operator on arrays with negative index" stdin: | set abcde fghij klmno pqrst uvwxy z echo "\${@: -2:2}: ${@: -2:2}" echo "\${@: -2}: ${@: -2}" myarray=(abcde fghij klmno pqrst uvwxy z) echo "\${myarray[@]: -2:2}: ${myarray[@]: -2:2}" echo "\${myarray[@]: -2}: ${myarray[@]: -2}" - name: "Substring operator on single-element array" stdin: | myarray=("abcde fghij klmno pqrst uvwxy z") echo "\${myarray[@]:0:1}: ${myarray[@]:0:1}" echo "\${myarray[@]:1}: ${myarray[@]:1}" - name: "Substring operator past end of array" stdin: | set a b c declare -a result1=("${@:3}") declare -p result1 myarray=(a b c) declare -a result2=("${myarray[@]:3}") declare -p result2 - name: "Substring with length (with nested expressions)" stdin: | var="Hello, world!" offset=7 length=5 echo "\${var:\$offset:\${length}}: ${var:$offset:${length}}"