Files
sausage/userland/brush/source/brush-shell/tests/cases/compat/ifs.yaml
T
2026-11-11 22:04:16 +00:00

890 lines
20 KiB
YAML

name: "IFS"
cases:
- name: "Default IFS value"
stdin: |
# Default IFS is space, tab, newline
[[ "$IFS" == $' \t\n' ]] && echo "IFS has default value"
- name: "Field splitting with default IFS"
stdin: |
var="one two three
four"
echo "Fields:" $var
- name: "Field splitting with custom single-char IFS"
stdin: |
IFS=':'
var="one:two:three"
echo $var
- name: "Field splitting with multi-char IFS"
stdin: |
IFS=':,'
var="one:two,three:four"
echo $var
- name: "Empty IFS disables field splitting"
stdin: |
IFS=''
var="one two three"
echo "[$var]"
# Should print with all whitespace preserved
- name: "Empty IFS with expansion"
stdin: |
IFS=''
var="a b c"
set -- $var
echo "Count: $#"
echo "[$1]"
- name: "Unset IFS uses default splitting"
stdin: |
unset IFS
var="one two three"
echo $var
- name: "IFS whitespace vs non-whitespace splitting"
stdin: |
# Non-whitespace IFS chars create empty fields
IFS=':'
var=":one::two:"
set -- $var
echo "Count: $#"
echo "Args: $@"
- name: "IFS whitespace splitting ignores leading/trailing"
stdin: |
# Whitespace IFS chars ignore leading/trailing
IFS=' '
var=" one two "
set -- $var
echo "Count: $#"
echo "Args: $@"
- name: "Mixed whitespace and non-whitespace IFS"
stdin: |
IFS=' :'
var="one:two three : four"
set -- $var
echo "Count: $#"
for arg in "$@"; do
echo "[$arg]"
done
- name: "Mixed IFS leading non-whitespace"
stdin: |
IFS=': '
var=":one: :two"
set -- $var
echo "Count: $#"
for arg in "$@"; do
echo "[$arg]"
done
- name: "Mixed IFS trailing non-whitespace"
stdin: |
IFS=' :'
var="one :two: "
set -- $var
echo "Count: $#"
for arg in "$@"; do
echo "[$arg]"
done
- name: "IFS only whitespace multiple chars"
stdin: |
IFS=$' \t'
var=" one two three "
set -- $var
echo "Count: $#"
echo "Args: $@"
- name: "IFS only non-whitespace multiple chars"
stdin: |
IFS=':,'
var=":,one,:,two,:"
set -- $var
echo "Count: $#"
for arg in "$@"; do
echo "[$arg]"
done
- name: "IFS only tab character"
stdin: |
IFS=$'\t'
var=" a b "
set -- $var
echo "Count: $#"
echo "Args: $@"
- name: "IFS only newline character"
stdin: |
IFS=$'\n'
var="
a
b
"
set -- $var
echo "Count: $#"
echo "Args: $@"
- name: "IFS affects command substitution"
stdin: |
IFS=':'
result=$(echo "a:b:c")
# Command substitution itself doesn't split, but expansion does
set -- $result
echo "$#"
echo "$@"
- name: "IFS with command substitution multiline"
stdin: |
IFS=$'\n'
result=$(printf "a\nb\nc")
set -- $result
echo "Count: $#"
echo "$1|$2|$3"
- name: "IFS affects arithmetic expansion"
stdin: |
IFS=' '
# Arithmetic expansion result is splittable
set -- $((1 + 1)) $((2 + 2))
echo "Count: $#"
echo "$@"
- name: "IFS in array expansion"
stdin: |
arr=(one two three)
IFS=':'
echo "${arr[*]}"
# With $* should join with first char of IFS
- name: "IFS with $* vs $@"
stdin: |
set -- one two three
IFS=':'
echo "$*"
echo "$@"
- name: "IFS with $* unquoted"
stdin: |
set -- "a:b" "c:d"
IFS=':'
# Unquoted $* joins then splits
set -- $*
echo "Count: $#"
for arg in "$@"; do
echo "[$arg]"
done
- name: "IFS null char handling"
stdin: |
IFS=':'
var=""
set -- $var
echo "Empty var count: $#"
- name: "IFS preservation across commands"
stdin: |
IFS=':'
echo "First: $IFS"
var="a:b"
echo $var
echo "After: $IFS"
- name: "IFS in subshell"
stdin: |
IFS=':'
(
echo "In subshell: [$IFS]"
var="a:b:c"
set -- $var
echo "Count: $#"
)
echo "Parent: [$IFS]"
- name: "IFS local to command"
stdin: |
var="a:b:c"
IFS=':' echo $var
# IFS assignment should only affect that command
echo "After: [$IFS]"
- name: "IFS does not affect for loop literal words"
stdin: |
IFS=':'
for word in a:b:c; do
echo "[$word]"
done
- name: "IFS affects for loop variable expansion"
stdin: |
IFS=':'
var="a:b:c"
for word in $var; do
echo "[$word]"
done
- name: "IFS affects for loop command substitution"
stdin: |
IFS=':'
for word in $(echo "x:y:z"); do
echo "[$word]"
done
- name: "IFS with quoted vs unquoted expansion"
stdin: |
IFS=':'
var="a:b:c"
echo "Unquoted:" $var
echo "Quoted: $var"
- name: "IFS with partially quoted expansion"
stdin: |
IFS=':'
var="a:b:c"
set -- "$var":more
echo "Count: $#"
echo "$@"
- name: "IFS whitespace sequence handling"
stdin: |
IFS=' '
var="a b"
set -- $var
echo "Count: $#"
echo "Args: $@"
- name: "IFS non-whitespace sequence handling"
stdin: |
IFS=':'
var="a::::b"
set -- $var
echo "Count: $#"
for arg in "$@"; do
echo "[$arg]"
done
- name: "IFS with parameter expansion"
stdin: |
IFS=':'
var="a:b:c"
result=${var}
set -- $result
echo "$#"
echo "$@"
- name: "IFS with parameter expansion default value"
stdin: |
IFS=':'
set -- ${unset:-a:b:c}
echo "Count: $#"
echo "$@"
- name: "IFS with parameter expansion empty default"
stdin: |
IFS=':'
set -- ${unset:-}
echo "Count: $#"
- name: "IFS with parameter length"
stdin: |
IFS=':'
var="a:b:c"
# Length expansion returns a number, not subject to further splitting
len=${#var}
set -- $len
echo "Count: $#"
echo "[$1]"
- name: "IFS with glob pattern literal"
stdin: |
IFS=':'
# Glob patterns as literals shouldn't split
for word in *.txt:*.sh; do
echo "[$word]"
done
- name: "IFS newline handling"
stdin: |
IFS=$'\n'
var="a
b
c"
set -- $var
echo "Count: $#"
echo "$1|$2|$3"
- name: "IFS tab handling"
stdin: |
IFS=$'\t'
var="a b c"
set -- $var
echo "Count: $#"
echo "$1|$2|$3"
- name: "IFS with backslash in value"
stdin: |
IFS=':'
var='a\:b:c\:d'
set -- $var
echo "Count: $#"
for arg in "$@"; do
echo "[$arg]"
done
- name: "IFS first char for joining"
stdin: |
set -- a b c
IFS=':'
# First char of IFS used for $* joining
result="$*"
echo "[$result]"
- name: "IFS first char empty IFS"
stdin: |
set -- a b c
IFS=''
result="$*"
echo "[$result]"
- name: "IFS multiple whitespace at start"
stdin: |
IFS=' '
var=" a b"
set -- $var
echo "Count: $#"
echo "$@"
- name: "IFS multiple whitespace at end"
stdin: |
IFS=' '
var="a b "
set -- $var
echo "Count: $#"
echo "$@"
- name: "IFS with read builtin integration"
stdin: |
IFS=':'
echo "a:b:c" | (read x y z; echo "x=[$x] y=[$y] z=[$z]")
- name: "IFS whitespace before non-whitespace"
stdin: |
IFS=' :'
var=" :a"
set -- $var
echo "Count: $#"
for arg in "$@"; do
echo "[$arg]"
done
- name: "IFS non-whitespace before whitespace"
stdin: |
IFS=': '
var=": a"
set -- $var
echo "Count: $#"
for arg in "$@"; do
echo "[$arg]"
done
- name: "Array star join in scalar assignment honors IFS"
stdin: |
arr=(a b c)
IFS=:
x=${arr[*]}
echo "[$x]"
- name: "Array at join in scalar assignment uses space"
stdin: |
arr=(a b c)
IFS=:
y=${arr[@]}
echo "[$y]"
- name: "Export assignment from array star honors IFS"
stdin: |
np=(/first/bin /usr/lib/llvm/21/bin /usr/bin)
IFS=:
export TESTPATH=${np[*]}
echo "[$TESTPATH]"
- name: "Split on IFS then rejoin via star"
stdin: |
p="x:y:z"
IFS=:
s=( ${p} )
out=${s[*]}
echo "[$out]"
- name: "Positional star join in scalar assignment honors IFS"
stdin: |
set -- a b c
IFS=:
x=$*
echo "[$x]"
- name: "Assign-default array star join honors IFS"
# `:` + a multi-field `:=` argument triggers a pre-existing, unrelated
# brush quirk ("command not found: <first field>" on stderr); the stdout
# is what verifies the IFS join.
ignore_stderr: true
stdin: |
arr=(a b c)
IFS=:
unset x
: ${x:=${arr[*]}}
echo "[$x]"
- name: "Assign-default array at join uses space"
ignore_stderr: true
stdin: |
arr=(a b c)
IFS=:
unset y
: ${y:=${arr[@]}}
echo "[$y]"
- name: "Use-default array star join honors IFS"
stdin: |
arr=(a b c)
IFS=:
unset z
echo "[${z:-${arr[*]}}]"
- name: "Assign-default positional star join honors IFS"
ignore_stderr: true
stdin: |
set -- a b c
IFS=:
unset p
: ${p:=$*}
echo "[$p]"
- name: "Run of non-whitespace delimiters yields empty fields"
stdin: |
IFS=:
var="a:b:::c"
set -- $var
echo "Count: $#"
for arg in "$@"; do
echo "[$arg]"
done
- name: "Leading delimiter yields an empty field, trailing one does not"
stdin: |
IFS=:
for var in ":a" "a:" ":a:" ":" "::"; do
set -- $var
echo "$var -> $#"
for arg in "$@"; do
echo " [$arg]"
done
done
- name: "Whitespace around a delimiter belongs to the same run"
stdin: |
IFS=': '
for var in "a : b" "a :: b" "a: :b" " a b "; do
set -- $var
echo "$var -> $#"
for arg in "$@"; do
echo " [$arg]"
done
done
- name: "Carriage return, vertical tab and form feed split like whitespace"
stdin: |
for c in $'\r' $'\v' $'\f'; do
IFS="$c"
var="a${c}b${c}${c}c"
set -- $var
echo "count=$#"
done
- name: "A delimiter split across two expansions in one word"
stdin: |
IFS=:
a="1:"
b=":4"
set -- $a$b
echo "Count: $#"
for arg in "$@"; do
echo "[$arg]"
done
- name: "Each field of an array expansion splits on its own"
stdin: |
IFS=:
arr=("a:b" "c::d")
set -- ${arr[@]}
echo "Count: $#"
for arg in "$@"; do
echo "[$arg]"
done
# Cases below test IFS behavior with empty/unset IFS across different
# expansion types: variable, brace, mixed, arithmetic, command substitution.
- name: "Empty IFS disables splitting of variable expansion"
stdin: |
str="a b c"
IFS=""
arr=( $str )
echo "split: ${#arr[@]} elements"
- name: "Unset IFS uses default splitting"
stdin: |
str="a b c"
unset IFS
arr=( $str )
echo "split: ${#arr[@]} elements"
- name: "Local IFS=: then unset restores default splitting"
stdin: |
test_func() {
local str2="a b c"
local IFS=:
unset IFS
local arr=( $str2 )
echo "split: ${#arr[@]} elements"
for (( i=0; i<${#arr[@]}; i++ )); do
echo " [$i]='${arr[$i]}'"
done
}
test_func
- name: "Local IFS='' disables splitting of variable"
stdin: |
test_func() {
local str2="a b c"
local IFS=""
local arr=( $str2 )
echo "split: ${#arr[@]} elements"
for (( i=0; i<${#arr[@]}; i++ )); do
echo " [$i]='${arr[$i]}'"
done
}
test_func
- name: "Brace expansion with local IFS=: then unset"
stdin: |
test_func() {
local IFS=:
unset IFS
set -- python3_{11..14}
echo "Count: $#"
for arg in "$@"; do echo "[$arg]"; done
}
test_func
- name: "Brace expansion with local IFS=''"
stdin: |
test_func() {
local IFS=""
set -- python3_{11..14}
echo "Count: $#"
for arg in "$@"; do echo "[$arg]"; done
}
test_func
- name: "Mixed variable and brace expansion with local IFS=''"
stdin: |
test_func() {
local str="python3_"
local IFS=""
set -- ${str}{11..14}
echo "Count: $#"
for arg in "$@"; do echo "[$arg]"; done
}
test_func
- name: "Command substitution with local IFS=''"
stdin: |
test_func() {
local IFS=""
set -- $(echo "a b c")
echo "Count: $#"
for arg in "$@"; do echo "[$arg]"; done
}
test_func
- name: "Arithmetic expansion with local IFS=''"
stdin: |
test_func() {
local IFS=""
set -- $((1+1)) $((2+2))
echo "Count: $#"
for arg in "$@"; do echo "[$arg]"; done
}
test_func
- name: "Special parameters with local IFS=''"
stdin: |
test_func() {
local IFS=""
set -- $*
echo "Count: $#"
}
test_func
- name: "Complex mixed variable and brace expansion with local IFS=''"
stdin: |
test_func() {
local str="file"
local IFS=""
set -- ${str}{1..3}.txt
echo "Count: $#"
for arg in "$@"; do echo "[$arg]"; done
}
test_func
- name: "Empty IFS with set -- variable splitting"
stdin: |
test_func() {
local var="a b c"
local IFS=""
set -- $var
echo "Count: $#"
echo "[$1]"
}
test_func
- name: "Empty IFS with for-loop variable splitting"
stdin: |
test_func() {
local var="x y z"
local IFS=""
for word in $var; do
echo "[$word]"
done
}
test_func
- name: "Empty IFS with comma brace expansion"
stdin: |
test_func() {
local IFS=""
set -- {a,b,c}
echo "Count: $#"
for arg in "$@"; do echo "[$arg]"; done
}
test_func
- name: "Empty IFS with sequence brace expansion"
stdin: |
test_func() {
local IFS=""
set -- {1..5}
echo "Count: $#"
for arg in "$@"; do echo "[$arg]"; done
}
test_func
- name: "Empty IFS with parameter default value expansion"
stdin: |
test_func() {
local var="a b c"
local IFS=""
set -- ${var:-default}
echo "Count: $#"
echo "[$1]"
}
test_func
- name: "Empty IFS scoping across nested functions"
stdin: |
inner() {
local IFS=""
local var="a b c"
set -- $var
echo "Inner count: $#"
}
outer() {
local var="a b c"
set -- $var
echo "Outer before count: $#"
inner
set -- $var
echo "Outer after count: $#"
}
outer
- name: "Empty IFS with positional parameters expansion"
stdin: |
test_func() {
local IFS=""
set -- a b c
set -- $@
echo "Count: $#"
for arg in "$@"; do echo "[$arg]"; done
}
test_func
- name: "Brace expansion preserves array[@] element separation"
stdin: |
arr=(1 2 3)
set -- {a,b}"${arr[@]}"
echo "Count: $#"
for arg in "$@"; do echo "[$arg]"; done
- name: "Brace expansion with array[*] concatenates elements"
stdin: |
arr=(1 2 3)
set -- {a,b}"${arr[*]}"
echo "Count: $#"
for arg in "$@"; do echo "[$arg]"; done
- name: "Brace expansion with unquoted array[@] splits elements"
stdin: |
arr=(1 2 3)
set -- {a,b}${arr[@]}
echo "Count: $#"
for arg in "$@"; do echo "[$arg]"; done
# Brace expansion happens before field splitting and its results are separate
# words regardless of IFS; a non-whitespace IFS that stays set must not
# merge or re-split them.
- name: "Brace expansion stays separate under IFS=:"
stdin: |
IFS=:
set -- python3_{11..14}
echo "Count: $#"
for arg in "$@"; do echo "[$arg]"; done
- name: "Brace expansion stays separate under IFS=, (the list separator)"
stdin: |
IFS=,
set -- {a,b}{c,d}
echo "Count: $#"
for arg in "$@"; do echo "[$arg]"; done
- name: "Brace expansion under IFS=: still splits the expansions inside it"
stdin: |
IFS=:
v="1:2"
set -- {$v,x}
echo "Count: $#"
for arg in "$@"; do echo "[$arg]"; done
- name: "Brace expansion under IFS=: with quoted array[*] inside the list"
stdin: |
IFS=:
arr=(1 2)
set -- {"${arr[*]}",z}
echo "Count: $#"
for arg in "$@"; do echo "[$arg]"; done
- name: "Brace expansion under IFS=: with tilde prefix"
env:
HOME: /h
stdin: |
IFS=:
set -- ~/{x,y}
echo "Count: $#"
for arg in "$@"; do echo "[$arg]"; done
# `$*` and `${arr[*]}` join with the first character of IFS. An unset IFS
# means a space; an empty IFS means no separator at all.
- name: "Empty IFS joins \"$*\" with no separator"
stdin: |
set -- a b c
IFS=
echo "[$*]"
x="$*"
echo "[$x]"
arr=(1 2 3)
echo "[${arr[*]}]"
y=${arr[*]}
echo "[$y]"
- name: "Unset IFS joins \"$*\" with a space"
stdin: |
set -- a b c
unset IFS
echo "[$*]"
arr=(1 2 3)
echo "[${arr[*]}]"
- name: "Empty IFS join inside a larger double-quoted word"
stdin: |
set -- a b c
IFS=
echo "[<$*>]"
echo "[${#*}]"
case "$*" in abc) echo joined;; *) echo "not joined";; esac
# Field splitting applies only to the results of parameter expansion, command
# substitution and arithmetic expansion. Literal text in the word is never
# split, even when it contains IFS characters, but it is still globbed.
- name: "Literal word text is not field-split"
stdin: |
IFS=:
set -- a:b
echo "Count: $#"
set -- {a:b,c}
echo "Count: $#"
for arg in "$@"; do echo "[$arg]"; done
set -- a:$'b'
echo "Count: $#"
- name: "Literal word text still globs under a non-default IFS"
test_files:
- path: "a:1"
- path: "a:2"
stdin: |
IFS=:
set -- a:*
echo "Count: $#"
for arg in "$@"; do echo "[$arg]"; done
IFS='*'
set -- a:*
echo "Count: $#"
- name: "Expansion result adjacent to literal text"
stdin: |
IFS=:
x="1:2"
set -- $x:3
echo "Count: $#"
for arg in "$@"; do echo "[$arg]"; done
x="1:"
set -- $x:3
echo "Count: $#"
for arg in "$@"; do echo "[$arg]"; done
set -- pre:$x
echo "Count: $#"
for arg in "$@"; do echo "[$arg]"; done
- name: "Default and alternate value words are split like any expansion result"
stdin: |
IFS=:
x=
set -- ${x:-a:b}
echo "Count: $#"
set -- ${x:-"a:b"}
echo "Count: $#"
set -- ${x:=c:d}
echo "Count: $#"
echo "[$x]"
set -- ${x:+e:f}
echo "Count: $#"
- name: "compgen -W splits its word list on IFS"
stdin: |
IFS=:
compgen -W 'a:b c'
v="p:q"
compgen -W "$v"