为什么使用命令替换时换行符会丢失?

use*_*373 48 shell command-substitution newlines

我有一个名为 links.txt 的文本文件,它看起来像这样

link1
link2
link3
Run Code Online (Sandbox Code Playgroud)

我想逐行遍历这个文件并对每一行执行一个操作。我知道我可以使用 while 循环来做到这一点,但由于我正在学习,我想使用 for 循环。我实际上使用了这样的命令替换

a=$(cat links.txt)
Run Code Online (Sandbox Code Playgroud)

然后像这样使用循环

for i in $a; do ###something###;done
Run Code Online (Sandbox Code Playgroud)

我也可以做这样的事情

for i in $(cat links.txt); do ###something###; done
Run Code Online (Sandbox Code Playgroud)

现在我的问题是当我在变量 a 中替换 cat 命令输出时,link1 link2 和 link3 之间的换行符被删除并被空格替换

echo $a
Run Code Online (Sandbox Code Playgroud)

产出

链接1 链接2 链接3

然后我使用了 for 循环。当我们执行命令替换时,是否总是用空格替换新行?

问候

Joh*_*ith 44

换行符在某些时候会被换掉,因为它们是特殊字符。为了保留它们,您需要使用引号确保它们始终被解释:

$ a="$(cat links.txt)"
$ echo "$a"
link1
link2
link3
Run Code Online (Sandbox Code Playgroud)

现在,由于我在操作数据时使用引号,换行符 ( \n) 总是由 shell 解释,因此保留下来。如果您在某些时候忘记使用它们,这些特殊字符将丢失。

如果您在包含空格的行上使用循环,则会发生完全相同的行为。例如,给定以下文件...

mypath1/file with spaces.txt
mypath2/filewithoutspaces.txt
Run Code Online (Sandbox Code Playgroud)

输出将取决于您是否使用引号:

$ for i in $(cat links.txt); do echo $i; done
mypath1/file
with
spaces.txt
mypath2/filewithoutspaces.txt

$ for i in "$(cat links.txt)"; do echo "$i"; done
mypath1/file with spaces.txt
mypath2/filewithoutspaces.txt
Run Code Online (Sandbox Code Playgroud)

现在,如果您不想使用引号,可以使用一个特殊的 shell 变量来更改 shell 字段分隔符 ( IFS)。如果您将此分隔符设置为换行符,您将摆脱大多数问题。

$ IFS=$'\n'; for i in $(cat links.txt); do echo $i; done
mypath1/file with spaces.txt
mypath2/filewithoutspaces.txt
Run Code Online (Sandbox Code Playgroud)

为了完整起见,这里是另一个示例,它不依赖于命令输出替换。一段时间后,我发现由于该read实用程序的行为,大多数用户认为此方法更可靠。

$ cat links.txt | while read i; do echo $i; done
Run Code Online (Sandbox Code Playgroud)

以下是read's 手册页的摘录:

read 实用程序应从标准输入中读取一行。

由于read逐行获取其输入,因此只要出现空格,您就可以确定它不会中断。只需通过cat管道将输出传递给它,它就会很好地遍历您的行。

编辑:我可以从其他答案和评论中看出,人们在使用cat. 正如jasonwryan在他的评论中所说,在 shell 中读取文件的更正确方法是使用流重定向 ( <),正如您在val0x00ff 的回答中看到的那样。但是,由于问题不是“如何在 shell 编程中读取/处理文件”,我的回答更多地关注引号行为,而不是其他。

  • 尽管所有人都尊重约翰·WH·史密斯,但我不确定谁会赞成这个答案。`for i in $(cat ..)` 是错误的。请参阅“jasonwryan”的评论。这就是从文件中读取行的方式。cat(1) 用于将多个文件连接在一起。它不应该用于将文件数据提供给进程。有更好的方法可以实现这一目标。应用程序可能采用文件作为参数(例如 grep ^foo 文件);或者您可能想使用文件重定向(例如读取行&lt;文件)。 (2认同)

cuo*_*glm 40

换行符丢失了,因为 shell在命令替换后执行了字段拆分

在 POSIX命令替换部分:

shell 应通过在子 shell 环境中执行命令来扩展命令替换(请参阅 Shell 执行环境)并将命令替换(命令文本加上封闭的“$()”或反引号)替换为命令的标准输出,删除替换结束时的一个或多个字符的序列。输出结束前的嵌入字符不得删除;但是,它们可能会被视为字段分隔符并在字段拆分期间被消除,具体取决于 IFS 的值和有效的引用。如果输出包含任何空字节,则行为未指定。

默认IFS值(至少在bash):

$ printf '%q\n' "$IFS"
$' \t\n'
Run Code Online (Sandbox Code Playgroud)

在您的情况下,您没有设置IFS或使用双引号,因此在字段拆分期间将消除换行符。

您可以保留换行符,例如设置IFS为空:

$ IFS=
$ a=$(cat links.txt)
$ echo "$a"
link1
link2
link3
Run Code Online (Sandbox Code Playgroud)

  • @OliverDungey,这不是关于“echo”或“printf”,而是关于双引号“$a”。最初的问题是使用 for 循环,即命令替换后发生字段分割的情况。 (2认同)

gle*_*man 6

为了增加我的重点,for循环遍历words。如果您的文件是:

one two
three four
Run Code Online (Sandbox Code Playgroud)

那么这将发出4线:

for word in $(cat file); do echo "$word"; done
Run Code Online (Sandbox Code Playgroud)

要遍历文件的,请执行以下操作:

while IFS= read -r line; do
    # do something with "$line" <-- quoted almost always
done < file
Run Code Online (Sandbox Code Playgroud)