Ser*_*nyy 239

这里的文件

<<被称为here-document结构。您让程序知道结束文本是什么,每当看到该分隔符时,程序将读取您提供给程序的所有内容作为输入并对其执行任务。

这就是我的意思:

$ wc << EOF
> one two three
> four five
> EOF
 2  5 24
Run Code Online (Sandbox Code Playgroud)

在这个例子中,我们告诉wc程序等待EOF字符串,然后输入五个单词,然后输入EOF表示我们已经完成输入。实际上,它类似于自己运行wc,输入单词,然后按CtrlD

在 bash 中,这些是通过临时文件实现的,通常以 形式/tmp/sh-thd.<random string>,而在 dash 中,它们是作为匿名管道实现的。这可以通过使用strace命令跟踪系统调用来观察。替换bashsh以查看如何/bin/sh执行此重定向。

$ strace -e open,dup2,pipe,write -f bash -c 'cat <<EOF
> test
> EOF'
Run Code Online (Sandbox Code Playgroud)

这里字符串

<<<被称为here-string。您无需输入文本,而是将预制的文本字符串提供给程序。例如,bc我们可以使用这样的程序bc <<< 5*4来获取特定情况下的输出,而无需交互运行 bc。把它想象成等价于echo '5*4' | bc.

bash 中的here-strings 是通过临时文件实现的,通常是 格式/tmp/sh-thd.<random string>,后来被取消链接,从而使它们暂时占用一些内存空间而不显示在/tmp目录条目列表中,并且有效地作为匿名文件存在,这些文件可能仍然存在由 shell 本身通过文件描述符引用,该文件描述符由命令继承,然后通过dup2()函数复制到文件描述符 0 (stdin) 。这可以通过观察

$ ls -l /proc/self/fd/ <<< "TEST"
total 0
lr-x------ 1 user1 user1 64 Aug 20 13:43 0 -> /tmp/sh-thd.761Lj9 (deleted)
lrwx------ 1 user1 user1 64 Aug 20 13:43 1 -> /dev/pts/4
lrwx------ 1 user1 user1 64 Aug 20 13:43 2 -> /dev/pts/4
lr-x------ 1 user1 user1 64 Aug 20 13:43 3 -> /proc/10068/fd
Run Code Online (Sandbox Code Playgroud)

并通过跟踪系统调用(为了可读性而缩短输出;注意临时文件是如何作为 fd 3 打开的,数据写入其中,然后使用O_RDONLYfd 4 的标志重新打开它,然后取消链接,然后dup2()到 fd 0,该文件由cat后来的继承):

$ strace -f -e open,read,write,dup2,unlink,execve bash -c 'cat <<< "TEST"'
execve("/bin/bash", ["bash", "-c", "cat <<< \"TEST\""], [/* 47 vars */]) = 0
...
strace: Process 10229 attached
[pid 10229] open("/tmp/sh-thd.uhpSrD", O_RDWR|O_CREAT|O_EXCL, 0600) = 3
[pid 10229] write(3, "TEST", 4)         = 4
[pid 10229] write(3, "\n", 1)           = 1
[pid 10229] open("/tmp/sh-thd.uhpSrD", O_RDONLY) = 4
[pid 10229] unlink("/tmp/sh-thd.uhpSrD") = 0
[pid 10229] dup2(4, 0)                  = 0
[pid 10229] execve("/bin/cat", ["cat"], [/* 47 vars */]) = 0
...
[pid 10229] read(0, "TEST\n", 131072)   = 5
[pid 10229] write(1, "TEST\n", 5TEST
)       = 5
[pid 10229] read(0, "", 131072)         = 0
[pid 10229] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=10229, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++
Run Code Online (Sandbox Code Playgroud)

意见:可能是因为此处的字符串使用临时文本文件,这可能是此处字符串始终插入尾随换行符的可能原因,因为POSIX 定义的文本文件必须具有以换行符结尾的行。

过程替换

正如tldp.org解释的那样,

进程替换将一个(或多个)进程的输出提供给另一个进程的标准输入。

所以实际上这类似于将一个命令的标准输出管道传输到另一个命令,例如echo foobar barfoo | wc. 但请注意:在bash 联机帮助页中,您会看到它表示为<(list). 所以基本上你可以重定向多个(!)命令的输出。

注意:从技术上讲,当您说< <您不是指一件事时,而是指两个重定向 <以及来自<( . . .).

现在如果我们只进行过程替换会发生什么?

$ echo <(echo bar)
/dev/fd/63
Run Code Online (Sandbox Code Playgroud)

如您所见,shell/dev/fd/63在输出的位置创建临时文件描述符(根据Gilles 的回答,这是一个匿名管道)。这意味着 <将该文件描述符重定向为命令的输入。

所以非常简单的例子是将两个 echo 命令的输出过程替换为 wc:

$ wc < <(echo bar;echo foo)
      2       2       8
Run Code Online (Sandbox Code Playgroud)

所以在这里我们让 shell 为括号中发生的所有输出创建一个文件描述符,并将其作为输入重定向到wc.正如预期的那样, wc 从两个 echo 命令接收该流,它本身会输出两行,每行都有一个单词,适当地,我们有 2 个单词、2 行和 6 个字符加上两个换行符。

流程替换是如何实现的?我们可以使用下面的跟踪找到(为简洁起见缩短了输出)

