Skip to content

Commit

Permalink
Convert $IFS to a read-specific thing
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mqudsi committed Dec 16, 2024
1 parent ee19759 commit b3ea21e
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 4 deletions.
12 changes: 10 additions & 2 deletions src/builtins/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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() {
Expand Down
63 changes: 61 additions & 2 deletions tests/checks/read.fish
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand Down

0 comments on commit b3ea21e

Please sign in to comment.