问题描述
bash中<
最佳解决方案
这里的文件
<
这就是我的意思:
$ wc << EOF
> one two three
> four five
> EOF
2 5 24
在这个例子中,我们告诉wc程序等待EOF字符串,然后输入五个字,然后输入EOF来表示我们已完成输入。实际上,它类似于单独运行wc,输入单词,然后按Ctrl D
在bash中,这些是通过临时文件实现的,通常采用/tmp/sh-thd.形式,而在破折号中,它们是作为匿名管道实现的。这可以通过使用strace命令跟踪系统调用来观察。将bash替换为sh以查看/bin/sh如何执行此重定向。
$ strace -e open,dup2,pipe,write -f bash -c 'cat <
> test
> EOF'
这里字符串
<<
bash中的Here-strings是通过临时文件实现的,通常采用/tmp/sh-thd.格式,后来取消链接,从而使它们暂时占用一些内存空间但不会显示在/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中,您将看到它被表示为
注意:从技术上讲,当您说<
现在如果我们只是处理替换会发生什么?
$ echo
/dev/fd/63
如您所见,shell创建临时文件描述符/dev/fd/63,其中输出(根据Gilles’s answer,是一个匿名管道)。这意味着
因此,非常简单的示例是将两个echo命令的输出进程替换为wc:
$ wc <
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
次佳解决方案
<
$ cat < <
bash: syntax error near unexpected token `
<
一个人为的例子:
$ wc -l <
4
$ wc -l
4 /dev/fd/63
使用进程替换,文件描述符的路径就像文件名一样使用。如果您不想(或不能)直接使用文件名,则将进程替换与重定向结合使用。
需要说明的是,没有<
第三种解决方案
<
command2 | command1
假设运行bash的区别是command1在第二种情况下在子shell中运行,而它在第一种情况下在当前shell中运行。这意味着command1中设置的变量不会因过程替换变量而丢失。
第四种方案
<
借助示例解释:
<
while read line;do
echo $line
done<
在上面的例子中,while循环的输入将来自ls命令,该命令可以逐行读取,循环中编码为echo。
参考资料