代码之家  ›  专栏  ›  技术社区  ›  StevieD

如何为从perl脚本运行的bash命令加载bash_概要文件?

  •  2
  • StevieD  · 技术社区  · 7 年前

    我编写了一个简单的命令,让我可以运行终端历史记录中的最后N个命令。看起来是这样的: $> r 3

    我的bash配置文件中有以下别名: alias r="history -w; runlast $1"

    然后使用以下简单的perl脚本 runlast 命令:

    #!/usr/bin/env perl
    use strict;
    use warnings;
    
    my $lines = $ARGV[0] || exit;
    
    my @last_commands = split /\n/, 
        `bash -ic 'set -o history; history' | tail -$lines`;
    
    @last_commands = 
        grep { $_ !~ /(^r |^history |^rm )/ } 
        map { local $_ = $_; s/^\s+\d+\s+//; $_ } 
        @last_commands;
    
    foreach my $cmd (@last_commands) {
      system("$cmd");
    }
    

    source ~/.bash_profile; 给我的 r 命令别名,但没有效果。不过,我不确定我做的是否正确。

    3 回复  |  直到 7 年前
        1
  •  3
  •   zdim    7 年前

    这个 system 分叉运行shell的进程,该进程是非登录和非交互的;因此不会进行初始化,也不会得到别名。还要注意使用的外壳是 /bin/sh ,这通常是指向另一个shell的链接。这通常是 bash 但不总是,所以快跑 猛击 明确地。

    为了避免这种情况,您需要使用别名来源文件,但是 猛击 曼佩奇说

    所以你需要 shopt -s expand_aliases ,如前所述。但是还有另一个问题:在同一个物理行上,别名还不可用;因此在一个行中,它不会像这样工作。

    我也建议你加上别名 .bashrc ,或者在一个单独的源文件中。

    解决

    • shopt-s扩展别名 ~/.bashrc ,和 (或源文件),然后运行 猛击

      system('/bin/bash', '-cl', 'source ~/.bashrc; command');
      

      哪里 -l --login .

      在我的测试中 source ~/.bashrc

      当bash作为交互式登录shell调用,或者作为带有--login选项的非交互式shell调用时,它首先从文件中读取并执行命令 /etc/profile ,如果该文件存在。在读了那个文件之后,它会查找 ~/.bash_profile , ~/.bash_login ,和 ~/.profile ,并从第一个存在且可读的命令中读取和执行命令。

      并进一步说明 ~/.bashrc公司 在非登录的交互式shel运行时读取。所以我增加了明确的来源。

      在我的测试中 巴什尔 (与 shopt

      这有点笨手笨脚。此外,初始化可能不希望从脚本运行。

    • ~/.bashrc公司 并发布 肖特 指挥部, 然后换行 在命令之前

      system('/bin/bash', '-c', 
          'source ~/.bashrc; shopt -s expand_aliases\ncommand');
      

      真正地。它起作用了。

    最后,这有必要吗?它需要麻烦,也许有更好的设计。

    其他意见

    • 反拍( qx )是上下文感知的。如果它在列表上下文中使用(例如,它的返回被分配给数组),则命令的输出将作为行列表返回。当你用它作为 split 分裂

      my @last_commands = `bash -ic 'set -o history; history $lines`;
      

      history N 最后一个 N 线。在这种情况下,新线保持不变。

    • 最后返回 N个 历史的长河所以没有必要 last

    • map 可以不改变原稿

      map { s/^\s+\d+\s+//r } @last_commands;
      

      /r 修改 s/// 运算符返回新字符串,而不更改原始字符串。

    • 无需显式使用 $_ 最后一次 grep

      grep { not /^r |^history |^rm ?/ } ...
      

      grep { not /^(?:r|history|rm)[ ]?/ } ...
      

      现在需要paren,但它只用于分组 ?: 使其无法捕获匹配项。我用 [ ] 强调有一个预定的空间;这是不必要的。

      ? 使空间可选 history r ?) 可能没有空间。

        2
  •  2
  •   tripleee    7 年前

    打印 命令,并使您当前的交互式shell eval 从你的历史中打印出来的字符串。(我可能会完全摆脱Perl,但这与本文无关。)

    如果在当前shell中对命令求值,则可以避免许多上下文问题,这些问题对于 system() 或是任何涉及新程序的东西。例如,子进程不能访问当前shell中的未导出变量。 var="foo", echo "$var"; r 1

    不管怎样,别名都很糟糕,所以让我们重新定义 r

    r(){
        history -w
        eval $(printlast "$1")
    }
    

    ... 哪里重构 runlast 变成另一个剧本 printlast 是一个微不足道的附加要求。或者把它变成一个(简单得多的!)外壳函数:

    printlast () {
        history "$1" |
        perl -ne 's/^\s*\d+\s+\*?//; print unless m/^(history|rm?)($|\s)'
    }
    

    有了这个,你也可以摆脱 history -w

    请注意我们是如何使用Perl的,因为它很有用;但是在处理shell时,将主要功能放在shell中是有意义的。

        3
  •  1
  •   codeforester    7 年前

    不能将Bash脚本中的源代码转换为Perl脚本。bash_概要文件必须由执行命令的shell提供。当Perl运行时 system ,每次都会叉出一个新壳。

    对于运行的每个命令,都必须在bash配置文件中找到源代码 :

    system('source ~/.bash_profile; ' + $cmd);
    

    还有一件事, 系统 调用非交互式shell。因此,.Bash_profile中定义的Bash别名将无法工作,除非调用:

    shopt -s expand_aliases
    

    在剧本里