通过管道传递标准输出一个命令的...到标准输入另一个命令是一种强大的技术。但是,如果你需要将标准输出多个命令的输出通过管道传递? 这就是进程替换派上用场的地方。
进程替换 将一个 进程 (或多个进程) 的输出馈送到标准输入另一个进程的输入。
>>(command_list)
<(command_list)
进程替换使用/dev/fd/<n>文件,将括号内进程的结果发送到另一个进程。 [1]
![]() | 在 "<" 或 ">" 与括号之间 *没有* 空格。 如果有空格会产生错误消息。 |
bash$ echo >(true) /dev/fd/63 bash$ echo <(true) /dev/fd/63 bash$ echo >(true) <(true) /dev/fd/63 /dev/fd/62 bash$ wc <(cat /usr/share/dict/linux.words) 483523 483523 4992010 /dev/fd/63 bash$ grep script /usr/share/dict/linux.words | wc 262 262 3601 bash$ wc <(grep script /usr/share/dict/linux.words) 262 262 3601 /dev/fd/63 |
![]() | Bash 创建一个带有两个 文件描述符 的管道,--fIn和fOut--。 true 的标准输入输出连接到fOut(dup2(fOut, 0)),然后 Bash 传递一个/dev/fd/fIn参数给 echo。 在缺少/dev/fd/<n>文件描述符的系统上,Bash 可能会使用临时文件。(感谢 S.C.) |
进程替换可以比较两个不同命令的输出,甚至可以比较同一命令不同选项的输出。
bash$ comm <(ls -l) <(ls -al) total 12 -rw-rw-r-- 1 bozo bozo 78 Mar 10 12:58 File0 -rw-rw-r-- 1 bozo bozo 42 Mar 10 12:58 File2 -rw-rw-r-- 1 bozo bozo 103 Mar 10 12:58 t2.sh total 20 drwxrwxrwx 2 bozo bozo 4096 Mar 10 18:10 . drwx------ 72 bozo bozo 4096 Mar 10 17:58 .. -rw-rw-r-- 1 bozo bozo 78 Mar 10 12:58 File0 -rw-rw-r-- 1 bozo bozo 42 Mar 10 12:58 File2 -rw-rw-r-- 1 bozo bozo 103 Mar 10 12:58 t2.sh |
进程替换可以比较两个目录的内容 —— 查看哪些文件名在一个目录中,但不在另一个目录中。
diff <(ls $first_directory) <(ls $second_directory) |
进程替换的其他一些用法和用途
read -a list < <( od -Ad -w24 -t u2 /dev/urandom ) # Read a list of random numbers from /dev/urandom, #+ process with "od" #+ and feed into stdin of "read" . . . # From "insertion-sort.bash" example script. # Courtesy of JuanJo Ciarlante. |
PORT=6881 # bittorrent # Scan the port to make sure nothing nefarious is going on. netcat -l $PORT | tee>(md5sum ->mydata-orig.md5) | gzip | tee>(md5sum - | sed 's/-$/mydata.lz2/'>mydata-gz.md5)>mydata.gz # Check the decompression: gzip -d<mydata.gz | md5sum -c mydata-orig.md5) # The MD5sum of the original checks stdin and detects compression issues. # Bill Davidsen contributed this example #+ (with light edits by the ABS Guide author). |
cat <(ls -l) # Same as ls -l | cat sort -k 9 <(ls -l /bin) <(ls -l /usr/bin) <(ls -l /usr/X11R6/bin) # Lists all the files in the 3 main 'bin' directories, and sorts by filename. # Note that three (count 'em) distinct commands are fed to 'sort'. diff <(command1) <(command2) # Gives difference in command output. tar cf >(bzip2 -c > file.tar.bz2) $directory_name # Calls "tar cf /dev/fd/?? $directory_name", and "bzip2 -c > file.tar.bz2". # # Because of the /dev/fd/<n> system feature, # the pipe between both commands does not need to be named. # # This can be emulated. # bzip2 -c < pipe > file.tar.bz2& tar cf pipe $directory_name rm pipe # or exec 3>&1 tar cf /dev/fd/4 $directory_name 4>&1 >&3 3>&- | bzip2 -c > file.tar.bz2 3>&- exec 3>&- # Thanks, St�phane Chazelas |
这里有一种方法可以规避 echo 通过管道传递到在子shell中运行的 while-read 循环 的问题。
示例 23-1. 无需 fork 的代码块重定向
#!/bin/bash # wr-ps.bash: while-read loop with process substitution. # This example contributed by Tomas Pospisek. # (Heavily edited by the ABS Guide author.) echo echo "random input" | while read i do global=3D": Not available outside the loop." # ... because it runs in a subshell. done echo "\$global (from outside the subprocess) = $global" # $global (from outside the subprocess) = echo; echo "--"; echo while read i do echo $i global=3D": Available outside the loop." # ... because it does NOT run in a subshell. done < <( echo "random input" ) # ^ ^ echo "\$global (using process substitution) = $global" # Random input # $global (using process substitution) = 3D: Available outside the loop. echo; echo "##########"; echo # And likewise . . . declare -a inloop index=0 cat $0 | while read line do inloop[$index]="$line" ((index++)) # It runs in a subshell, so ... done echo "OUTPUT = " echo ${inloop[*]} # ... nothing echoes. echo; echo "--"; echo declare -a outloop index=0 while read line do outloop[$index]="$line" ((index++)) # It does NOT run in a subshell, so ... done < <( cat $0 ) echo "OUTPUT = " echo ${outloop[*]} # ... the entire script echoes. exit $? |
示例 23-2. 将 进程替换 的输出重定向到循环中。
#!/bin/bash # psub.bash # As inspired by Diego Molina (thanks!). declare -a array0 while read do array0[${#array0[@]}]="$REPLY" done < <( sed -e 's/bash/CRASH-BANG!/' $0 | grep bin | awk '{print $1}' ) # Sets the default 'read' variable, $REPLY, by process substitution, #+ then copies it into an array. echo "${array0[@]}" exit $? # ====================================== # bash psub.bash #!/bin/CRASH-BANG! done #!/bin/CRASH-BANG! |
一位读者发来了以下关于进程替换的有趣示例。
# Script fragment taken from SuSE distribution: # --------------------------------------------------------------# while read des what mask iface; do # Some commands ... done < <(route -n) # ^ ^ First < is redirection, second is process substitution. # To test it, let's make it do something. while read des what mask iface; do echo $des $what $mask $iface done < <(route -n) # Output: # Kernel IP routing table # Destination Gateway Genmask Flags Metric Ref Use Iface # 127.0.0.0 0.0.0.0 255.0.0.0 U 0 0 0 lo # --------------------------------------------------------------# # As St�phane Chazelas points out, #+ an easier-to-understand equivalent is: route -n | while read des what mask iface; do # Variables set from output of pipe. echo $des $what $mask $iface done # This yields the same output as above. # However, as Ulrich Gayer points out . . . #+ this simplified equivalent uses a subshell for the while loop, #+ and therefore the variables disappear when the pipe terminates. # --------------------------------------------------------------# # However, Filip Moritz comments that there is a subtle difference #+ between the above two examples, as the following shows. ( route -n | while read x; do ((y++)); done echo $y # $y is still unset while read x; do ((y++)); done < <(route -n) echo $y # $y has the number of lines of output of route -n ) More generally spoken ( : | x=x # seems to start a subshell like : | ( x=x ) # while x=x < <(:) # does not ) # This is useful, when parsing csv and the like. # That is, in effect, what the original SuSE code fragment does. |
[1] | 这与 命名管道 (临时文件) 具有相同的效果,事实上,命名管道曾经在进程替换中使用过。 |