读 -N 和 IFS

read -N and IFS

根据手册页中的"read -N"描述:

-N nchars return 仅在准确读取 NCHARS 个字符后,除非遇到 EOF 或读取超时,忽略任何分隔符

但是,为了响应以下命令:

$ echo 'a b' | while read -N1 c; do echo ">>>$c<<<"; done
>>>a<<<
>>><<<
>>>b<<<
>>><<<

space和换行符都被翻译成空字符串,而在命令中:

$ echo 'a b' | while IFS= read -N1 c; do echo ">>>$c<<<"; done
>>>a<<<
>>> <<<
>>>b<<<
>>>
<<<

space 和换行符已正确存储在变量中。

所以,似乎分隔符在"read"或"while"命令中仍然有一些处理,我不明白。

我们可以将这些结果与使用 "read -n" 的结果进行比较,该手册描述为:

-n nchars return 在读取 NCHARS 字符之后而不是等待换行符,但如果在分隔符[=之前读取的字符少于 NCHARS 字符,则使用分隔符13=]

$ echo 'a b' | while read -n1 c; do echo ">>>$c<<<"; done
>>>a<<<
>>><<<
>>>b<<<
>>><<<

$ echo 'a b' | while IFS= read -n1 c; do echo ">>>$c<<<"; done
>>>a<<<
>>> <<<
>>>b<<<
>>><<<

我认为,在使用选项 -N 时,read 的行为在

时是不同的
  • 读取一个定界符作为输入
  • 分配那个分隔符给一个变量

当它读取一个字符时,定界符被视为与非定界符相同,read 将计算它们。但是,当 read 分配定界符时,它会考虑读取输入是否为定界符,如果它是定界符,则将 null 分配给相应的变量。

因此,IFS= 将更改将 white-space 分配给变量的行为,并导致将 space 分配给 c 而不是 null .

这是POSIX行为。赋值给一个变量时,IFS字符应该被剥离:结果应该像shell一样被分割成字段shell参数扩展的结果(当然,-n和-N 不是 POSIX).

这是由 read 源代码注释得出的:

/* This code implements the Posix.2 spec for splitting the words
     read and assigning them to variables. */
  orig_input_string = input_string;

  /* Remove IFS white space at the beginning of the input string.  If
     $IFS is null, no field splitting is performed. */

read 无法确定字符是否为定界符(忽略它),直到它已经读取了该字符,并且 read 必须分配 some值到 c,即使该值是空字符串。当读取并随后丢弃定界符时,c 的值必须设置为 something,因此它被分配为空字符串。

这与不使用 -n/-N 选项的 read 一致;分隔符仅在 被读取并且不需要设置所提供参数的值时被丢弃。最简单的情况是您不向 read:

提供任何参数
$ read <<< " a b c "
$ echo ">>>$REPLY<<<"
>>> a b c <<<

使用单个显式参数,去除前导和尾随定界符:

$ read line <<< " a b c "
$ echo ">>>$line<<<"
>>>a b c<<<

使用两个参数,第一个定界符一旦被读取就会被忽略。第二个保留,因为字符串只需要拆分成两个词来填充提供的参数。

$ read field1 field2 <<< " a b c """
$ echo ">>>$field1<<<"
>>>a<<<
$ echo ">>>$field2<<<"
>>>b c<<<

使用 hexdump 可以让我们准确地看到组成输出的字符,因此稍微更改您的查询可能会有所帮助:

(1) 使用普通 IFS 并使用 -N 选项

$ (echo 'a b' | while read -N1 c; do c="$c<"; echo -n "$c"; done | hexdump -C)
00000000  61 3c 3c 62 3c 3c                                 |a<<b<<|
00000006 

在第一种情况下,0x0a 和 space 字符的内置读取 return 是空字符串,因为字符在默认 IFS 中,而字符在 IFS 中由于 cdarke 的回答中解释的原因,在输出中被忽略。

(2) 使用空 IFS 和 -N 选项

$ (IFS=""; echo 'a b' | while read -N1 c; do c="$c<"; echo -n "$c"; done | hexdump -C)
00000000  61 3c 20 3c 62 3c 0a 3c                              |a< <b<.<|
00000008

在这种情况下,read builtin 将匹配 echo 命令输出的四个字符中的每一个,并且在输出中可以看到 0x0a 和 space,因为空 IFS读取的字符可以赋值给局部变量c.

(3) 使用普通 IFS 和 -n 选项

$ (echo 'a b' | while read -n1 c; do c="$c<"; echo -n "$c"; done | hexdump -C)
00000000  61 3c 3c 62 3c 3c                                 |a<<b<<|
00000006 

这给出了与案例 (1) 相同的输出,尽管语义有点不同:0x0a 和 space 字符的 read 内置 return 空字符串,因为 (i) 这两个字符都在默认 IFS 中,并且 (ii) read 内置函数的 -n 选项在任何情况下都不会传递尾随 0x0a 字符

(4) 使用空 IFS 和 -n 选项

$ (IFS=""; echo 'a b' | while read -n1 c; do c="$c<"; echo -n "$c"; done | hexdump -C)
00000000  61 3c 20 3c 62 3c 3c                              |a< <b<<|
00000007

这里我们观察到读取的 -n 和 -N 选项之间的区别:使用 -n 选项时,换行符由 read 内置函数特殊处理并丢弃,因此从 IFS 中排除 0x0a没有机会允许它传递给局部变量 c.