如何在Perl中执行多行Shell命令?

3
我希望在perl中以以下方式运行shell命令:
tar --exclude="*/node_modules" \
--exclude="*/vendor" \
--exclude='.git' \
-zvcf /tmp/robot.tgz .

但是似乎 Perl 无法执行这个操作:
`tar --exclude="cv/node_modules" \
--exclude="*/vendor" \
--exclude='.git' \
-zvcf /tmp/robot.tgz .`;

这里是错误信息:
tar: Must specify one of -c, -r, -t, -u, -x
sh: line 1: --exclude=*/vendor: No such file or directory
sh: line 2: --exclude=.git: command not found
sh: line 3: -zvcf: command not found

看起来Perl将每行视为一个命令。


2
为什么不将其作为一行命令而不是多行命令运行呢?Perl可以很好地处理长字符串。无论如何,你需要在反斜杠前加倍,因为Perl会去除它们。 - Jonathan Leffler
1
@JonathanLeffler 是的,你说得对,我可以在一行中处理这个问题,但是我还是尝试着去做,因为有时候一行太长了,不容易阅读。 - roger
1
你可以通过连接多个较短的字符串来构建一个长行,这样就不会有可读性问题。但是提出请求也是有效的。 - Jonathan Leffler
1个回答

14

更新

我很抱歉,我的原始诊断是错误的。

在像这样的帖子中清楚地表达包含Perl转义字符的字符串的内容是很困难的。如果下面的任何内容对您来说不清楚,请写一条评论告诉我。我希望我没有使事情变得不必要地复杂。

我的原始解决方案仍然有效,并且将为您提供更好的命令内容控制,但是我为什么OP的代码对他们不起作用的原因是错误的,事实上提供了其他解决方法。

问题在于反引号(或qx / ... / )的内容被评估为双引号字符串,这意味着在执行字符串之前会展开Perl变量和转义序列,例如 \t \x20 。这其中的一个后果是,如果反斜杠后面跟着一个文字换行符,则删除反斜杠,只留下换行符。

这意味着像这样的语句:

my $output = `ls \
-l`;

将被预处理为"ls \n-l",不再包含用于向shell信号换行应该被移除的反斜杠(或确保命令首先传递给shell的反斜杠)。

除了直接操作命令字符串之外,还有两种解决方法。第一种是通过加倍反斜杠来转义反斜杠,像这样:

my $output = `ls \\
-l`;

其中一种方法是在反引号和换行符之间加上反斜杠,这可以防止Perl删除它。这将把反斜杠-换行符序列传递到Shell中,Shell会像正常情况下一样将其删除。

另一种方法是使用qx'...'代替反引号,并使用单引号定界符,这可以防止内容被处理为双引号字符串。

my $output = qx'ls \
-l';

这将很好地工作,除非您在要插值的字符串中使用了Perl变量。

原始帖子

问题是,在执行命令之前,shell会从命令字符串中删除由反斜杠引导的换行符。如果没有这一步,命令就无效了。

因此,在Perl中,您必须自己完成相同的操作,并且为此必须将命令放入临时变量中。

use strict;
use warnings 'all';

my $cmd = <<'END';
tar --exclude="*/node_modules" \
--exclude="*/vendor" \
--exclude='.git' \
-zvcf /tmp/robot.tgz .
END

$cmd =~ s/\\\n//g;

my $output = `$cmd`;

当然,没有必要使用反斜杠;您可以使用换行符并在执行命令之前将其删除

或者您可能更喜欢将操作包装在子例程中,像这样

use strict;
use warnings 'all';

my $output = do_command(<<'END');
tar --exclude="*/node_modules" \
--exclude="*/vendor" \
--exclude='.git' \
-zvcf /tmp/robot.tgz .
END

sub do_command {
    my ($cmd) = @_;
    $cmd =~ s/\\\n//g;
    `$cmd`;
}

所以,当Perl运行qx//时,它的行为有点像shell(根据"perlop" perldoc),但又不完全相同? - sferencik
@sferencik:这并不是那么简单,但qx//的行为与system相同,就像Perl的大部分功能一样,它被设计成“做你想要的”。基本上,如果Perl在字符串中发现任何shell元字符,比如通配符文件扩展、输入输出重定向、管道等,它将启动一个shell进程来执行命令。这里有一个列表。否则,它将像shell自己一样直接启动程序。 - Borodin
谢谢,很有道理。所以如果Borodin在那个多行qx//语句中使用了重定向,它就会起作用,对吗? - sferencik
@sferencik: 不,那里已经有通配符文件名了,所以它会被传递给shell。正如我在更新中所说的,问题是Perl双引号字符串中的反斜杠后跟换行符与仅有换行符相同:反斜杠被删除,因此shell永远不会看到它。单独的反斜杠是另一个shell元字符,因此如果你使用单引号或转义反斜杠来阻止其被吞噬,则它将被传递给shell并正常工作。通过多种具有不同规则的不同语言传递元字符总是很麻烦的。 - Borodin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接