让Perl参数被当前Shell解释的正确方法
我写了一个简单的Perl脚本,想让传入的命令参数能被当前Shell正确解释通配符,但遇到了问题。脚本内容如下:
#!/usr/bin/perl use strict; use warnings; use IPC::Run3; my $stdout2; print $ARGV[0]; print "\n"; my @cmd1 = split /\s+/, $ARGV[0] ; run3 (\@cmd1, \undef, \$stdout2, \$stdout2); print $stdout2
执行命令:
perl comp.pl "md5sum *(.)"
输出结果:
md5sum *(.) md5sum: '*(.)': No such file or directory
可以看到*(.)没有被Shell解释成当前目录下的所有文件,请问该怎么让Perl参数被当前Shell正确解释?
这个问题的核心很明确:你现在的做法是手动把命令字符串拆成数组传给run3,但run3在这种模式下会直接执行目标程序,不会经过Shell处理——而*(.)是Bash的扩展通配符(需要开启extglob选项),只有Shell才有能力解析它。
这里有两种靠谱的解决思路:
1. 让Shell全权处理整个命令
如果你希望完全依赖当前Shell的解析能力(包括通配符、别名、管道这类Shell特性),最简单的方式是让run3调用Shell来执行整个命令字符串,而不是拆分后的数组。修改后的代码如下:
#!/usr/bin/perl use strict; use warnings; use IPC::Run3; my $stdout2; print $ARGV[0]; print "\n"; # 调用bash并通过-c参数传入完整命令字符串 run3 ['bash', '-c', $ARGV[0]], \undef, \$stdout2, \$stdout2; print $stdout2;
这样一来,md5sum *(.)会被Bash完整解析,*(.)会扩展成当前目录下的所有文件(前提是你的Bash已经开启extglob,默认一般是开启的,如果没开可以先执行shopt -s extglob)。
2. 在Perl内部处理通配符
如果你不想依赖Shell,也可以让Perl自己完成通配符扩展。Perl自带的glob函数能处理基础通配符,但不支持*(.)这种Bash扩展语法,不过我们可以用File::Glob模块的bsd_glob来实现:
#!/usr/bin/perl use strict; use warnings; use IPC::Run3; use File::Glob qw(bsd_glob); my $stdout2; my $cmd_str = $ARGV[0]; print $cmd_str, "\n"; # 拆分命令名和参数部分 my ($cmd, $args_part) = split /\s+/, $cmd_str, 2; # 用bsd_glob处理参数中的通配符 my @args = map { bsd_glob($_) } split /\s+/, $args_part; run3 [$cmd, @args], \undef, \$stdout2, \$stdout2; print $stdout2;
这种方式不需要调用Shell,直接在Perl内部完成通配符解析,兼容性更好,但要注意:管道、重定向这类Shell专属语法还是无法处理,适合单纯处理文件通配符的场景。
补充:为什么原来的代码不行?
原来的代码里,split /\s+/, $ARGV[0]只是把字符串按空格拆成了md5sum和*(.)两个元素,run3直接把*(.)作为普通参数传给md5sum——但md5sum本身完全不认识Shell的通配符语法,自然会把它当成一个不存在的文件名,所以才会报错。
内容的提问来源于stack exchange,提问作者zzxyz

