shell 为什么< exist重定向到stdin

0lvr5msh  于 2023-06-06  发布在  Shell
关注(0)|答案(2)|浏览(155)

提问

当管道存在时,为什么shell要实现<<<< <(command)< /dev/fd/*等替代方法来将某些内容重定向到stdin

示例

|方式(经典管道)

echo 'text' | sed 's/x/y/'
# or
cat - | sed 's/x/y/' # type text afterwards

<<<方式

sed 's/x/y/' <<< 'text'

< <(command)方式

sed 's/x/y/' < <(echo 'text')
# while <(command) becomes a file descriptor like
sed 's/x/y/' < /dev/fd/42

它们都返回teyt

bf1o4zei

bf1o4zei1#

除了program <file之外的所有东西都不太适合program <file处理的用例。

讨论的其他方法都做了额外的工作,这使得它们的设置速度较慢或运行速度较慢。<是所描述的机制中唯一一种将stdin直接连接到预先存在的文件并避免额外程序、FIFO和其他移动部件的机制。

案例一:program <file

在这里,您将program的stdin直接连接到file
shell执行以下步骤:

  • 派生一个子进程,在其中运行program
  • 打开file进行读取。
  • 使用fdup2()将文件描述符重命名为FD 0。
  • 关闭原始的预重命名描述符。
  • 使用execve()实际启动program

案例二:cat file | program

在这里,您正在运行 * 两个 * 程序:/bin/catprogram;第二个的输入端连接到第一个的输出端。
这意味着program无法看到原始文件名是什么;它不能检查文件的大小;它不能乱序读取或并行化--它只能看到由cat写入内容。
shell执行以下步骤:

  • 使用mkfifo()设置管道
  • fork()关闭子shell以运行/bin/cat
  • 在该子shell中,使用dup2()将该FIFO的写入端连接到stdout。
  • 在该子 shell 中,关闭FIFO的读取端。
  • 在该子shell中,调用execve("/bin/cat", "cat", "file")
  • fork()关闭子shell以运行program
  • 在该子 shell 中,关闭FIFO的写入端
  • 在该子shell中,使用dup2()将该FIFO的读取端连接到stdin。
  • 在该子shell中,使用execve()将shell替换为program

每当program从输入读取时,它不是从file阅读;相反,它从/bin/cat的stdout阅读,这必须执行额外的读写对。
这是一个简单的例子在真实的世界中,shell还需要将管道本身与父进程分离。

案例三:program <<<content

这与program <file不同,因为它根本不接受文件名;它只获取特定的内容,shell将其写入一个 new 文件。
在这里,shell做了以下事情(除了在bash的新版本中,它做了一些更接近于第二种情况的事情,但在左侧没有exec):

  • 派生一个运行program的子shell
  • 创建一个临时文件,就像mktemp()一样,打开它进行写操作。
  • 将字符串content写入该临时文件。
  • 关闭该文件的写句柄。
  • 打开同一文件的只读文件描述符。
  • 删除临时文件的目录项(直到文件上没有更多的句柄时,文件才真正被删除;这意味着当你运行的程序退出时它会被自动删除)。
  • 使用fdup2()将该描述符重命名为文件描述符0。
  • 使用execve()实际启动program,使用子shell。

就性能而言,这比echo content | file(通常)更好,但它也使您需要编写content的新副本,而在<file的情况下,您使用的内容 * 已经写入到预先存在的文件 *。

案例四:program < <(cat file)

这只是案例2;你仍然在创建一个FIFO,启动一个单独的/bin/cat,等等;当program是一个shell函数、循环或类似的东西时,它有一些优势(因为它在现有的shell中运行代码,所以对变量的更改是完整的),但是当program实际上是一个外部可执行文件时,它几乎没有实际的区别。

yks3o0rb

yks3o0rb2#

cmd1 | cmd2通常在子shell中运行cmd1cmd2。因此,参数赋值等副作用将丢失:

$ var=a
$ echo $var
a
$ echo b | read var
$ echo $var
a
$ read var <<<c
$ echo $var
c
$ i=0
$ echo $i
$ seq 2 | while read f; do i=$((i+1)); done
$ echo $i
0
$ while read f; do i=$((i+1)); done < <(seq 2)
$ echo $i
2
$ j=0
$ echo $j
0
$ echo $((++j)) | cat
1
$ echo $j
0
$ echo $((++j))
1
$ echo $j
1

相关问题