如何将命令多行输出作为多个参数传入单个命令(非逐行调用)
解决方案
所有需要把多行内容拆分为独立参数传给单条命令的场景,不要用字符串拼接、手动加引号、eval这类hack写法,这类写法遇到带空格、换行、shell特殊字符的输入必然出问题,还存在命令注入风险。以下是全兼容bash、zsh的标准实现,按场景选用即可:
- 针对
find输出文件列表的场景,优先用find原生的-exec {} +语法,是最安全、最简洁的方案 - 通用任意命令输出的场景,用
xargs指定分隔符传参 - 需要暂存参数多次复用的场景,用shell数组存储
针对find场景的最优解:-exec {} +
这是POSIX标准定义的find原生用法,不需要任何管道、字符串处理,自动把所有匹配到的文件路径作为独立参数填充到{}位置,只执行一次目标命令,天生兼容所有特殊字符(空格、换行、引号、通配符全不怕)。
注意结尾必须是+,如果写\;就会变成每个文件执行一次命令,属于你提到的非预期效果。
对应你的Swift编译场景,直接写即可:
find * -type f -name "*.swift" -exec xcrun -sdk macosx swiftc -o "$OUTPUT_FILE" {} +
通用场景:xargs传参
如果输入不是来自find,而是其他任意命令的多行输出,可以用xargs实现批量传参,默认会把所有输入项攒到一起一次性调用目标命令,不会逐行执行:
- 如果上游命令支持NUL分隔输出(绝大多数处理文件的命令都支持,比如
grep -l、sort -z等),用-0参数按NUL拆分,是100%安全的写法,兼容所有特殊字符:# 示例:把grep找到的包含指定关键字的swift文件一次性传给编译命令 grep -rlz "class Car" --include="*.swift" . | xargs -0 xcrun -sdk macosx swiftc -o "$OUTPUT_FILE" - 如果上游只能输出换行分隔的内容,且你确定输入内容本身不含换行(绝大多数日常场景都满足,比如普通文件名不会带换行),可以用
-d '\n'指定按换行拆分参数,bash 4.4+、zsh原生支持:# 对应你最开始的a/b/c测试用例,执行效果就是 foo a b c printf "a\nb\nc\n" | xargs -d '\n' foo
需要暂存参数:shell数组
如果你需要先把参数列表存起来,后续传给多个不同命令,直接用shell数组存储即可,不要用普通字符串变量存参数列表。
对应你存Swift文件列表的场景,写法如下:
# 初始化空数组 SOURCE_FILES=() # 安全读取所有匹配的swift文件路径到数组 while IFS= read -r -d '' file; do SOURCE_FILES+=("$file") done < <(find * -type f -name "*.swift" -print0) # 调用命令时,用 "${数组名[@]}" 格式展开,每个元素自动成为独立参数 xcrun -sdk macosx swiftc -o "$OUTPUT_FILE" "${SOURCE_FILES[@]}"
注意展开数组时双引号不能丢,[@]不能写成[*],否则会回到参数拆分错误的问题。
之前尝试的方案问题说明
- 方案1把换行替换成空格存到普通变量:shell展开无引号的变量时,会按所有空白字符(空格、制表符、换行)拆分内容,带空格的文件名比如
Models/Road Bike.swift会被拆成Models/Road和Bike.swift两个参数,必然报错。 - 方案2手动给文件名加引号:shell解析无引号包裹的变量值时,不会识别变量内部的引号,你手动加的
"只会被当成文件名的一部分,完全起不到分隔参数的作用。 - 方案3用eval拼命令:本质是把拼接后的字符串重新交给shell做完整解析,只要文件名里包含
;、$、>这类shell特殊字符,轻则参数解析错误,重则直接执行恶意代码,安全风险极高,除非没有其他替代方案否则绝对不要用。
额外提醒:不要解析
ls的输出传参,ls的输出是面向人类阅读的格式化内容,天生不适合机器解析,所有处理文件列表的场景,优先用find、shell通配符。如果不需要递归查找文件,直接用通配符更简单:# 匹配当前目录下所有swift文件,自动处理带空格的文件名 xcrun -sdk macosx swiftc -o "$OUTPUT_FILE" -- *.swift
内容的提问来源于stack exchange,提问作者Mark A. Donohoe
相关产品推荐
相关产品推荐

