![bash 中](https://linux22.com/image/984333/bash%20%E4%B8%AD%20.png)
bash 中<<
,<<<
和有什么区别?< <
答案1
这里的文件
<<
被称为here-document
结构。您让程序知道结束文本是什么,并且每当看到该分隔符时,程序就会读取您提供给程序的所有内容作为输入并对其执行任务。
我的意思是:
$ wc << EOF
> one two three
> four five
> EOF
2 5 24
在这个例子中,我们告诉wc
程序等待EOF
字符串,然后输入五个单词,然后输入EOF
以表示我们已完成输入。实际上,它类似于wc
自行运行,输入单词,然后按CtrlD
在 bash 中,这些是通过临时文件实现的,通常采用 形式/tmp/sh-thd.<random string>
,而在 dash 中,它们以匿名管道的形式实现。这可以通过使用strace
命令跟踪系统调用来观察。替换bash
为sh
以查看如何/bin/sh
执行此重定向。
$ strace -e open,dup2,pipe,write -f bash -c 'cat <<EOF
> test
> EOF'
这里字符串
<<<
称为here-string
。您无需输入文本,只需将预先制作好的文本字符串提供给程序即可。例如,使用这样的程序,bc
我们可以bc <<< 5*4
只获取特定情况的输出,而无需以交互方式运行 bc。可以将其视为 的等价物echo '5*4' | bc
。
bash 中的 here-strings 是通过临时文件实现的,通常格式为/tmp/sh-thd.<random string>
,这些文件稍后会被取消链接,因此它们会暂时占用一些内存空间,但不会显示在/tmp
目录条目列表中,并且实际上作为匿名文件存在,这些文件仍可通过文件描述符由 shell 本身引用,并且该文件描述符由命令继承,稍后通过函数复制到文件描述符 0(stdin)dup2()
。这可以通过以下方式观察到
$ 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
为 fd 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 +++
意见:可能是因为这里的字符串使用了临时文本文件,这可能是为什么这里的字符串总是插入一个尾随换行符,因为文本文件POSIX 定义必须有以换行符结尾的行。
流程替代
作为tldp.org解释道,
进程替换将一个进程(或多个进程)的输出馈送到另一个进程的标准输入中。
所以实际上这类似于管道标准输出一个命令到另一个命令,例如echo foobar barfoo | wc
。但请注意:在bash 手册页您将看到它被表示为<(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 命令接收该流,这些命令本身将输出两行,每行都有一个单词,并且我们适当地计算了 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
Ubuntu 上的上述跟踪(也暗示 Linux 上的情况)表明,进程替换是通过反复分叉多个子进程来实现的(因此进程 8953 分叉了多个子进程 8954、8955、8956 等)。然后,所有这些子进程都通过它们的 stdout 进行通信,但这些通信会被复制(即被拷贝)到从 63 开始的下一个可用文件描述符堆栈上。为什么从 63 开始?这可能是开发人员要问的一个好问题。众所周知,bash
可以使用 fd 255用于在“主”命令/管道的流被重定向时保存文件描述符。
边注:流程替代可以称为巴什主义(在高级 shell 中可用的命令或结构bash
,但未被 POSIX 指定),但它是在ksh
bash 出现之前实现的,如下所示ksh 手册页和这个答案tcsh
建议。然而,像和这样的Shellmksh
没有进程替换。那么我们如何才能在不进行进程替换的情况下将多个命令的输出重定向到另一个命令?分组加管道!
$ (echo foo;echo bar) | wc
2 2 8
实际上,这与上面的例子相同,但是,这与进程替换在底层有所不同,因为我们将整个子 shell 的标准输出和wc
与管道相连另一方面,进程替换使命令读取临时文件描述符。
那么如果我们可以使用管道进行分组,为什么还需要进程替换呢?因为有时我们不能使用管道。考虑下面的例子——比较两个命令的输出diff
(这需要两个文件,在这种情况下我们给它两个文件描述符)
diff <(ls /bin) <(ls /usr/bin)
答案2
< <
是语法错误:
$ cat < <
bash: syntax error near unexpected token `<'
< <()
是流程替代( <()
) 与重定向 ( <
) 结合:
一个人为的例子:
$ wc -l < <(grep ntfs /etc/fstab)
4
$ wc -l <(grep ntfs /etc/fstab)
4 /dev/fd/63
使用进程替换时,文件描述符的路径可以像文件名一样使用。如果您不想(或不能)直接使用文件名,可以将进程替换与重定向结合起来。
要明确的是,没有< <
操作员。
答案3
< <
是语法错误,您可能指的是command1 < <( command2 )
这是一个简单的输入重定向,后跟一个过程替换,并且非常相似但不等同于:
command2 | command1
bash
假设您正在运行的是command1
第二种情况下的子 shell 中的程序,而第一种情况下的当前 shell 中的程序,则存在差异。这意味着command1
使用进程替换变体时设置的变量不会丢失。
答案4
< <
会出现语法错误。正确使用方法如下:
举例解释:
例如< <()
:
while read line;do
echo $line
done< <(ls)
在上面的例子中,while 循环的输入将来自ls
可以逐行读取并echo
在循环中执行的命令。
<()
用于进程替换。更多信息和示例<()
可在此链接中找到: