Mar*_*tin 94 bash shell-script
我显然明白可以为内部字段分隔符变量添加值。例如:
$ IFS=blah
$ echo "$IFS"
blah
$
Run Code Online (Sandbox Code Playgroud)
我也明白这read -r line会将数据保存stdin到名为的变量line:
$ read -r line <<< blah
$ echo "$line"
blah
$
Run Code Online (Sandbox Code Playgroud)
但是,命令如何分配变量值?它是否首先存储来自stdinto 变量的数据line,然后赋予lineto 的值IFS?
Sté*_*las 144
在 POSIX shell 中read,没有任何选项不读取line,它从(可能是反斜杠连续的)行读取单词,其中单词被$IFS分隔,反斜杠可用于转义分隔符(或继续行)。
通用语法是:
read word1 word2... remaining_words
Run Code Online (Sandbox Code Playgroud)
read一次读取一个字节¹,直到找到一个未转义的换行符(或输入结束),根据复杂的规则将其拆分并将拆分的结果存储为$word1, $word2... $remaining_words。
例如在像这样的输入上:
<tab> foo bar\ baz bl\ah blah\
whatever whatever
Run Code Online (Sandbox Code Playgroud)
并使用默认值$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和""( 以及""一些实现的额外内容,尽管除了 之外无关紧要read -a)。而如果我们:用空格替换它,分裂就只完成了foo和bar。即忽略前导和尾随,并将它们的序列视为一个序列。当空白字符和非空白字符在$IFS. 某些实现可以通过将 IFS (IFS=::或IFS=' ') 中的字符加倍来添加/删除特殊处理。
所以在这里,如果我们不想去除前导和尾随未转义的空白字符,我们需要从 IFS 中删除那些 IFS 空白字符。
即使使用 IFS 非空白字符,如果输入行包含这些字符中的一个(并且只有一个),并且它是具有 POSIX shell(不是也不是某些版本)的行中的最后一个字符(例如IFS=: read -r word在输入上),则该输入被视为一个词,因为在这些 shell 中,字符被视为终止符,因此将包含,而不是。foo:zshpdkshfoo$IFSwordfoofoo:
因此,使用read内置函数读取一行输入的规范方法是:
IFS= read -r line
Run Code Online (Sandbox Code Playgroud)
(请注意,对于大多数read实现,它仅适用于文本行,因为除了 in 之外不支持 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
Run Code Online (Sandbox Code Playgroud)
如果cmd是内置(如是read),则在完成后var保持设置。这一点尤其重要,因为在 Bourne shell 中,它用于拆分所有内容,而不仅仅是扩展。此外,如果您从Bourne shell 中删除空格字符,则不再有效。valuecmd$IFS$IFS$IFS"$@"
在 Bourne shell 中,重定向复合命令会导致它在子 shell 中运行(在最早的版本中,甚至类似read var < file或exec 3< file; read var <&3不起作用的东西),因此在 Bourne shell 中很少read用于除终端上的用户输入之外的任何内容(该行继续处理有意义的地方)
一些 Unices(如 HP/UX,也有一个 in util-linux)仍然有一个line命令来读取一行输入(在 Single UNIX Specification version 2之前,它曾经是标准的 UNIX 命令)。
head -n 1除了一次读取一个字节以确保它不会读取多于一行之外,这与基本相同。在这些系统上,您可以执行以下操作:
line=`line`
Run Code Online (Sandbox Code Playgroud)
当然,这意味着生成一个新进程,执行命令并通过管道读取其输出,因此效率比 ksh 低很多IFS= read -r line,但仍然直观得多。
¹尽管在可搜索输入上,一些实现可以恢复为按块读取,然后作为优化返回。ksh93 更进一步并记住读取的内容并将其用于下一次read调用,尽管目前已损坏
² IFS 空白字符,每个 POSIX 是[:space:]在语言环境中分类的字符,$IFS尽管在 ksh88(POSIX 规范所基于的)和大多数 shell 中,它仍然限于 SPC、TAB 和 NL。我发现在这方面唯一符合 POSIX 的 shell 是yash. ksh93并且bash(从 5.0 开始)还包括其他空格(例如 CR、FF、VT...),但仅限于单字节(在某些系统上(例如 Solaris)要小心,其中包括单字节的不间断空格在某些地区)
use*_*791 17
这里有两个概念在起作用:
IFS是输入字段分隔符,这意味着读取的字符串将根据IFS. 在命令行上,IFS通常是任何空白字符,这就是命令行在空格处拆分的原因。VAR=value command意味着“修改命令的环境,使其VAR具有价值value”。基本上,该命令command将被VAR视为具有 value value,但在此之后执行的任何命令仍将被VAR视为具有其先前的值。换句话说,该变量将仅针对该语句进行修改。因此,在执行时IFS= read -r line,您所做的是设置IFS为空字符串(不会使用字符进行拆分,因此不会发生拆分),以便read读取整行并将其视为将分配给line变量的一个单词。更改IFS仅影响该语句,因此任何后续命令都不会受到更改的影响。
虽然命令是正确的并且可以按预期工作,但IFS在这种情况下设置is not 1可能没有必要。如内置部分的bash手册页中所写read:
从标准输入 [...] 读取一行,第一个单词分配给第一个 name,第二个单词分配给第二个 name,依此类推,剩余的单词及其中间分隔符分配给 last name。如果从输入流中读取的单词少于名称,则剩余的名称将被分配空值。中的字符
IFS用于将行拆分为单词。[...]
由于您只有line变量,因此无论如何都会将每个单词分配给它,因此如果您不需要任何前面和后面的空格字符1 ,您可以编写read -r line并完成它。
[1] 作为一个unset或默认$IFS值将如何导致read前导/尾随IFS 空白的示例,您可以尝试:
echo ' where are my spaces? ' | {
unset IFS
read -r line
printf %s\\n "$line"
} | sed -n l
Run Code Online (Sandbox Code Playgroud)
运行它,您将看到如果IFS未取消设置,前面和后面的字符将无法生存。此外,如果$IFS要在脚本的较早位置进行修改,可能会发生一些奇怪的事情。
您应该分两部分阅读该语句,第一部分清除 IFS 变量的值,即相当于更易读的IFS="",第二部分是line从标准输入读取变量,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\]
Run Code Online (Sandbox Code Playgroud)
清除 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]
Run Code Online (Sandbox Code Playgroud)
感谢 rici 指出这种差异。