UP | HOME

<< & <<< & < <()

目录

<<(Here document)

<< 被称为 here-document 结构。你让程序知道什么将会结束文本,并且无论何时见到该分隔符,程序就会读取你提供给程序的所有内容作为输入并在其上执行任务。

这里有一个例子:

    $ wc << EOF
    > one two three
    > four five
    > EOF
     2  5 24

在该例中我们告诉 wc 程序等待 EOF 字符串,然后输入五个单词,并之后输入 EOF 来通知我们已经完成了输入。实际上,这类似于单独运行 wc ,输入单词,然后按 Ctrl-D

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

    $ strace -e open,dup2,pipe,write -f bash -c 'cat <<EOF
    > test
    > EOF'

<<<(Here string)

<<< 被称为 here-string 。代替输入文本,你将预制的文本字符串提供给程序。例如,对于诸如 bc 之类的程序,我们可以执行 bc <<< 5*4 来获取此特定情况下的输出,而不需要交互运行 bc。

here-string 在 bash 中通过临时文件实现,通常是 /tmp/sh-thd.<random string> 的形式,之后被 unlink,所以会使它们临时占用一些内存空间,但又不会出现在 /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

并通过追踪系统调用(缩短输出以提高可读性;注意临时文件是如何被作为 fd 3 打开的,写入数据,然后使用=O_RDONLY=flag 作为 fd 4 重新打开并之后被 unlink,然后=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 +++

观点:可能因为 here-string 使用了临时文件,这是为什么 here-string 总是插入尾随换行符的可能原因,因为由 POSIX 定义的文本文件必须具有以换行符结尾的行。

< <()(Process Substitution)

如 tldp.org 的解释:

进程替换一个(或多个)进程的输出馈送到另一个进程的标准输入中。

所以这类似于将一个命令的 stdout 传递给另一个命令,例如 echo foobar barfoo | wc 。但是注意:再 bash manpage 中你会看到它表示为 <(list) 。所以基本上你可以重定向多个(!)命令的输出。

注意:从技术上讲,当你说 < < 时并不是指一件事,而是两个重定向,一个是单一的 < 以及一个 <( . . . ) 输出的进程重定向。

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

    $ echo <(echo bar)
    /dev/fd/63

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

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

    $ wc < <(echo bar;echo foo)
          2       2       8

所以在这里我们让 shell 为括号中出现的所有输出创建文件描述符,并将其重定向为 wc 的输入。如期待的一样,wc 从两个 echo 命令接收该 stream,它们本身输出两行,每行有一个单词,并适当地我们有 2 个单词,2行和 6 个字符加两个换行符。

    $ (echo foo;echo bar) | wc
          2       2       8

实际上,着和上面的例子相同。然而,这与进程替换在表面之下是不同的,因为我们将 subshell 的整个标准输出与=wc=的标准输入使用管道链接在了一起。在另一方面,进程替换使命令读取一个临时文件描述符。

所以如果我们可以使用管道进行分组,为什么我们需要进程替换?因为有些时候我们不能使用管道。考虑以下示例


使用 diff 比较两个命令的输出(其需要两个文件,在这种情况下,我们为它提供两个文件描述符)

diff <(ls /bin) <(ls /usr/bin)

Link

作者: Petrus.Z

Created: 2021-09-01 Wed 00:39