$ strace -e clone,execve,pipe,dup2 -f bash -c 'cat <(/bin/true) <(/bin/false) <(/bin/echo)'
execve("/bin/bash", ["bash", "-c", "cat <(/bin/true) <(/bin/false) <"...], 0x7ffcb96004f8 /* 50 vars */) = 0
pipe([3, 4])                            = 0
dup2(3, 63)                             = 63
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f6c12569a10) = 8954
strace: Process 8954 attached
[pid  8953] pipe([3, 4])                = 0
[pid  8953] dup2(3, 62)                 = 62
[pid  8953] clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f6c12569a10) = 8955
strace: Process 8955 attached
[pid  8953] pipe([3, 4])                = 0
[pid  8953] dup2(3, 61)                 = 61
[pid  8953] clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f6c12569a10) = 8956
[pid  8953] execve("/bin/cat", ["cat", "/dev/fd/63", "/dev/fd/62", "/dev/fd/61"], 0x55ab7566e8f0 /* 50 vars */) = 0
Run Code Online (Sandbox Code Playgroud)

上面在 Ubuntu 上的跟踪(通常也暗示在 Linux 上)表明进程替换是通过重复分叉多个子进程来实现的(因此进程 8953 分叉多个子进程 8954、8955、8956 等)。然后所有这些子进程通过它们的 stdout 进行通信,但它被复制(即复制)到从 63 向下开始的下一个可用文件描述符的堆栈上。为什么从 63 开始?对于开发人员来说,这可能是一个很好的问题。众所周知,当流被重定向时,bash可以使用 fd 255来保存“主”命令/管道的文件描述符。

旁注进程替换可能被称为bashism(一种可在高级 shell 中使用的命令或结构,如bash,但未由 POSIX 指定),但它是在kshbash 存在之前作为ksh 手册页实现的这个答案建议。像贝壳tcshmksh但没有进程替换。那么我们如何在没有进程替换的情况下将多个命令的输出重定向到另一个命令呢?分组加管道!

$ (echo foo;echo bar) | wc
      2       2       8
Run Code Online (Sandbox Code Playgroud)

实际上,这与上面的示例相同,但是,这与进程替换在本质上是不同的,因为我们将整个子外壳的标准输出和标准输入wc 与管道连接起来。另一方面,进程替换使命令读取临时文件描述符。

那么如果我们可以用管道进行分组,为什么我们需要过程替换?因为有时我们不能使用管道。考虑下面的例子 - 比较两个命令的输出diff(需要两个文件,在这种情况下我们给它两个文件描述符)

diff <(ls /bin) <(ls /usr/bin)
Run Code Online (Sandbox Code Playgroud)

  • `&lt; &lt;` 用于从 _process 替换_获取标准输入。这样的命令可能看起来像:`cmd1 &lt; &lt;(cmd2)`。例如,`wc &lt; &lt;(日期)` (7认同)
  • 是的,[bash、zsh 和 AT&amp;T ksh{88,93} 支持进程替换,但 pdksh/mksh 不支持。](http://mywiki.wooledge.org/ProcessSubstitution) (4认同)
  • 另一个不能使用管道的例子:`echo 'foo' | 读; echo ${REPLY}` 将_不_返回 `foo`,因为 `read` 是在一个子 shell 中启动的——管道启动一个子 shell。但是,`read &lt; &lt;(echo 'foo'); echo ${REPLY}` 正确返回 `foo`,因为没有子 shell。 (4认同)
  • `&lt; &lt;` * 本身不是一个东西*,在进程替换的情况下,它只是一个 `&lt;` 后面跟着其他的东西,恰好以 `&lt;` 开头 (2认同)

mur*_*uru 37

< < 是语法错误:

$ cat < <
bash: syntax error near unexpected token `<'
Run Code Online (Sandbox Code Playgroud)

< <()进程替换<())结合重定向(<):

一个人为的例子:

$ wc -l < <(grep ntfs /etc/fstab)
4
$ wc -l <(grep ntfs /etc/fstab)
4 /dev/fd/63
Run Code Online (Sandbox Code Playgroud)

通过进程替换,文件描述符的路径就像文件名一样使用。如果您不想(或不能)直接使用文件名,则可以将进程替换与重定向结合起来。

需要明确的是,没有< <运算符。

  • @solfish `&lt;()` 给出了一个类似文件名的东西,所以它更普遍有用 - `&lt; &lt;()` 正在替换可能不需要的标准输入。在 `wc` 中,后者恰好更有用。在其他地方可能不太有用 (2认同)

jll*_*gre 18

< <是语法错误,您可能的意思是command1 < <( command2 )这是一个简单的输入重定向,然后是进程替换,非常相似但不等同于:

command2 | command1
Run Code Online (Sandbox Code Playgroud)

假设您正在运行的区别bashcommand1在第二种情况下在子外壳中运行,而在第一种情况下在当前外壳中运行。这意味着设置的变量command1不会随着流程替换变体而丢失。


sno*_*oop 14

< <会出现语法错误。正确使用方法如下:

借助示例进行解释:

示例< <()

while read line;do
   echo $line
done< <(ls)
Run Code Online (Sandbox Code Playgroud)

在上面的例子中,while 循环的输入将来自ls可以逐行读取并echo在循环中 ed的命令。

<()用于进程替换。<()可以在此链接中找到更多信息和示例:

进程替换和管道