了解“ IFS =读-r行”


60

我显然明白,可以为内部字段分隔符变量添加值。例如:

$ IFS=blah
$ echo "$IFS"
blah
$ 

我也知道这read -r line会将数据从保存stdin到名为的变量line

$ read -r line <<< blah
$ echo "$line"
blah
$ 

但是,命令如何分配变量值?它是否首先存储数据from stdin到变量line,然后给出lineto的值IFS


Answers:


104

某些人有错误的概念,read即读取行的命令。不是。

read从(可能是反斜杠连续的)行中读取单词,其中的单词是$IFS定界的,反斜杠可用于转义定界符(或继续行)。

通用语法为:

read word1 word2... remaining_words

read一次读取标准输入一个字节直到找到一个未转义换行符(或输入结束),分裂,根据复杂的规则并存储分割的结果为$word1$word2... $remaining_words

例如在类似的输入上:

  <tab> foo bar\ baz   bl\ah   blah\
whatever whatever

并用默认值$IFSread a b c将会分配:

  • $afoo
  • $bbar baz
  • $cblah blahwhatever whatever

现在,如果仅传递一个参数,则不会变为read line。还在read remaining_words。反斜杠处理仍然完成,IFS空格字符仍从开头和结尾删除。

-r选项删除反斜杠处理。所以上面的相同命令-r将分配

  • $afoo
  • $bbar\
  • $cbaz bl\ah blah\

现在,对于拆分部分,重要的是要认识到有两类字符$IFS:IFS空格字符(即空格和制表符(以及换行符,尽管在这里除非使用-d无关紧要),否则也会发生)设为$IFS)和其他默认值。这两类字符的处理方式不同。

随着IFS=::是不是IFS空白字符),如输入:foo::bar::将被分裂成"""foo"""bar""(和一个额外的""一些实现方式虽然不除无所谓read -a)。如果将其替换:为空格,则拆分为only foobar。那就是前导和尾随的被忽略,并且它们的序列被视为一个。将中的空白字符和非空白字符结合使用时,还有其他规则$IFS。一些实现可以通过将IFS中的字符加倍(IFS=::IFS=' ')来添加/删除特殊处理。

因此,在这里,如果我们不希望删除开头和结尾的未转义的空白字符,则需要从IFS中删除那些IFS空白字符。

即使使用IFS非空白字符,如果输入行包含这些字符中的一个(只有一个),并且它是该行中的最后一个字符(IFS=: read -r word例如foo:),则使用POSIX shell(不是zsh某些pdksh版本),该输入之所以被视为一个foo单词,是因为在这些shell中,这些字符$IFS被视为终结符,因此word将包含foo,而不是foo:

因此,使用read内置方法读取一行输入的规范方法是:

IFS= read -r line

(请注意,对于大多数read实现,该方法仅适用于文本行,因为除中不支持NUL字符zsh)。

使用var=value cmd语法可确保IFS仅在该cmd命令期间设置不同的设置。

历史记录

read内置由Bourne shell的引入,已经读的话,而不是行。现代POSIX外壳有一些重要的区别。

Bourne shell read不支持-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是内置的(如readis),则在完成后var仍设置为。这一点特别重要,因为在Bourne shell中,它用于拆分所有内容,而不仅仅是扩展。另外,如果您从Bourne外壳程序中删除空格字符,将不再起作用。valuecmd$IFS$IFS$IFS"$@"

在Bourne Shell中,重定向复合命令会使它在子Shell中运行(在最早的版本中,即使类似read var < fileexec 3< file; read var <&3不起作用的东西),因此在Bourne Shell中很少read用于除终端上的用户输入以外的任何内容(在该行继续处理有意义的地方)

某些Unices(例如HP / UX,也有in in util-linux)仍然具有line读取一行输入的命令(在Single UNIX Specification版本2之前它一直是标准UNIX命令)。

基本上与一次head -n 1读取相同,只是一次读取一个字节以确保读取的行不超过一行。在这些系统上,您可以执行以下操作:

line=`line`

当然,这意味着产生一个新进程,执行一个命令并通过管道读取其输出,因此效率比ksh差IFS= read -r line很多,但仍然更加直观。


3
+1感谢您对bash中IFS中空格/制表符与“其他”的不同处理的一些有用见解...我知道它们的处理方式有所不同,但是此说明将其简化了很多。(而且bash(和其他posix shell)与常规sh差异之间的洞察力对于编写可移植脚本也很有用!)
Olivier Dulac

至少对于bash-4.4.19while read -r; do echo "'$REPLY'"; done起作用while IFS= read -r line; do echo "'$line'"; done
x-yuri

这是:“ ...读取的错误概念是读取行的命令...”使我想到,如果使用read读取行是错误的,则还必须有其他东西。这个无误的概念是什么?还是从技术上来说,第一个语句是正确的,但实际上,无误的概念是:“ read是从一行中读取单词的命令。由于它功能如此强大,您可以通过执行以下操作从文件中读取行:IFS= read -r line
Mike S

8

理论

这里有两个概念在起作用:

  • IFS是输入字段分隔符,这表示将根据中的字符对读取的字符串进行分割IFS。在命令行上,IFS通常是空格字符,这就是命令行在空格处分割的原因。
  • 做类似的事情VAR=value command意味着“修改命令环境,使其VAR具有值value”。基本上,该命令commandVAR具有值value,但此后执行的任何命令仍将VAR具有其先前的值。换句话说,将仅针对该语句修改该变量。

在这种情况下

因此,在执行操作时IFS= read -r line,您正在将其设置IFS为一个空字符串(不使用任何字符来拆分,因此不会进行拆分),以便read读取整行并将其视为分配给line变量的一个单词。IFS所做的更改仅影响该语句,因此所有后续命令都不会受到更改的影响。

作为旁注

虽然该命令是正确的,并会工作打算,设置IFS在这种情况下是不是 强权1没有必要。如内置部分的bash手册页中所述read

从标准输入中读取一行,并将第一个单词分配给名字,第二个单词分配给名字,依此类推,剩下的单词及其中间的分隔符分配给姓氏。如果从输入流中读取的单词少于名称,则为其余名称分配空值。中的字符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

运行它,您会发现,如果IFS未设置,则前面和后面的字符将无法生存。此外,如果$IFS要在脚本的较早位置进行修改,可能会发生一些奇怪的事情。


5

您应该分两部分阅读该语句,第一部分清除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指出了这种差异。


您所缺少的是,如果不更改IFS,read -r line则在将输入分配给line变量之前将修剪前导和尾随空格。
rici

@rici我怀疑这样的事情,但是只检查了单词之间的IFS字符,而不是前导/后缀字符。感谢您指出这一事实!
jlliagre 2015年

清除IFS还将防止分配多个变量(副作用)。 IFS= read a b <<< 'aa bb' ; echo "-$a-$b-"将显示-aa bb--
kyodev
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.