在sh -c调用场景下控制通配符展开的技术求助
sh -c调用Shell函数时的通配符展开控制问题 我来帮你搞定这个通配符展开的控制难题——用sh -c调用自定义Shell函数时,通配符的展开时机很容易踩坑,核心要明确:你希望通配符是在父shell(调用sh -c的那个shell)展开,还是在子shell(sh -c启动的shell)展开,或是留到函数内部自己处理?下面针对不同需求给出具体方案:
1. 让通配符在子shell(sh -c的执行环境)里展开
这是最常见的需求:你希望函数拿到的是匹配后的文件列表,而不是带通配符的字符串。但如果直接写sh -c ". ~/dwh_env.sh && my_unpack_func /path/*.gz /dest",父shell会先把/path/*.gz展开成所有匹配的文件名,导致子shell里的函数把第一个文件名当作FILEPATTERN,剩下的文件名变成多余参数,完全不符合预期。
正确写法:用引号/转义阻止父shell提前展开
你需要把带通配符的参数用单引号包裹,或者转义通配符,让父shell原封不动把字符串传给子shell,由子shell在执行函数时展开:
方法一:用单引号保护参数
sh -c '. ~/dwh_env.sh && my_unpack_func '\''/path/*.gz'\'' /dest'
这里的'\''是Shell里的实用技巧:先闭合外层单引号,插入单引号,再重新打开外层单引号,确保/path/*.gz作为原始字符串被传递。
方法二:转义通配符
sh -c ". ~/dwh_env.sh && my_unpack_func /path/\*.gz /dest"
通过在*前加反斜杠,让父shell把*.gz当作普通字符处理,子shell执行时再展开通配符。
2. 让通配符原封不动传递给函数(由函数内部处理)
如果你的函数需要自己处理通配符(比如用find做更复杂的匹配,或者需要判断模式本身),那就要确保父shell和子shell都不展开通配符,把原始的带通配符字符串传给函数。
正确写法:双重保护
sh -c '. ~/dwh_env.sh && my_unpack_func '\''/path/*.gz'\'' /dest'
或者用双引号嵌套转义:
sh -c ". ~/dwh_env.sh && my_unpack_func \"/path/*.gz\" /dest"
这时候函数里的FILEPATTERN变量就是原始的/path/*.gz,不会被任何shell提前展开。
3. 函数内部的安全展开技巧
如果函数需要自己展开通配符,别用eval(容易引发命令注入风险),推荐用compgen -G安全展开:
# 示例解压函数 my_unpack_func() { local file_pattern="$1" local dest_dir="$2" # 安全展开通配符,生成文件列表 local matched_files=() while IFS= read -r -d '' file; do matched_files+=("$file") done < <(compgen -G "$file_pattern" -0) # 检查是否有匹配的文件 if [ ${#matched_files[@]} -eq 0 ]; then echo "Error: No files match pattern '$file_pattern'" >&2 return 1 fi # 执行解压操作 for file in "${matched_files[@]}"; do echo "Unpacking $file to $dest_dir..." tar -xzf "$file" -C "$dest_dir" done }
compgen -G会安全匹配通配符,-0用null字符分隔文件名,避免空格或特殊字符导致的问题。
关键原理回顾
sh -c "<command_string>"的本质是让子shell解析执行<command_string>这个字符串:
- 如果通配符没有被引号/转义保护,父shell会先展开通配符,再把展开后的结果传给子shell;
- 如果用引号/转义保护,父shell会把带通配符的字符串原封不动传给子shell,由子shell在执行时决定是否展开;
- 要是需要完全保留通配符字符串,就要确保父shell和子shell都不处理它,这时候单引号是最可靠的方式。
内容的提问来源于stack exchange,提问作者htz

