sh -c调用grep时丢失xargs首个传入参数的修复方案
问题根因
sh -c的参数传递规则是核心诱因:
- 内联脚本字符串后的第一个传入参数,会被绑定到特殊变量
$0(当前shell进程的标识名,仅用于报错、日志场景),不会进入普通位置参数($1/$2...)序列 - 你写的
for i do ... done是for i in "$@"的简写,仅会遍历从$1开始的普通位置参数,因此xargs传入的第一个文件out-0.06被直接赋值给$0,没有进入循环处理流程,最终表现为首个参数丢失。
你用xargs echo能正常输出全部文件名,是因为echo会把所有传入参数(包括对应$0位置的第一个值)全部打印出来,不存在sh -c的特殊参数绑定逻辑,所以不会丢参数。
修复方法
推荐写法:补$0占位符(POSIX标准兼容)
在内联脚本字符串后加一个任意占位字符串(通常用sh或者_作为占位值,无实际逻辑作用),后续xargs传入的所有文件就会从$1开始正常进入位置参数列表,循环可以完整遍历所有文件:
ls out* | xargs sh -c 'for i do echo "$i";grep etot "$i" | tail -n 1; done' sh
注意命令末尾的sh就是占位用的$0参数。
可选写法:手动将$0加入遍历范围
如果不想加占位参数,也可以修改循环的遍历列表,显式把$0纳入处理范围,这种写法可读性较差,不推荐日常使用:
ls out* | xargs sh -c 'for i in "$0" "$@"; do echo "$i";grep etot "$i" | tail -n 1; done'
鲁棒性增强写法(适配特殊文件名)
ls管道传参的写法在文件名包含空格、换行、通配符时会出现解析错误,生产环境处理文件推荐用find+空分隔符的写法,彻底规避特殊文件名问题:
find . -maxdepth 1 -type f -name "out*" -print0 | xargs -0 sh -c 'for i do echo "$i";grep etot "$i" | tail -n 1; done' sh
修复后预期输出
执行修复后的命令,三个目标文件都会被正常处理,不会再出现首个文件遗漏的问题:
out-0.06 etot = -6.355xxxxxxE+04 abs_ev = xxxxxx rel_ev = xxxxxx out-0.08 etot = -6.35545639E+04 abs_ev = 7.24E-04 rel_ev = 5.81E-08 out-0.1 etot = -6.35550650E+04 abs_ev = 5.53E-05 rel_ev = 4.43E-09
内容的提问来源于stack exchange,提问作者Jack
相关产品推荐
相关产品推荐

