使用“边读边……”,echo和printf会得到不同的结果


14

根据这个问题“ 在Linux脚本中使用“边读边...

echo '1 2 3 4 5 6' | while read a b c;do echo "$a, $b, $c"; done

结果:

1, 2, 3 4 5 6

但是当我替换echoprintf

echo '1 2 3 4 5 6' | while read a b c ;do printf "%d, %d, %d \n" $a $b $c; done

结果

1, 2, 3
4, 5, 6

有人可以告诉我这两个命令有何不同吗?谢谢〜

Answers:


24

不只是echo vs printf

首先,让我们了解read a b c零件的情况。read将根据IFS变量的默认值space-tab-newline 进行单词拆分,并根据该值拟合所有内容。如果输入的内容多于要容纳的变量,它将把分割的部分放入第一个变量中,而无法拟合的部分将进入最后一个变量。这就是我的意思:

bash-4.3$ read a b c <<< "one two three four"
bash-4.3$ echo $a
one
bash-4.3$ echo $b
two
bash-4.3$ echo $c
three four

这正是bash《用户手册》中描述的方式(请参见答案结尾的引言)。

在您的情况下,发生的是,1和2适合a和b变量,而c接受其他所有值,即3 4 5 6

您还将while IFS= read -r line; do ... ; done < input.txt经常看到人们习惯于逐行读取文本文件。再次,IFS=这里是出于控制单词拆分的原因,或更具体地说-禁用它,并将一行文本读入变量。如果不存在,read将尝试使每个单词适合line变量。但这是另一个故事,我鼓励您以后再学习,因为它while IFS= read -r variable是一个非常常用的结构。

回声与打印行为

echo符合您在这里的期望。它完全按照read排列顺序显示变量。在前面的讨论中已经证明了这一点。

printf这是非常特殊的,因为它将继续将变量拟合为格式字符串,直到用尽所有变量为止。因此,当您执行printf "%d, %d, %d \n" $a $b $cprintf时,看到格式字符串带有3个小数,但是参数多于3个(因为您的变量实际上扩展为单个1,2,3,4,5,6)。这听起来可能令人困惑,但是出于某种原因而存在,因为它的行为比实际 printf()函数在C语言中的行为要好。

您在这里还影响输出的是,您的变量未加引号,这使shell(not printf)可以将变量分解为6个单独的项目。将此与引用进行比较:

bash-4.3$ read a b c <<< "1 2 3 4"
bash-4.3$ printf "%d %d %d\n" "$a" "$b" "$c"
bash: printf: 3 4: invalid number
1 2 3

正是因为$c变量被加了引号,所以现在可以将其识别为一个完整的字符串3 4,并且它不适合%d格式,后者只是一个整数

现在做同样的事情而不引用:

bash-4.3$ printf "%d %d %d\n" $a $b $c
1 2 3
4 0 0

printf 再次说:“好,那里有6个项目,但格式仅显示3个项目,因此我将保留合适的内容,并在无法与用户实际输入匹配的地方将其留空”。

在所有这些情况下,您都不必相信我。只需运行strace -e trace=execve并亲自查看命令实际上是“看到”了什么:

bash-4.3$ strace -e trace=execve printf "%d %d %d\n" $a $b $c
execve("/usr/bin/printf", ["printf", "%d %d %d\\n", "1", "2", "3", "4"], [/* 80 vars */]) = 0
1 2 3
4 0 0
+++ exited with 0 +++

bash-4.3$ strace -e trace=execve printf "%d %d %d\n" "$a" "$b" "$c"
execve("/usr/bin/printf", ["printf", "%d %d %d\\n", "1", "2", "3 4"], [/* 80 vars */]) = 0
1 2 printf: 3 4’: value not completely converted
3
+++ exited with 1 +++

补充说明

正如Charles Duffy在注释中正确指出的那样,bash它具有自己的内置printf命令(strace实际上是您在命令中使用的内置命令),而/usr/bin/printf不是shell的版本。除了细微的差异外,出于我们对这个特定问题的兴趣,标准格式说明符相同,行为相同。

还应该记住的是,printf语法比更具可移植性(因此是首选)echo,更不用说该语法对C或其中具有printf()功能的任何类似C的语言更熟悉。请参阅terdon关于printfvs的出色回答echo。虽然可以在特定版本的Ubuntu上针对特定shell量身定制输出,但是如果要在不同系统之间移植脚本,则可能更希望使用printfecho而不是echo。也许您是使用Ubuntu和CentOS机器的初学者系统管理员,或者甚至是FreeBSD(知道),因此在这种情况下,您将不得不做出选择。

