不只是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 $c
printf时,看到格式字符串带有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关于printf
vs的出色回答echo
。虽然可以在特定版本的Ubuntu上针对特定shell量身定制输出,但是如果要在不同系统之间移植脚本,则可能更希望使用printf
echo而不是echo。也许您是使用Ubuntu和CentOS机器的初学者系统管理员,或者甚至是FreeBSD(知道),因此在这种情况下,您将不得不做出选择。
从bash手册引用,SHELL BUILTIN COMMANDS部分
读取[-ers] [-a名称] [-d delim] [-i文本] [-n nchars] [-N nchars] [-p提示] [-t超时] [-u fd] [名称... ]
从标准输入或作为-u选项的参数提供的文件描述符fd中读取一行,然后将第一个单词分配给名字,将第二个单词分配给第二个名字,依此类推,其余单词及其中间的分隔符(分配给姓氏)。如果从输入流中读取的单词少于名称,则为其余名称分配空值。IFS中的字符用于使用外壳程序用于扩展的相同规则将行拆分为单词(在“单词拆分”中已进行了介绍)。
strace
情况与另一种情况之间的一个值得注意的区别strace printf
是使用/usr/bin/printf
,而printf
直接在bash中使用的是使用同名内置的shell。它们并不总是相同的-例如,bash实例具有格式说明符,%q
并且在新版本中$()T
具有时间格式。