如何让$!在sh -c中正常工作?求修正指定bash脚本的可行方案
Bash脚本问题分析与修正方案
咱们来一步步拆解你碰到的两个脚本问题,再给出可行的解决办法:
第一个脚本报错的根源
你第一个用双引号包裹sh -c参数的脚本,踩了两个坑:
!触发历史扩展:bash里!是历史命令扩展的特殊字符,双引号包裹的内容里它会被当前shell尝试解析,所以才会弹出bash: !: event not found的错误。- 变量被提前解析:
$pid1、$pid2甚至$!都会被外层shell先解析一遍,根本轮不到sh -c启动的子shell去处理,导致子shell里拿不到正确的进程ID。
第二个脚本的变量解析问题
第二个用单引号的脚本,虽然避开了历史扩展,但单引号是bash里的“强引用”——里面所有变量都会原封不动传递,外层的$name不会被解析成bob,所以子shell里输出的就是$name这几个字符,不是你要的名字。
靠谱的修正方案
要同时搞定变量传递和子shell内的进程ID捕获,最稳妥的方式是用单引号包裹sh -c的核心逻辑,通过位置参数传递外层变量,这样既不会触发历史扩展,又能让子shell拿到外层的$name:
name='bob' sh -c 'echo "$1"; sleep 20 & pid1=$!; sleep 10 & pid2=$!; echo "pid1: $pid1, pid2: $pid2"' _ "$name"
为啥这个写法能work?
- 单引号包裹的命令串里,
$!、$pid1、$pid2都会在子shell里被解析,外层shell不会插手,自然不会触发历史扩展。 _是给sh -c的第一个参数(对应子shell的$0,也就是shell的名称,这里用下划线占位就行),"$name"作为第二个参数,对应子shell里的$1,这样就能把外层的bob准确传递进去。
如果你实在想用双引号写法,得给所有子shell需要保留的变量加反斜杠转义,还要处理!的问题(得临时关闭历史扩展),但这种写法容易踩坑,不推荐:
name='bob' # 临时关闭历史扩展再执行 set +o histexpand sh -c "echo $name; sleep 20 & pid1=\$!; sleep 10 & pid2=\$!; echo \"pid1: \$pid1, pid2: \$pid2\"" set -o histexpand
这里我们先关闭历史扩展,再执行脚本,同时给$!、$pid1、$pid2加反斜杠,让它们留在子shell里解析,但这种写法太繁琐,还是第一种位置参数的方式更简洁可靠。
内容的提问来源于stack exchange,提问作者Y.Huang
相关产品推荐
相关产品推荐

