【问题标题】:How can I execute Perl code stored inside a shell script variable?如何执行存储在 shell 脚本变量中的 Perl 代码?
【发布时间】:2021-04-29 19:03:33
【问题描述】:

我有一个脚本,它调用 Perl 的 Time::HiRes 模块来计算经过的时间。基本上,脚本通过以下一行来获取时间:

use Time::HiRes qw(time); print time

通过反引号发送到 Perl 解释器并取回结果。

#/bin/sh

START_TIME=`perl -e 'use Time::HiRes qw(time); print time'`
END_TIME=`perl -e 'use Time::HiRes qw(time); print time'`
ELAPSED_TIME=$(echo "($END_TIME - $START_TIME)" | bc)
echo $ELAPSED_TIME

我试图以更模块化的方式重写它,但我被 Bash shell 的引用规则难住了。

#/bin/sh
CALCULATE='bc'
NOW="perl -e 'use Time::HiRes qw(time); print time'"
START_TIME=`$NOW`
[Some long running task ...]
ELAPSED_TIME=$(echo "($NOW - $START_TIME)" | $CALCULATE)
echo $ELAPSED_TIME

Bash 抱怨没有正确引用某些内容。为什么 Bash 不直接扩展 $NOW 中的命令并将其传递给反引号执行?

我尝试了各种方法将 Perl 代码嵌入到 shell 脚本变量中,但我似乎无法做到这一点。

如何在 shell 脚本中正确引用 Perl 代码?

【问题讨论】:

  • @Mat 为了让人们更容易回答,我只是展示了一个更大的脚本的摘录。我剪切并粘贴错误。感谢您指出。固定的。但问题仍然是引用。
  • 仅供参考,你可以将你的 perl 单行代码写成perl -MTime::HiRes=time -e 'print time'

标签: perl bash escaping


【解决方案1】:

我认为使用函数是最直接的方法:

#! /bin/bash

now() {
    perl -e 'use Time::HiRes qw(time); print time';
}

calc=bc
time1=$(now)
time2=$(now)
elapsed=$(echo $time2 - $time1 | $calc)
echo $elapsed $time1 $time2

基本上不需要引用。

【讨论】:

  • 就是这样!傻我。谈论被困在一个盒子里。我有时会忘记 Bash 是一种成熟的编程语言。谢谢。
【解决方案2】:

您的问题是 $NOW 只是一个包含一些 Perl 代码的字符串。你需要告诉 Bash 执行它,使用反引号或$()

ELAPSED_TIME=$(echo "($($NOW) - $START_TIME)" | $CALCULATE)

此外,Bash 可以原生地进行算术运算:

ELAPSED_TIME=$(( $($NOW) - $START_TIME))

无需调用bc

最后,启动和停止perl 可能会花费大量时间,这会给您的结果添加噪音。我建议只运行一次perl,并让perl 自己执行长时间运行的任务。然后,您也可以在 Perl 本身内进行所有计算:

#!/usr/bin/perl

use Time::HiRes qw(time);

my $start = time;
system(@ARGV);
my $end = time;

print "Elapsed: ", ($end - $start), "\n"

或者您可以使用 Bash 内置 time(或 /usr/bin/time)直接进行所有计时。

【讨论】:

  • 关于 perl 解释器加载时间的要点。但这是我正在维护的一些旧代码,所以我不想改变太多。切换到 Perl 将是移植它,而不是重构,这有点太多的工作。我宁愿随时用纯 Perl 编写它。
  • "无需调用 bc" - 我的 bash(和 expr)不使用十进制数进行算术运算。
  • @Mat,啊,是的,zsh 做到了,这让我有点不知所措
【解决方案3】:

如果$NOW 在引号之外,它会被空格分割。

$ perl -E'say 0+@ARGV; say for @ARGV' $NOW
7
perl
-e
'use
Time::HiRes
qw(time);
print
time'

您可以用双引号将变量括起来以避免这种情况:

$ perl -E'say 0+@ARGV; say for @ARGV' "$NOW"
1
perl -e 'use Time::HiRes qw(time); print time'

但是您想将该字符串作为 shell 命令执行。为此,请使用eval

$ eval "$NOW"
1335602750.57325

最后,为了分配它,我们使用反引号(或等效的$( ... ))。

$ START_TIME=$(eval "$NOW")
$ echo $START_TIME
1335602898.78472

之前发布的函数显然更干净,但你说你需要帮助引用。


顺便说一句,

perl -e 'use Time::HiRes qw(time); print time'

可以缩短为

perl -MTime::HiRes=time -e'print time'

甚至到以下内容(因为尾随的新行非常好):

perl -MTime::HiRes=time -E'say time'

或者如果你真的想打高尔夫球:

perl -MTime::HiRes=time -Esay+time

【讨论】:

  • 在最后加了一点颜色。
【解决方案4】:

以下是您的脚本的修改版本。您基本上需要了解某些应用程序的标准输出是针对标准错误(stderr),所以当您没有看到它们的输出放入变量时,您只需将其重定向到标准输出(stdout):

#/bin/sh
CALCULATE='bc'
echo 'starting'
NOW=$(perl -e 'use Time::HiRes qw(time); print time' 2>&1)
sleep 3
echo 'ending'
END_TIME=$(perl -e 'use Time::HiRes qw(time); print time' 2>&1)
ELAPSED_TIME=$(echo "($NOW - $START_TIME)")
echo $ELAPSED_TIME

【讨论】:

    【解决方案5】:

    我认为 HiRes 时间的好处被 perl 是一个相对繁重的外部进程并且被单独调用两次这一事实所抵消。

    如果您不需要那么多小数位的值。您可以像使用 Bash 中内置的时间一样

    task() {
        [Some long running task ...]
    }
    TIMEFORMAT=%R
    elapse=$({ time task > task.out 2>&1; } 2>&1)
    echo $elapse
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-27
      • 1970-01-01
      • 2011-08-02
      相关资源
      最近更新 更多