use*_*631 14 command-line bash echo
根据这个问题“在 linux 脚本中使用“while read... ”
echo '1 2 3 4 5 6' | while read a b c;do echo "$a, $b, $c"; done
Run Code Online (Sandbox Code Playgroud)
结果:
1, 2, 3 4 5 6
Run Code Online (Sandbox Code Playgroud)
但是当我替换echo为printf
echo '1 2 3 4 5 6' | while read a b c ;do printf "%d, %d, %d \n" $a $b $c; done
Run Code Online (Sandbox Code Playgroud)
结果
1, 2, 3
4, 5, 6
Run Code Online (Sandbox Code Playgroud)
有人能告诉我是什么让这两个命令不同吗?谢谢~
Ser*_*nyy 24
首先,让我们了解read a b cpart会发生什么。read将根据IFS变量的默认值(空格-制表符-换行符)执行分词,并基于此拟合所有内容。如果输入比保存它的变量多,它会将拆分的部分拟合到第一个变量中,不能拟合的部分将放在最后。这就是我的意思:
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
Run Code Online (Sandbox Code Playgroud)
这正是它在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)。这听起来可能令人困惑,但存在的原因是改进了 C 语言中真实 printf()函数所做的行为。
您在此处所做的也会影响输出的是您的变量没有被引用,这允许 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
Run Code Online (Sandbox Code Playgroud)
正是因为$c变量被引用,它现在被识别为一个完整的字符串,3 4,并且它不符合%d格式,它只是一个整数
现在做同样的事情而不引用:
bash-4.3$ printf "%d %d %d\n" $a $b $c
1 2 3
4 0 0
Run Code Online (Sandbox Code Playgroud)
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 +++
Run Code Online (Sandbox Code Playgroud)
正如 Charles Duffy 在评论中正确指出的那样,bash它有自己的内置printf,这是您在命令中使用的,strace实际上会调用/usr/bin/printfversion,而不是 shell 的 version。除了细微的差异之外,对于我们对这个特定问题的兴趣,标准格式说明符是相同的,行为也是相同的。
还应该记住的是,printf语法比 更可移植(因此更受欢迎)echo,更不用说语法对 C 或任何具有printf()函数的类 C 语言更熟悉。请参阅terdon关于printfvs 的出色答案echo。虽然您可以在您的特定版本的 Ubuntu 上根据您的特定 shell 定制输出,但如果您要跨不同系统移植脚本,您可能应该更喜欢printf而不是 echo。也许你是一个使用 Ubuntu 和 CentOS 机器的初学者系统管理员,或者甚至是 FreeBSD - 谁知道 - 所以在这种情况下你将不得不做出选择。
读取 [-ers] [-a aname] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ... ]
从标准输入中读取一行,或者从作为 -u 选项的参数提供的文件描述符 fd 中读取,第一个单词分配给第一个名称,第二个单词分配给第二个名称,依此类推,剩下的单词和它们之间的分隔?分配给姓氏的 tors。如果从输入流中读取的单词少于名称,则剩余的名称将被分配空值。IFS 中的字符用于将行拆分为单词,使用的规则与 shell 用于扩展的规则相同(在上面的单词拆分中描述)。
这只是一个建议,根本不打算取代 Sergiy 的答案。我认为 Sergiy 写了一个很好的答案,解释了为什么它们在印刷上不同。读取中的变量如何与剩余的$c变量一起分配到变量中3 4 5 6,1然后2分配给a和b。 echo不会printf用%ds为你拆分变量。
但是,您可以通过操纵命令开头的数字的回声,使它们基本上给您相同的答案:
在/bin/bash你可以使用:
echo -e "1 2 3 \n4 5 6"
Run Code Online (Sandbox Code Playgroud)
在/bin/sh你可以只使用:
echo "1 2 3 \n4 5 6"
Run Code Online (Sandbox Code Playgroud)
Bash 使用 -e 来启用 \ 转义字符,其中 sh 不需要它,因为它已经启用。 \n导致它创建一个新行,所以现在回声行被分成两个单独的行,现在可以为您的回声循环语句使用两次:
:~$ 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
Run Code Online (Sandbox Code Playgroud)
使用以下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
Run Code Online (Sandbox Code Playgroud)
在 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
Run Code Online (Sandbox Code Playgroud)
希望这可以帮助!