bash中的<<<<<< <有什么区别?

评论

至少在这些以Google为中心的时代,很难搜索这些基于符号的运算符。是否有一个搜索引擎,您可以在其中插入“ << <<< <<”并获得有用的信息?

@DanielGriscom有SymbolHound。

@DanielGriscom Stack Exchange曾经支持搜索符号,但是后来出现了问题,没有人修复它。

它已经存在(已经使用了将近一年):Shell的控制和重定向运算符是什么?

#1 楼

在这里,文档
<<被称为here-document结构。您让程序知道结束文本是什么,每当看到分隔符时,程序就会读取您提供给程序的所有内容作为输入并在其上执行任务。 :
$ wc << EOF
> one two three
> four five
> EOF
 2  5 24

在此示例中,我们告诉wc程序等待EOF字符串,然后键入5个字,然后键入EOF表示已完成输入。实际上,它类似于单独运行wc,键入文字,然后按CtrlD
。在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。您无需输入文本,而是将预制的文本字符串提供给程序。例如,使用bc之类的程序,我们可以执行bc <<< 5*4来仅获取特定情况下的输出,而无需交互运行bc。
bash中的字符串是通过临时文件实现的,通常格式为/tmp/sh-thd.<random string>,后来取消链接,因此使它们暂时占据了一些内存空间,但没有出现在/tmp目录条目的列表中,并有效地以匿名文件的形式存在,仍然可以通过外壳本身通过文件描述符进行引用,并且该文件描述符被继承由命令执行,然后通过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标志作为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来保存“ main”命令/管道的文件描述符。
侧面注意:进程替代可以称为bashism(命令或可在高级外壳程序(如bash中使用,但未由POSIX指定)的结构,但已在bash作为ksh手册页存在之前在ksh中实现,并且此答案表明了。
tcshmksh等外壳程序没有进程替代。那么,如何在不进行进程替换的情况下将多个命令的输出重定向到另一个命令呢?分组并加上管道!
$ (echo foo;echo bar) | wc
      2       2       8

实际上与上面的示例相同,但是,这与过程替换完全不同,因为我们将整个子外壳的stdout和wc的stdin与管道连接在一起。另一方面,进程替换使命令读取临时文件描述符。
因此,如果我们可以使用管道进行分组,为什么需要进行进程替换?因为有时我们不能使用管道。考虑下面的示例-比较两个命令与diff的输出(需要两个文件,在这种情况下,我们为它提供两个文件描述符)
diff <(ls /bin) <(ls /usr/bin)


评论


<<用于从进程替换中获取标准输入时使用。这样的命令可能类似于:cmd1 <<(cmd2)。例如,wc <<(日期)

–John1024
2015年9月27日在8:05

是的,bash,zsh和AT&T ksh {88,93}支持进程替换,但pdksh / mksh不支持。

–John1024
2015年9月27日在8:12

<<本身不是事物,在流程替换的情况下,它只是一个<,后面紧跟其他以<

–user253751
2015年9月27日在8:40



@muru据我所知,<<<首先由Plan 9 rc shell的Unix端口实现,然后由zsh,bash和ksh93采用。我不会再称其为bashism。

– jlliagre
2015年9月27日在11:19

不能使用管道的另一个示例:echo'foo'|读; echo $ {REPLY}将不返回foo,因为在子外壳中启动了读取—管道启动了一个子外壳。但是,请阅读<<(echo'foo'); echo $ {REPLY}正确返回foo,因为没有子外壳。

–帕迪·兰道(Paddy Landau)
2015年9月29日上午10:38

#2 楼

< <是语法错误:

$ cat < <
bash: syntax error near unexpected token `<'


< <()是结合了重定向(<())的进程替换(<): br />
$ wc -l < <(grep ntfs /etc/fstab)
4
$ wc -l <(grep ntfs /etc/fstab)
4 /dev/fd/63


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

要清楚,没有< <运算符。

评论


我的回答是,<<(()比<()更有用,对吗?

– solfish
17年8月17日在13:28

@solfish <()给出了类似文件名的内容,因此它通常更有用-<<()在可能不需要的地方替换标准输入。在wc中,后者恰好更有用。它可能在其他地方用处不大

–muru
17年8月17日在15:07



#3 楼

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

command2 | command1


区别在于您正在运行bashcommand1在第二种情况下在子shell中运行,而在第一种情况下在当前shell中运行。这意味着在command1中设置的变量不会因过程替换变量而丢失。

#4 楼

< <将给出语法错误。正确使用方法如下:

借助示例进行解释:

< <()的示例:

while read line;do
   echo $line
done< <(ls)


在上面的示例中,while循环的输入来自ls命令,该命令可以逐行读取,并且可以在循环中读取echo

<()用于替换进程。有关<()的更多信息和示例,请参见以下链接:

过程替换和管道