针对只有一行的文件,"$(cat file)"、"$(<file)" 和 "read ... < file" 有什么区别?

11

我有一个包含一行文本的输入文件:

$ cat input
foo bar

我想在我的脚本中使用这行代码,目前我知道有三种获取它的方法:


line=$(cat input)
line=$(<input)
IFS= read -r line < input
例如,使用命令替换意味着我会生成一个子shell,而使用read则不会,是这样吗?还有哪些区别,哪种方式更受欢迎?我还注意到(使用strace),由于某种原因只有read触发系统调用openat。其他方法为什么不会触发?
例如,使用命令替换意味着我会生成一个子shell,而使用read则不会,是这样吗?还有哪些区别,哪种方式更受欢迎?我还注意到(使用strace),由于某种原因只有read触发系统调用openat。其他方法为什么不会触发?
$ strace ./script |& grep input
read(3, "#!/usr/bin/env bash\n\ncat > input"..., 80) = 80
read(255, "#!/usr/bin/env bash\n\ncat > input"..., 167) = 167
read(255, "\nline=$(cat input)\nline=$(<input"..., 167) = 60
read(255, "line=$(<input)\nIFS= read -r line"..., 167) = 41
read(255, "IFS= read -r line < input\n", 167) = 26
openat(AT_FDCWD, "input", O_RDONLY)     = 3

1
由于某种原因,只有read会触发系统调用openat。其他命令为何不会呢?正如您所知,前两个命令在子shell中读取文件。请确保要求strace跟踪由您跟踪的初始进程生成的子进程。 - axiac
1个回答

18
  • line=$(cat input) 是读取整个文件的 POSIX 方法。它需要一个 fork 操作。

  • line=$(< input) 是稍微更有效率的 Bashism,用于读取整个文件。它也会执行 fork,但不需要 execve 操作。

  • 虽然没有提到,但 mapfile/readarray 是将整个文件逐行读入数组的显著更有效率的 Bashisms。它们并不需要 fork 操作。

  • IFS= read -r line < input 是在没有子 shell 的情况下读取单行的 POSIX 方法。它不需要 fork 操作。

你只看到最后一个例子打开了文件,原因是其他方法在子 shell 中进行,如果没有指定 -f 来跟踪子进程,你就无法看到这些操作。


3
呵呵,我一直误以为“$(<...)”都是在进程内部执行的,但你说得对,它确实会派生出新进程。不过,未来的Bash版本可以不这样做,从而提供更高效的实现方式--因此使用POSIX语法不仅锁定了exec还锁定了fork,而$(<...)可以在未来改进而无需进行这两个操作。 - Charles Duffy

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