Bash管道与Here-String的区别

6

我以为在Bash中这些命令是等效的,但它们产生了不同的输出。你能帮我理解为什么吗?

$ echo "SEBA" | wc
      1       1       5

$ wc <<< "SEBA"
1 1 5

运行环境:

  • Ubuntu 20.04.2 LTS
  • GNU bash,版本 5.0.17(1)-release (x86_64-pc-linux-gnu)
  • wc(GNU coreutils)8.30

以下是一些测试:

$ echo "SEBA" | wc | hexdump 
0000000 2020 2020 2020 2031 2020 2020 2020 2031
0000010 2020 2020 2020 0a35                    
0000018

$ wc <<< "SEBA" | hexdump 
0000000 2031 2031 0a35                         
0000006

$ echo "SEBA" | hexdump 
0000000 4553 4142 000a                         
0000005

$ hexdump <<< "SEBA"
0000000 4553 4142 000a                         
0000005

你使用的是哪个 shell?在我的系统上,两者都使用 bash 5.1 并产生相同的输出。 - larsks
我无法从herestring重现该输出。使用的是哪个版本的bash? - Jetchisel
它们并不相同,但我很难解释为什么这种差异会影响wc输出。(这是特定于Linux coreutils版本的wc;我无法在macOS上使用BSD版本重现这种差异。) - chepner
我注意到在macOS上,wc的输出实际上使用了" %7d %7d %7d\n"作为格式 - 数字以8个字符宽的字段打印(直到您处理大文件)。 - Jonathan Leffler
只是确认一下:我可以重现这个问题(CentOS7,bash 4.2.46(2),wc 8.22)。 - psmears
显示剩余4条评论
1个回答

10
当 GNU wc 从文件获取所有输入时,它使用 stat()(对于 stdin 使用 fstat())获取所有文件的字符大小。由此可以确定每个输出字段所需的最大数字数量,并仅使用这么多位数。
当输入中有任何管道时,无法提前确定其大小。默认为该输入使用7个数字。
Here-strings 是通过将字符串复制到临时文件并将 stdin 重定向到该文件来实现的,因此此情况可以使用优化的字段大小。但是,从 echo 管道传输不允许此操作,因此会得到7位数字的字段。
请参见 GNU coreutils 源代码中的函数 get_input_fstatuscompute_number_width
如评论中所述,bash 5.1 对于小的 here-strings 或 here-documents 不使用临时文件,而使用管道。 "小" 可能不是非常小,而是管道缓冲区大小。 正如在 How big is the pipe buffer? 中解释的那样,在 Mac OS X 上默认为16K,在 Linux 上为64K。因此,您不应该在不同的 bash 版本之间移植这种行为。

哪里有问题?它打印的信息相同,唯一的区别在于值之间的空格数,这不是规格的一部分。 - Barmar
它不等待管道完全读取的原因是因为可能有很多个管道。它必须保存所有结果,直到处理完每一个输入。 - Barmar
请记住,一些输入可能是命名管道或进程替换,因此不仅仅是一个管道。 - Barmar
@Barmar - 关于*Here-strings are implemented by copying the string to a temporary file[...]*:从bash 5.1开始,只要它们足够小,here-strings就不会创建临时文件 - Abdull
@Abdull 很好的发现,我已经更新了答案。 - Barmar
显示剩余2条评论

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