You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让$!在sh -c中正常工作?求修正指定bash脚本的可行方案

Bash脚本问题分析与修正方案

咱们来一步步拆解你碰到的两个脚本问题,再给出可行的解决办法:

第一个脚本报错的根源

你第一个用双引号包裹sh -c参数的脚本,踩了两个坑:

  1. !触发历史扩展:bash里!是历史命令扩展的特殊字符,双引号包裹的内容里它会被当前shell尝试解析,所以才会弹出bash: !: event not found的错误。
  2. 变量被提前解析:$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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 06:51:36