问题描述
bash中<<
,<<<
和< <
有什么区别?
最佳解决方案
这里的文件
<<
被称为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>
形式,而在破折号中,它们是作为匿名管道实现的。这可以通过使用strace
命令跟踪系统调用来观察。将bash
替换为sh
以查看/bin/sh
如何执行此重定向。
$ strace -e open,dup2,pipe,write -f bash -c 'cat <<EOF
> test
> EOF'
这里字符串
<<<
被称为here-string
。您可以向程序提供pre-made文本字符串,而不是键入文本。例如,使用bc
这样的程序,我们可以执行bc <<< 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
并通过跟踪系统调用(输出缩短为可读性;注意临时文件如何打开为fd 3,写入数据,然后是re-opened,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 +++
意见:可能是因为这里的字符串使用了临时文本文件,这是here-strings总是插入尾部换行符的可能原因,因为POSIX definition的文本文件必须包含以换行符结尾的行。
流程替代
正如tldp.org所解释的那样,
Process substitution feeds the output of a process (or processes) into the stdin of another process.
因此实际上这类似于将一个命令的管道stdout传递给另一个命令,例如echo foobar barfoo | wc
。但请注意:在bash manpage中,您将看到它被表示为<(list)
。所以基本上你可以重定向多个(!)命令的输出。
注意:从技术上讲,当您说< <
时,您不是指一件事,而是使用单个<
进行两次重定向并处理来自<( . . .)
的输出重定向。
现在如果我们只是处理替换会发生什么?
$ echo <(echo bar)
/dev/fd/63
如您所见,shell创建临时文件描述符/dev/fd/63
,其中输出(根据Gilles’s answer,是一个匿名管道)。这意味着<
将该文件描述符重定向为命令的输入。
因此,非常简单的示例是将两个echo命令的输出进程替换为wc:
$ wc < <(echo bar;echo foo)
2 2 8
所以这里我们让shell为括号中的所有输出创建一个文件描述符,并将其重定向为wc
的输入。正如所料,wc从两个echo命令接收该流,这两个echo本身将输出两行,每行有一个单词,适当地,我们有2个单词,2行,6个字符加上两个新行计数。
附注:过程替换可以称为基础(在高级shell中可用的命令或结构,如bash
,但未由POSIX指定),但它在bash存在之前在ksh
中实现,如ksh man page和this answer建议。然而,像tcsh
和mksh
这样的壳没有工艺替代。那么我们怎样才能在没有进程替换的情况下将多个命令的输出重定向到另一个命令呢?分组加管道!
$ (echo foo;echo bar) | wc
2 2 8
实际上这与上面的例子相同,但是,这与流程替换有所不同,因为我们制作了wc
linked with the pipe的整个子shell和stdin的stdout。另一方面,进程替换使命令读取临时文件描述符。
因此,如果我们可以对管道进行分组,为什么我们需要进程替换呢?因为有时候我们不能使用滚边。考虑下面的例子 – 比较两个命令的输出与diff
(需要两个文件,在这种情况下,我们给它两个文件描述符)
diff <(ls /bin) <(ls /usr/bin)
次佳解决方案
< <
是语法错误:
$ cat < <
bash: syntax error near unexpected token `<'
< <()
是process substitution(<()
)与重定向相结合(<
):
一个人为的例子:
$ wc -l < <(grep ntfs /etc/fstab)
4
$ wc -l <(grep ntfs /etc/fstab)
4 /dev/fd/63
使用进程替换,文件描述符的路径就像文件名一样使用。如果您不想(或不能)直接使用文件名,则将进程替换与重定向结合使用。
需要说明的是,没有< <
运算符。
第三种解决方案
< <
是一个语法错误,您可能意味着command1 < <( command2 )
这是一个简单的输入重定向,后跟一个进程替换,非常相似但不等同于:
command2 | command1
假设运行bash
的区别是command1
在第二种情况下在子shell中运行,而它在第一种情况下在当前shell中运行。这意味着command1
中设置的变量不会因过程替换变量而丢失。
第四种方案
< <
将给出语法错误。正确使用如下:
借助示例解释:
< <()
的示例:
while read line;do
echo $line
done< <(ls)
在上面的例子中,while循环的输入将来自ls
命令,该命令可以逐行读取,循环中编码为echo
。
<()
用于过程替换。有关<()
的更多信息和示例,请访问此链接: