393 lines
13 KiB
YAML
393 lines
13 KiB
YAML
name: "Builtins: read"
|
|
cases:
|
|
- name: "Basic read usage from file"
|
|
test_files:
|
|
- path: "data.txt"
|
|
contents: |
|
|
a 1
|
|
b 2
|
|
stdin: |
|
|
while read name num; do echo "Hello, $name => $num"; done < data.txt
|
|
echo "Exited loop; name='$name', num='$num'"
|
|
|
|
- name: "Basic read usage from file and no var name"
|
|
test_files:
|
|
- path: "data.txt"
|
|
contents: |
|
|
a 1
|
|
b 2
|
|
stdin: |
|
|
while read; do echo "reply: '$REPLY'"; done < data.txt
|
|
echo "Exited loop; reply='$REPLY'"
|
|
|
|
- name: "Basic read usage with empty file"
|
|
stdin: |
|
|
while read name num; do echo "Hello, $name => $num"; done < /dev/null
|
|
declare -p name num
|
|
|
|
while read; do echo "reply: '$REPLY'"; done < /dev/null
|
|
declare -p REPLY
|
|
|
|
while read -a arr; do declare -p arr; done < /dev/null
|
|
declare -p arr
|
|
|
|
- name: "Basic read usage from pipe"
|
|
stdin: |
|
|
echo "1."
|
|
(echo a; echo b) | while read name; do echo "Hello, $name"; done
|
|
|
|
echo "2."
|
|
(echo "a b") | while read name; do echo "Hello, 1:$name REPLY:$REPLY"; done
|
|
|
|
echo "3."
|
|
(echo "a b") | while read -a arr; do declare -p arr; done
|
|
|
|
- name: "read with closed stdin"
|
|
ignore_stderr: true
|
|
stdin: |
|
|
echo 1 | read <&-
|
|
echo "result: $?"
|
|
|
|
- name: "read from here string"
|
|
stdin: |
|
|
read myvar <<< "hello"
|
|
echo "myvar: ${myvar}"
|
|
|
|
- name: "read from process substitution"
|
|
stdin: |
|
|
read myvar < <(echo hello)
|
|
echo "myvar: ${myvar}"
|
|
|
|
- name: "read with custom IFS"
|
|
stdin: |
|
|
content=" a b c "
|
|
while IFS= read line; do
|
|
echo "LINE: '$line'"
|
|
done <<<"${content}"
|
|
|
|
- name: "read text with tabs and custom IFS"
|
|
stdin: |
|
|
while IFS="" read myvar; do
|
|
echo "myvar1: |${myvar}|"
|
|
done < <(printf "a\tb\nc d\te\n")
|
|
|
|
- name: "read with empty entries"
|
|
stdin: |
|
|
(echo "x"; echo ""; echo "y"; echo ""; echo "") | while read line; do echo "LINE: '$line'"; done
|
|
echo 'x,,y,z' | (IFS=',' read READ1 READ2 READ3 READ4; declare -p READ1; declare -p READ2; declare -p READ3; declare -p READ4)
|
|
|
|
- name: "read -a with empty lines"
|
|
stdin: |
|
|
(echo "hi"; echo ""; echo "there"; echo ""; echo "you") | (read -a READ; declare -p READ)
|
|
(echo -e "hi\t\tthere\t\tyou") | (read -a READ -d $'\t'; declare -p READ)
|
|
|
|
- name: "read -a with empty interior field"
|
|
stdin: |
|
|
echo -e -n "hi||there||you" | (IFS='|' read -a READ; declare -p READ)
|
|
echo -e -n "hi there you " | (IFS=' ' read -a READ; declare -p READ)
|
|
echo -e -n "hi there you " | (read -a READ; declare -p READ)
|
|
|
|
- name: "read -a with empty leading field"
|
|
stdin: |
|
|
echo -e -n "|hi|there|you" | (IFS='|' read -a READ; declare -p READ)
|
|
|
|
- name: "read -a with empty trailing field"
|
|
stdin: |
|
|
echo -e -n "hi|there|you|" | (IFS='|' read -a READ; declare -p READ)
|
|
|
|
- name: "read -a with empty entries + empty delimiter"
|
|
stdin: |
|
|
(echo "hi"; echo ""; echo "there"; echo ""; echo "you") | (read -a READ -d ''; declare -p READ)
|
|
echo -e -n "hi\n\nthere\n\nyou\n" | (read -a READ -d ''; declare -p READ)
|
|
|
|
- name: "read -a with empty entries + empty delimiter + custom IFS"
|
|
stdin: |
|
|
(echo 'x,,y,z'; echo 'w,v'; echo ''; echo ''; echo 'm') | (IFS=',' read -d "" -a READ; declare -p READ)
|
|
|
|
- name: "read with empty delimiter"
|
|
min_oracle_version: 5.2 # \n renders differently in older shell versions
|
|
stdin: |
|
|
echo x | (read -d ""; declare -p REPLY)
|
|
|
|
# Timeout tests (read -t)
|
|
- name: "read -t 0 with data available in pipe"
|
|
# TODO(read):
|
|
# This test is racy - poll(0) may check the pipe before echo's data arrives.
|
|
# Bash consistently wins this race; our implementation sometimes doesn't.
|
|
# Worth investigating.
|
|
skip: true
|
|
stdin: |
|
|
echo "test" | { read -t 0; echo "exit: $?"; }
|
|
|
|
- name: "read -t 0 with no data (empty pipe)"
|
|
skip: true # TODO(read): see above
|
|
stdin: |
|
|
# Closed pipe with no data
|
|
: | { read -t 0; echo "exit: $?"; }
|
|
|
|
- name: "read -t with data immediately available"
|
|
stdin: |
|
|
echo "hello world" | { read -t 1 line; echo "exit: $?, line: '$line'"; }
|
|
|
|
- name: "read -t with fractional timeout and data available"
|
|
stdin: |
|
|
echo "test data" | { read -t 0.5 var; echo "exit: $?, var: '$var'"; }
|
|
|
|
- name: "read -t with regular file (always ready)"
|
|
test_files:
|
|
- path: "data.txt"
|
|
contents: |
|
|
line1
|
|
line2
|
|
stdin: |
|
|
read -t 0 < data.txt; echo "exit: $?"
|
|
read -t 1 line < data.txt; echo "exit: $?, line: '$line'"
|
|
|
|
- name: "read -t negative timeout is invalid"
|
|
ignore_stderr: true
|
|
stdin: |
|
|
read -t -1
|
|
echo "exit: $?"
|
|
|
|
- name: "read -t with -n option"
|
|
stdin: |
|
|
echo "abcdef" | { read -t 1 -n 3 var; echo "exit: $?, var: '$var'"; }
|
|
|
|
- name: "read -t 0 with here string"
|
|
stdin: |
|
|
read -t 0 <<< "hello"; echo "exit: $?"
|
|
read -t 1 var <<< "hello"; echo "exit: $?, var: '$var'"
|
|
|
|
# Raw mode tests (read -r)
|
|
- name: "read -r preserves backslashes"
|
|
stdin: |
|
|
echo 'hello\nworld' | { read -r line; echo "line: '$line'"; }
|
|
echo 'path\\to\\file' | { read -r line; echo "line: '$line'"; }
|
|
|
|
- name: "read without -r processes escape sequences"
|
|
stdin: |
|
|
echo 'hello\nworld' | { read line; echo "line: '$line'"; }
|
|
echo 'a\\b' | { read line; echo "line: '$line'"; }
|
|
|
|
- name: "read -r with trailing backslash"
|
|
stdin: |
|
|
printf 'line with trailing\\' | { read -r line; echo "line: '$line'"; }
|
|
|
|
- name: "read without -r line continuation"
|
|
stdin: |
|
|
# Backslash-newline continues to next line
|
|
printf 'first\\\nsecond' | { read line; echo "line: '$line'"; }
|
|
|
|
- name: "read -r does not do line continuation"
|
|
stdin: |
|
|
printf 'first\\\nsecond' | { read -r line; echo "line: '$line'"; }
|
|
|
|
# -N vs -n option tests
|
|
- name: "read -n respects delimiter"
|
|
stdin: |
|
|
# -n stops at delimiter even before char count
|
|
echo "ab:cd" | { read -n 5 var; echo "var: '$var'"; }
|
|
|
|
- name: "read -N ignores delimiter"
|
|
skip: true # TODO(read): test sometimes results in printf error
|
|
stdin: |
|
|
# -N reads exactly N chars, delimiter treated as regular char
|
|
printf "ab:cd" | { read -N 4 var; echo "var: '$var'"; }
|
|
|
|
- name: "read -N does not split by IFS"
|
|
stdin: |
|
|
# -N should not split result by IFS
|
|
printf "a b c" | { read -N 5 var; echo "var: '$var'"; }
|
|
|
|
- name: "read -N with array"
|
|
stdin: |
|
|
# With -a and -N, entire input goes to single array element
|
|
printf "a b c" | { read -N 5 -a arr; declare -p arr; }
|
|
|
|
# Exit code tests
|
|
- name: "read exit code on success"
|
|
stdin: |
|
|
echo "data" | { read var; echo "exit: $?"; }
|
|
|
|
- name: "read exit code on EOF"
|
|
stdin: |
|
|
# EOF returns non-zero
|
|
read var < /dev/null; echo "exit: $?"
|
|
|
|
- name: "read exit code on EOF with partial data"
|
|
stdin: |
|
|
# EOF with partial data still returns non-zero but assigns
|
|
printf "partial" | { read var; echo "exit: $?, var: '$var'"; }
|
|
|
|
# IFS edge cases
|
|
- name: "read with IFS containing both whitespace and non-whitespace"
|
|
stdin: |
|
|
echo "a:b c:d" | (IFS=': ' read v1 v2 v3 v4; echo "v1='$v1' v2='$v2' v3='$v3' v4='$v4'")
|
|
|
|
- name: "read with consecutive non-whitespace delimiters"
|
|
stdin: |
|
|
echo "a::b" | (IFS=':' read v1 v2 v3; echo "v1='$v1' v2='$v2' v3='$v3'")
|
|
|
|
- name: "read assignment to fewer variables than fields"
|
|
stdin: |
|
|
echo "a b c d e" | { read v1 v2; echo "v1='$v1' v2='$v2'"; }
|
|
|
|
- name: "read assignment to more variables than fields"
|
|
stdin: |
|
|
echo "a b" | { read v1 v2 v3 v4; echo "v1='$v1' v2='$v2' v3='$v3' v4='$v4'"; }
|
|
|
|
- name: "read last variable preserves original delimiters"
|
|
stdin: |
|
|
# Last variable should get remainder with original delimiters, not re-joined with space
|
|
echo "x:y:z:w" | (IFS=':' read a b; echo "a='$a' b='$b'")
|
|
echo "x:y::z:w" | (IFS=':' read a b; echo "a='$a' b='$b'")
|
|
echo "a:b c:d e" | (IFS=': ' read v1 v2; echo "v1='$v1' v2='$v2'")
|
|
|
|
- name: "read with consecutive delimiters at field boundary"
|
|
stdin: |
|
|
# When field limit causes boundary at consecutive delimiters,
|
|
# extra delimiters should be preserved in remainder.
|
|
echo "x::y" | (IFS=':' read a b; echo "a='$a' b='$b'")
|
|
echo "x:::y" | (IFS=':' read a b; echo "a='$a' b='$b'")
|
|
# Also test with more than 2 variables
|
|
echo "a::b:c" | (IFS=':' read v1 v2 v3; echo "v1='$v1' v2='$v2' v3='$v3'")
|
|
|
|
- name: "read with empty IFS preserves entire line"
|
|
stdin: |
|
|
# Empty IFS means no splitting - entire line goes to first variable
|
|
echo "a b c" | (IFS='' read v1 v2 v3; echo "v1='$v1' v2='$v2' v3='$v3'")
|
|
echo "a:b:c" | (IFS='' read v1 v2; echo "v1='$v1' v2='$v2'")
|
|
# With array
|
|
echo "a b c" | (IFS='' read -a arr; declare -p arr)
|
|
|
|
- name: "read REPLY preserves leading and trailing whitespace"
|
|
stdin: |
|
|
# When no variable names given, REPLY should preserve whitespace
|
|
read <<< ' hello world '
|
|
echo "REPLY='$REPLY'"
|
|
read <<< ' tab separated '
|
|
echo "REPLY='$REPLY'"
|
|
|
|
- name: "read -a clears pre-existing array elements"
|
|
stdin: |
|
|
# Array should be cleared before assignment, not merged
|
|
arr=(old1 old2 old3 old4 old5)
|
|
read -a arr <<< 'new1 new2'
|
|
declare -p arr
|
|
|
|
- name: "read -N still processes backslashes without -r"
|
|
stdin: |
|
|
# -N ignores delimiter but still does backslash processing
|
|
printf 'a\\nb' | { read -N 4 var; echo "var='$var'"; }
|
|
printf 'a\\\\b' | { read -N 4 var; echo "var='$var'"; }
|
|
|
|
- name: "read -r -N for truly raw fixed-length reads"
|
|
stdin: |
|
|
# -r -N should preserve backslashes exactly
|
|
printf 'a\\nb' | { read -r -N 4 var; echo "var='$var'"; }
|
|
printf 'a\\\\b' | { read -r -N 4 var; echo "var='$var'"; }
|
|
|
|
- name: "read -u with invalid file descriptor"
|
|
ignore_stderr: true
|
|
stdin: |
|
|
read -u 99 var
|
|
echo "exit: $?"
|
|
|
|
- name: "read into readonly variable"
|
|
ignore_stderr: true
|
|
stdin: |
|
|
readonly myvar="original"
|
|
echo "test" | read myvar
|
|
echo "exit: $?, myvar='$myvar'"
|
|
|
|
- name: "read with mixed IFS into fewer variables"
|
|
stdin: |
|
|
# Complex IFS with fewer variables - verify remainder handling
|
|
echo 'x: y : z : w' | (IFS=': ' read a b; echo "a='$a' b='$b'")
|
|
|
|
# Backslash escape counting tests (bash counts OUTPUT chars, not INPUT)
|
|
- name: "read -n counts output chars after escape processing"
|
|
stdin: |
|
|
# Bash counts OUTPUT characters toward -n limit (after escape processing).
|
|
# Input: a, \, b, c = 4 input bytes. With -n 3:
|
|
# 'a' (output 1), '\b' → 'b' (output 2), 'c' (output 3) = "abc"
|
|
printf 'a\\bc' | { read -n 3 var; echo "var='$var'"; }
|
|
# Input: \, \, a, b = 4 input bytes. With -n 3:
|
|
# '\\' → '\' (output 1), 'a' (output 2), 'b' (output 3) = "\ab"
|
|
printf '\\\\ab' | { read -n 3 var; echo "var='$var'"; }
|
|
|
|
- name: "read -n with backslash-newline continuation"
|
|
stdin: |
|
|
# Backslash-newline is line continuation (consumes both, outputs nothing).
|
|
# Input: a, b, \, \n, c = 5 input bytes. With -n 3:
|
|
# 'a' (output 1), 'b' (output 2), '\n' → continuation, 'c' (output 3) = "abc"
|
|
printf 'ab\\\nc' | { read -n 3 var; echo "var='$var'"; }
|
|
|
|
- name: "read trailing backslash discarded on EOF"
|
|
stdin: |
|
|
# Bash discards a pending backslash when EOF is reached
|
|
printf 'ab\\' | { read var; echo "var='$var'"; }
|
|
printf 'ab\\' | { read -n 5 var; echo "var='$var'"; }
|
|
|
|
# Prompt tests (read -p)
|
|
- name: "read -p reads input correctly"
|
|
stdin: |
|
|
# Test that -p option reads input correctly (prompt display behavior
|
|
# differs between bash and brush when stdin is not a terminal)
|
|
read -p "Enter: " var <<< "hello"
|
|
echo "var='$var'"
|
|
|
|
# File descriptor tests (read -u)
|
|
- name: "read -u with valid file descriptor"
|
|
stdin: |
|
|
# Read from a specific file descriptor
|
|
exec 3<<< "from fd 3"
|
|
read -u 3 var
|
|
echo "var='$var'"
|
|
exec 3<&-
|
|
|
|
- name: "read -u combined with other options"
|
|
stdin: |
|
|
exec 4<<< "hello world test"
|
|
read -u 4 -a arr
|
|
declare -p arr
|
|
exec 4<&-
|
|
|
|
# Silent mode tests (read -s)
|
|
- name: "read -s with pipe input"
|
|
stdin: |
|
|
# Silent mode should still read input correctly (just not echo it)
|
|
echo "secret" | { read -s var; echo "var='$var'"; }
|
|
|
|
- name: "read -s with multiple variables"
|
|
stdin: |
|
|
echo "a b c" | { read -s v1 v2 v3; echo "v1='$v1' v2='$v2' v3='$v3'"; }
|
|
|
|
- name: "read into nameref variable"
|
|
known_failure: true
|
|
stdin: |
|
|
declare -n ref=result
|
|
read ref <<< "input_value"
|
|
echo "result: $result"
|
|
echo "ref: $ref"
|
|
|
|
- name: "read -a into nameref array"
|
|
known_failure: true
|
|
stdin: |
|
|
declare -n ref=myarr
|
|
read -a ref <<< "one two three"
|
|
echo "myarr[0]: ${myarr[0]}"
|
|
echo "myarr[1]: ${myarr[1]}"
|
|
echo "myarr[2]: ${myarr[2]}"
|
|
echo "ref[1]: ${ref[1]}"
|
|
|
|
- name: "read into subscripted nameref"
|
|
known_failure: true
|
|
stdin: |
|
|
arr=(a b c)
|
|
declare -n ref='arr[1]'
|
|
read ref <<< "replaced"
|
|
echo "arr[0]: ${arr[0]}"
|
|
echo "arr[1]: ${arr[1]}"
|
|
echo "arr[2]: ${arr[2]}"
|