我显然知道可以为内部字段分隔符变量添加值。例如:
$ IFS=blah
$ echo "$IFS"
blah
$
我还了解到这read -r line
会将数据保存到stdin
名为 的变量line
:
$ read -r line <<< blah
$ echo "$line"
blah
$
然而,命令如何给变量赋值呢?它是否首先存储stdin
to 变量的数据line
,然后给出line
to的值IFS
?
答案1
在 POSIX shell 中read
,不带任何选项的 不会读取线,它写着字来自(可能是反斜杠继续的)行,其中单词被$IFS
分隔,反斜杠可用于转义分隔符(或连续行)。
通用语法是:
read word1 word2... remaining_words
read
一次读取 stdin 一个字节,直到找到未转义的换行符(或输入结束),根据复杂的规则将其拆分,并将拆分结果存储到$word1
, $word2
...中$remaining_words
。
例如对于这样的输入:
<tab> foo bar\ baz bl\ah blah\
whatever whatever
并使用默认值$IFS
,read a b c
将分配:
$a
⇐foo
$b
⇐bar baz
$c
⇐blah blahwhatever whatever
现在,如果仅传递一个参数,则不会变成read line
.依然是read remaining_words
。反斜杠处理仍然完成,IFS 空白字符²仍然从开头和结尾删除。
该-r
选项删除反斜杠处理。因此上面的相同命令-r
将改为分配
$a
⇐foo
$b
⇐bar\
$c
⇐baz bl\ah blah\
现在,对于分割部分,重要的是要认识到有两类字符$IFS
:IFS 空白字符²(包括空格和制表符(以及换行符,尽管在这里除非使用 -d ,否则这并不重要),这也会发生为$IFS
) 和其他的默认值。对这两类角色的处理是不同的。
对于IFS=:
(:
不是 IFS 空白字符),像这样的输入:foo::bar::
将被分成""
、"foo"
、""
和(以及一些实现的额外内容bar
,尽管除了 之外这并不重要)。而如果我们将其替换为空格,则会将其拆分为仅和。也就是说,前导和尾随的序列将被忽略,并且它们的序列将被视为一个序列。当空格和非空格字符在 中组合时,还有其他规则。某些实现可以通过加倍 IFS 中的字符(或)来添加/删除特殊处理。""
""
read -a
:
foo
bar
$IFS
IFS=::
IFS=' '
所以在这里,如果我们不想删除前导和尾随的未转义空白字符,我们需要从 IFS 中删除那些 IFS 空白字符。
即使使用 IFS 非空白字符,如果输入行包含一个(且仅一个)这些字符,并且它是POSIX shell(不是也不是某些版本)的行中的最后一个字符(就像IFS=: read -r word
在类似 的输入上) ,则该输入被视为一个单词,因为在这些 shell 中,字符被视为foo:
zsh
pdksh
foo
$IFS
终结者,所以word
将包含foo
,不包含foo:
。
因此,使用内置函数读取一行输入的规范方法read
是:
IFS= read -r line
(请注意,对于大多数read
实现,这只适用于文本行,因为除 之外不支持 NUL 字符zsh
)。
使用var=value cmd
语法确保IFS
仅在该命令的持续时间内进行不同的设置cmd
。
历史笔记
该read
内置函数是由 Bourne shell 引入的,并且已经可以阅读字,不是线。与现代 POSIX shell 有一些重要的区别。
Bourne shellread
不支持-r
选项(由 Korn shell 引入),因此除了用类似的方法预处理输入之外,没有办法禁用反斜杠处理sed 's/\\/&&/g'
。
Bourne shell 没有两类字符的概念(这也是由 ksh 引入的)。在 Bourne shell 中,所有字符都经过与 ksh 中的 IFS 空白字符相同的处理,即IFS=: read a b c
在输入上(如foo::bar
分配给bar
)$b
,而不是空字符串。
在 Bourne shell 中,使用:
var=value cmd
如果cmd
是内置的(就像read
是),则在完成后var
保持设置。这一点尤其重要,因为在 Bourne shell 中,它用于分割所有内容,而不仅仅是扩展。另外,如果您从Bourne shell 中删除空格字符,则不再起作用。value
cmd
$IFS
$IFS
$IFS
"$@"
在 Bourne shell 中,重定向复合命令会导致它在子 shell 中运行(在最早的版本中,甚至类似read var < file
或exec 3< file; read var <&3
不起作用),因此在 Bourne shell 中很少用于read
除终端上的用户输入之外的任何内容(行延续处理有意义的地方)
一些 Unices(例如 HP/UX,在 中也有一个util-linux
)仍然有一个line
命令来读取一行输入(这曾经是一个标准的 UNIX 命令,直到单一 UNIX 规范版本 2)。
这基本上是相同的,head -n 1
只是它一次读取一个字节以确保它不会读取超过一行。在这些系统上,您可以执行以下操作:
line=`line`
当然,这意味着生成一个新进程,执行命令并通过管道读取其输出,因此效率比 ksh 低很多IFS= read -r line
,但仍然直观得多。
1 尽管在可查找输入上,某些实现可以恢复为按块读取,然后作为优化进行回溯。 ksh93 更进一步,它会记住所读取的内容并将其用于下一次read
调用目前已损坏
²IFS 空白字符,每个 POSIX 是在语言环境中分类的字符[:space:]
,并且恰好$IFS
在 ksh88(POSIX 规范所基于的)和大多数 shell 中,这仍然限于 SPC、TAB 和 NL。我发现在这方面唯一符合 POSIX 标准的 shell 是yash
.ksh93
并且bash
(自 5.0 起)还包括其他空格(例如 CR、FF、VT...),但仅限于单字节空格(请注意某些系统(如 Solaris),其中包括单字节的不间断空格在某些地区)
答案2
理论
这里有两个概念在起作用:
IFS
是输入字段分隔符,这意味着读取的字符串将根据 中的字符进行分割IFS
。在命令行上,IFS
通常是任何空白字符,这就是命令行在空格处分割的原因。- 做类似的事情
VAR=value command
意味着“修改命令环境,使其VAR
具有价值value
”。基本上,该命令command
将被视为VAR
具有值value
,但此后执行的任何命令仍将被视为VAR
具有其先前的值。换句话说,该变量将仅针对该语句进行修改。
在这种情况下
因此,在执行 时IFS= read -r line
,您所做的就是设置IFS
一个空字符串(不会使用任何字符来拆分,因此不会发生拆分),以便read
读取整行并将其视为将分配给变量的一个单词line
。更改IFS
仅影响该语句,因此任何后续命令都不会受到更改的影响。
作为旁注
虽然命令是正确的并且将按预期工作,但IFS
在这种情况下的设置并不正确 可能1不是必要的。正如内置部分bash
的手册页中所写read
:
从标准输入 [...] 读取一行,第一个单词分配给第一个名称,第二个单词分配给第二个名称,依此类推,将剩余单词及其中间分隔符分配给姓氏。如果从输入流中读取的单词少于名称,则其余名称将分配为空值。中的字符
IFS
用于将行分割成单词。 [...]
由于您只有line
变量,因此每个单词都会被分配给它,所以如果您不需要任何前面和后面的空白字符1你可以只写read -r line
并完成它。
unset
[1] 作为默认值或默认$IFS
值如何导致read
考虑前导/尾随的示例IFS 空白,你可以尝试:
echo ' where are my spaces? ' | {
unset IFS
read -r line
printf %s\\n "$line"
} | sed -n l
IFS
运行它,你会发现如果不取消设置,前面和后面的字符将不会保留。此外,如果$IFS
在脚本的较早位置进行修改,可能会发生一些奇怪的事情。
答案3
您应该分两部分阅读该语句,第一部分清除 IFS 变量的值,即相当于更具可读性IFS=""
,第二部分是line
从 stdin 读取变量read -r line
。
该语法的具体之处在于 IFS 影响是暂时的并且仅对命令有效read
。
除非我遗漏了一些东西,否则在这种特殊情况下,清除IFS
没有任何效果,尽管无论IFS
设置什么,整行都将在line
变量中读取。仅当多个变量作为参数传递给指令时,行为才会发生变化read
。
编辑:
允许-r
以 结尾的输入\
不进行特殊处理,即反斜杠包含在line
变量中而不是作为连续字符以允许多行输入。
$ read line; echo "[$line]"
abc\
> def
[abcdef]
$ read -r line; echo "[$line]"
abc\
[abc\]
清除 IFS 具有阻止读取以修剪潜在的前导和尾随空格或制表符的副作用,例如:
$ echo " a b c " | { IFS= read -r line; echo "[$line]" ; }
[ a b c ]
$ echo " a b c " | { read -r line; echo "[$line]" ; }
[a b c]
感谢 rici 指出了这一差异。
答案4
这是一个好答案从shell语法角度来看:
一个简单的命令是可选变量赋值的序列后跟空格分隔单词和重定向,并由控制操作符终止。第一个字指定要执行的命令,并作为参数零传递。其余单词作为参数传递给调用的命令。
任何简单命令或功能的环境都可以通过以下方式暂时增强:在其前面添加参数分配,如 Shell 参数中所述。这些赋值语句仅影响该命令所看到的环境。
IFS=""
仅可由此处的命令看到read
。换句话说,除了这一行之外,IFS
不会更改 的值。IFS