当IFS= read -r -d $'\0' file时,这行代码的含义是什么?

5

我不理解这行shell脚本。while语句难道不需要一个'test'或[ ]或[[ ]]表达式来将$?设置为1或0吗?这个脚本是如何运作的?

while IFS= read -r -d $'\0'; do ...; done

怎么做呢?非常感谢您帮忙理解这里的语法。


5
使用"-d $'\0'"容易引起误解,通常应改为使用"-d ''"。 - nosid
4个回答

13
在Bash中,varname=value command会将环境变量varname设置为value(并且所有其他环境变量都像通常继承一样)。因此,IFS= read -r -d $'\0'会使用空字符串设置环境变量IFS(意味着没有字段分隔符)来运行命令read -r -d $'\0'
由于read成功读取输入并且没有遇到文件结尾时返回成功(即将$?设置为0),因此总体效果是循环遍历一组以NUL分隔的记录(保存在变量REPLY中)。

while语句不需要'test'或[ ]或[[ ]]表达式来将$?设置为1或0吗?

test[ ... ][[ ... ]]实际上不是表达式,而是命令。在Bash中,每个命令都会返回成功(将$?设置为0)或失败(将$?设置为非零值,通常为1)。
(顺便说一下,正如nosid在上面的评论中指出的那样,-d $'\0'等效于-d ''。Bash变量在内部表示为C风格/NUL终止字符串,因此您实际上无法在字符串中包含NUL;例如,echo $'a\0b'只打印a。)

1
我认为OP也对while语句的语法感到困惑,没有意识到可以使用任意语句。 - user743382
@hvd:你可能是对的。我扩展了我的答案,直接解决了这个问题。 - ruakh

4
我不理解这行shell脚本。while语句难道不需要'test'或[ ]或[[ ]]表达式来将$?设置为1或0吗?那么怎么做到的呢?
大家都在回答-d和IFS=参数,但没有人回答你有关test和[[..]]的问题。所有标准的Unix命令在成功时返回零值,在退出时返回非零值。while和if语句使用该值来确定是否继续循环:
假设您执行以下操作:
$ touch foo.txt   #Create a "foo.txt" file
$ ls foo.txt
foo.txt
$ echo $?
0
$

请注意,ls命令返回了零值。现在尝试执行以下命令:
$ rm foo.txt
$ ls foo.txt
ls: foo.txt: No such file or directory
$ echo $?
1
$

如果 foo.txt 不存在,ls 命令返回退出代码 1。 ifwhile 命令可以操作这个退出代码。
打开另一个终端窗口并创建一个 foo.txt 文件。然后返回原始窗口并尝试执行以下命令:
$ while ls foo.txt > /dev/null
> do
> echo "The file foo.txt exists"
> sleep 2
> done
The file foo.txt exists
The file foo.txt exists
The file foo.txt exists
...

每两秒钟,while循环都会检查foo.txt是否存在,并打印出相应的语句。现在,回到第二个终端窗口并删除foo.txt。如果你回到第一个终端窗口,你会看到while循环结束了。
你也可以对if做同样的操作:
$ if ls foo.txt > /dev/null
> then
>     echo "foo.txt exists"
> else
>     echo "Whoops, it's gone!"
> fi

这个if语句将根据目录中是否存在名为foo.txt的文件,打印出foo.txt存在糟糕,它不见了!

注意,ifwhile会根据命令的返回退出值进行操作。完全没有必要进行测试。

那么为什么需要test[...][[...]]语法呢?

假设您想要找出$foo是否等于$bar,这就是test发挥作用的地方。试试这个:

$ foo=42
$ bar=42
$ test "$foo" -eq "$bar"
$ echo $?
0
$ bar=0
$ test "$foo" -eq "$bar"
$ echo $?
1

test命令只在测试结果为true时返回零,在测试结果为false时返回非零值。这使您可以在ifwhile语句中使用test命令。

$ if test $foo -eq $bar
> then
>     echo "foo is equal to bar"
> else
>     echo "foo and bar are different"
> fi
foo and bar are different
$

那么[...]是什么?
$ ls -il /bin/test /bin/[
10958 -rwxr-xr-x  2 root  wheel  18576 May 28 22:27 /bin/[
10958 -rwxr-xr-x  2 root  wheel  18576 May 28 22:27 /bin/test

第一列是 inode。如果两个文件具有相同的 inode,则它们是 硬链接 并引用同一个文件。请注意,在我的系统上,它们都是 18,576 字节。

[ 只是执行 test 命令的另一种方式。这两行命令是相同的:

if test $foo -eq $bar
if [ $foo -eq $bar ]
[的作用是让shell脚本看起来更加美观。但是,在其内部,它运行了一个test命令,该命令将返回一个零或非零值,可以被ifwhile所使用。
顺便说一下,在Kornshell和BASH中,[test都是shell的内建命令。但是,它们引用了相同的内建命令。 [[...]]是测试的一个特殊版本,最初出现在Kornshell中。它允许使用模式匹配,并且由于它不会生成另一个进程来运行test命令,因此速度更快一些。

2
虽然ls可能是演示目的的好选择,但在实际程序中使用它通常是一个错误。 - tripleee
@tripleee 这只是为了演示目的。ls 是一个易于理解且广为人知的命令。如果您想知道文件是否存在,请使用 [ -f ... ] - David W.

3

代码解释:

  • IFS= 将 IFS 设置为空(输入字段分隔符)
  • read 命令读取当前行,具有以下选项:
  • -r:不允许反斜杠转义任何字符
  • -d:继续读取,直到读取 DELIM 的第一个字符,而不是换行符
  • $'\0' 零字节(ASCII NUL 字符)

请参见

help read

2
不。命令test和表达式[[ ...]]只是提供一个反映其包含的条件表达式真假的退出状态(唯一关心的事情是while)的结构。
例如,如果$foo的值为bar,则[[ $foo = bar ]]的退出状态为0(成功),否则为非零存在状态(具体为1)。 while循环执行其主体,只要在while关键字后面的命令的退出状态为0。在此代码中,只要read可以从其输入中读取内容,它就具有0的退出状态。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接