从bash手册引用,SHELL BUILTIN COMMANDS部分

读取[-ers] [-a名称] [-d delim] [-i文本] [-n nchars] [-N nchars] [-p提示] [-t超时] [-u fd] [名称... ]

从标准输入或作为-u选项的参数提供的文件描述符fd中读取一行,然后将第一个单词分配给名字,将第二个单词分配给第二个名字,依此类推,其余单词及其中间的分隔符(分配给姓氏)。如果从输入流中读取的单词少于名称,则为其余名称分配空值。IFS中的字符用于使用外壳程序用于扩展的相同规则将行拆分为单词(在“单词拆分”中已进行了介绍)。


2
这种strace情况与另一种情况之间的一个值得注意的区别strace printf是使用/usr/bin/printf,而printf直接在bash中使用的是使用同名内置的shell。它们并不总是相同的-例如,bash实例具有格式说明符,%q并且在新版本中$()T具有时间格式。
查尔斯·达菲

7

这仅是一个建议,并不意味着完全取代Sergiy的答案。我认为塞尔吉(Sergiy)就他们为什么在印刷上有所不同写了一个很好的答案。如何将读取的变量分配给剩余的$c变量,如3 4 5 6后所述12并分配给abecho不会为您printf将变量拆分为%ds。

但是,您可以通过在命令开始时处理数字的回波,使它们基本上给您相同的答案:

/bin/bash您可以使用:

echo -e "1 2 3 \n4 5 6"

/bin/sh您可以使用:

echo "1 2 3 \n4 5 6"

Bash使用-e启用\转义字符,其中sh不需要它,因为它已经启用。 \n导致它创建新行,因此现在将echo行拆分为两条单独的行,这些行现在可以用于echo循环语句两次:

:~$ echo -e "1 2 3 \n4 5 6" | while read a b c; do echo "$a, $b, $c"; done
1, 2, 3
4, 5, 6

使用printf命令依次产生相同的输出:

:~$ echo -e "1 2 3 \n4 5 6" | while read a b c ;do printf "%d, %d, %d \n" $a $b $c; done
1, 2, 3 
4, 5, 6 

sh

$ echo "1 2 3 \n4 5 6" | while read a b c; do echo "$a, $b, $c"; done
1, 2, 3
4, 5, 6
$ echo "1 2 3 \n4 5 6" | while read a b c ;do printf "%d, %d, %d \n" $a $b $c; done
1, 2, 3 
4, 5, 6 

希望这可以帮助!


echo -e不仅是POSIX的扩展- 鉴于输入实际上违反了/违反了黑体字规范,因此任何echo-e在其输出上发出的输出。(bash默认情况下是不兼容的,但是在同时设置posixxpg_echo标志时符合标准;可以在编译时将后者设置为默认值,这意味着并非bash的所有版本都可以echo -e按照您的期望使用。)
查尔斯·达菲

参见POSIX规范的应用信息和理由章节echopubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html,明确地描述了echo的行为是在任一的存在未指定的-n或反斜杠在输入的任意位置,告知该printf代替使用。
查尔斯·达菲

@CharlesDuffy我曾经在我的代码中写过我希望它可以在所有bash版本中工作吗?您对我的回答有什么问题?如果您觉得自己有更好的解决方案,请将其写为答案,而不要试图证明别人做错了。我已经和您这样的人在一起过很多次,所以请停止这样的评论。这就是我要说的。
Terrance

3
在Ask Ubuntu中的@CharlesDuffy中,我们有时会写出不可移植到其他Linux发行版的答案。由于您的代表这里只有103分,您可能没有注意到这一点。但是,您在Stack Overflow上的代表为123.3K点,因此,您显然可以大致了解* NIX系统的大量知识。我认为您,Terrace和Serg都不错,但它们从不同角度看待线程。我回应(没有双关语)的情绪,希望为您写一个* NIX可移植的答案,或者至少在Linux发行版中是可移植的。
WinEunuuchs2Unix

2
@CharlesDuffy虽然我同意您的观点,但答案应该是可移植的,毕竟这是面向Ubuntu的网站,因此用户应使用Ubuntu专用的工具。因此,该警告有些隐含。我们不要进行冗长的讨论。没错,应该考虑其他外壳,也应该考虑新手阅读以从答案中学习。简短的评论可能足以说明这一点。
Sergiy Kolodyazhnyy
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.