Command Substitution(命令替换)
shell-intro#command-substitution
Subshell(子shell)
Definition: A subshell is a child process launched by a shell (or shell script).
一个最基本的motivation是,当你在shell脚本中写一些函数,这些函数如果涉及到切换目录,那函数执行完之后,当前目录到底有没有切换?
结论:如果使用子shell,切换目录的事情都在子shell发生,不会影响当前shell。如果没有用子shell,则当前shell的目录会受影响。
Note
这里我们说的shell其实就是一个bash/sh进程。进程有父子,bash进程(A)启动(fork)了另一个bash进程(B),那么B就是A的子shell,A就是B的父shell。
子shell一般用小括号()包裹,另一个常见的形式是管道符|,管道符之后的一般是子shell。
demo() {
echo $(pwd) # /a/b/c
( # launch a subshell to run the following
cd haha
echo $(pwd) # /a/b/c/haha
)
echo $(pwd) # /a/b/c
}
foo() {
cd haha
}
foo_sub() ( # note here we use '('
cd haha
)
bar() {
echo $(pwd) # /a/b/c
foo
echo $(pwd) # /a/b/c/haha
}
bar1() {
echo $(pwd) # /a/b/c
foo_sub # this will launch a subshell
echo $(pwd) # /a/b/c, so pwd here will not change
}再来看一个陷进,
#!/bin/bash
# Pitfalls of variables in a subshell.
outer_variable=outer
echo
echo "outer_variable = $outer_variable"
echo
(
# Begin subshell
echo "outer_variable inside subshell = $outer_variable"
inner_variable=inner # Set
echo "inner_variable inside subshell = $inner_variable"
outer_variable=inner # Will value change globally?
echo "outer_variable inside subshell = $outer_variable"
# Will 'exporting' make a difference?
# export inner_variable
# export outer_variable
# Try it and see.
# End subshell
)
echo
echo "inner_variable outside subshell = $inner_variable" # Unset.
echo "outer_variable outside subshell = $outer_variable" # Unchanged.
echo
exit 0
# What happens if you uncomment lines 19 and 20?
# Does it make a difference?
# Answer by yychi: No! 'export' in subshell does not transfer changes to parent shell.In general, an external command in a script forks off a subprocess, whereas a Bash builtin does not. For this reason, builtins execute more quickly and use fewer system resources than their external command equivalents.
参考:https://tldp.org/LDP/abs/html/subshells.html
Tip
如无必要,不要使用子shell,因为有性能损失。组命令
{ command1; command2; [command3; ...] }运行更快且占用内存更低。另,花括号和命令之间的空格是必须的。
Process Substitution(进程替换)
将一个命令的输出作为另一个命令的输入是一种很常见的手段。
# 文件排序、去重
cat a.txt | sort | uniq
# 对所有csv文件计算md5
ls *.csv | xargs -i md5sum {}
# 找出所有路径包含somedir的abc.cfg文件
find -name "*abc.cfg*" | grep somedir想一想,如果要将多个命令的输出,作为一个命令的输入,要怎么做呢?
这种情况就得用进程替换了!
进程替换的语法如下,
>(command_list)
<(commamd_list)注意小于/大于号和括号之间不能有空格,否则语法错误。
进程替换的实质是用
/dev/fd/<n>(旧版)/proc/self/fd/<n>(新版)
文件描述符作为临时文件,将括号里面命令的输出发送到另一个进程。
$ echo >(true)
/proc/self/fd/13
$ echo <(true)
/proc/self/fd/11
$ wc <(cat /usr/share/dict/words)
102401 102401 972398 /proc/self/fd/11
$ grep script /usr/share/dict/words | wc
67 67 845
$ wc <(grep script /usr/share/dict/words)
67 67 845 /proc/self/fd/11来看一个经典的Subshell(子shell)陷进,
echo haha | read reply
echo $reply # 输出为空这是因为管道命令一般在子shell执行,等于管道之后的命令会fork当前的shell环境,执行,返回。但子shell里面做的修改是无法传递到当前shell进程的。所以,当前shell压根没有reply变量。
使用进程替换可以规避,
$ read reply < <(echo haha)
# ^ ^ First < is redirection, second is process substitution.
$ echo $reply
haha再来看一个更复杂的例子,
#!/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 $?参考:https://tldp.org/LDP/abs/html/process-sub.html
see also: Brief Introduction to Shell Script - comments