Convert $IFS to a read-specific thing

This removes $IFS globally (which prevents fish internals from breaking when IFS
is manipulated) but keeps it as a legacy/deprecated mode of specifying `read`'s
`--delimiter` value for compatibility with (ba)sh and legacy fish scripts.
This commit is contained in:
Mahmoud Al-Qudsi 2024-12-15 21:21:47 -06:00
parent ee19759da1
commit b3ea21e7bc
2 changed files with 71 additions and 4 deletions

View file

@ -9,6 +9,7 @@ use crate::common::unescape_string;
use crate::common::valid_var_name;
use crate::common::UnescapeStringStyle;
use crate::env::EnvMode;
use crate::env::Environment;
use crate::env::READ_BYTE_LIMIT;
use crate::env::{EnvVar, EnvVarFlags};
use crate::input_common::terminal_protocols_disable_ifn;
@ -688,11 +689,18 @@ pub fn read(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Opt
continue;
}
// todo!("don't clone")
// Order of precedence: --delimiter, an explicitly set $IFS, the default IFS constant above.
// An empty --delimiter or $IFS is a special mode that splits by character (i.e. does not
// fall back to the next candidate).
let mut ifs_var = None;
let delimiter = opts
.delimiter
.as_ref()
.map(|delim| delim.as_utfstr())
.or_else(|| {
ifs_var = Some(parser.vars().get(L!("IFS")).map(|ifs| ifs.as_string()));
ifs_var.as_ref().unwrap().as_ref()
})
.map(|s| s.as_utfstr())
.unwrap_or(IFS);
if delimiter.is_empty() {

View file

@ -43,6 +43,31 @@ echo -n a | read -l one
echo "$status $one"
#CHECK: 0 a
# Test splitting input with IFS empty
set -l IFS
echo hello | read -l one
print_vars one
#CHECK: 1 'hello'
echo hello | read -l one two
print_vars one two
#CHECK: 1 'h' 1 'ello'
echo hello | read -l one two three
print_vars one two three
#CHECK: 1 'h' 1 'e' 1 'llo'
echo '' | read -l one
print_vars one
#CHECK: 0
echo t | read -l one two
print_vars one two
#CHECK: 1 't' 0
echo t | read -l one two three
print_vars one two three
#CHECK: 1 't' 0 0
echo ' t' | read -l one two
print_vars one two
#CHECK: 1 ' ' 1 't'
set -le IFS
echo 'hello there' | read -la ary
print_vars ary
#CHECK: 2 'hello' 'there'
@ -59,6 +84,18 @@ echo '' | read -la ary
print_vars ary
#CHECK: 0
set -l IFS
echo hello | read -la ary
print_vars ary
#CHECK: 5 'h' 'e' 'l' 'l' 'o'
echo h | read -la ary
print_vars ary
#CHECK: 1 'h'
echo '' | read -la ary
print_vars ary
#CHECK: 0
set -le IFS
# read -n tests
echo testing | read -n 3 foo
echo $foo
@ -190,7 +227,7 @@ echo abc\ndef | $fish -i -c 'read a; read b; set --show a; set --show b' | $filt
#CHECK: $b: set in global scope, unexported, with 1 elements
#CHECK: $b[1]: |def|
# Test --delimiter
# Test --delimiter (and $IFS, for now)
echo a=b | read -l foo bar
echo $foo
echo $bar
@ -212,7 +249,18 @@ echo $bar
echo $baz
#CHECK: b
# Default behavior
# IFS empty string
set -l IFS ''
echo a=b | read -l foo bar baz
echo $foo
#CHECK: a
echo $bar
#CHECK: =
echo $baz
#CHECK: b
# IFS unset
set -e IFS
echo a=b | read -l foo bar baz
echo $foo
#CHECK: a=b
@ -238,6 +286,17 @@ echo $b
#CHECK: b
echo $c
#CHECK: c
# Multi-char delimiters with IFS
begin
set -l IFS "..."
echo a...b...c | read -l a b c
echo $a
echo $b
echo $c
end
#CHECK: a
#CHECK: b
#CHECK: ..c
# At one point, whatever was read was printed _before_ banana
echo banana (echo sausage | read)