如何为从Perl脚本运行的bash命令加载bash_profile?

时间:2018-08-19 04:05:10

标签: bash perl

我写了一个简单的命令,使我可以运行终端历史记录中的最后N条命令。看起来像这样:$> r 3,它将重播最后3个命令。

我的bash个人资料中具有以下别名: alias r="history -w; runlast $1"

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

#!/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");
}

这可行,但是我的bash配置文件具有别名和其他功能(例如颜色输出),我希望perl脚本可以访问。如何为perl加载bash配置文件,以便它与我的bash配置文件一起运行bash命令?我在某处读到,如果您为perl“提供bash配置文件”,则可以使其正常运行。因此,我尝试将source ~/.bash_profile;添加到我的r命令别名中,但是没有效果。不过,我不确定我是否做得正确。

3 个答案:

答案 0 :(得分:2)

system派生一个进程,该进程在其中运行非登录且非交互的外壳程序;因此无需进行初始化,也不会获得别名。另请注意,使用的外壳程序是/bin/sh,通常是到另一个外壳程序的链接。这通常是bash,但并非总是如此,因此请明确运行bash

要避免这种情况,您需要使用别名来获取文件,但是正如bash手册页所述

  

如果外壳不是交互式的,则别名不会展开,除非使用shopt设置expand_aliases shell选项(请参阅下面的SHELL BUILTIN COMMANDS下的shopt说明)。

因此,如上所述,您需要shopt -s expand_aliases。但是还有另外一个螺丝:在相同的物理线路上别名还不可用。所以它不会像一线一样工作。

我还建议将别名放在.bashrc或源的单独文件中。

解决方案

  • 在您的shopt -s expand_aliases中添加~/.bashrc,并在定义别名之前 (或带有别名的文件),然后以bash身份运行登录外壳

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

    其中-l--login的缩写。

    在我的测试中,不需要source ~/.bashrc;但是,手册页上写着

      

    当bash作为交互式登录shell或使用--login选项作为非交互式shell调用时,它首先从文件/etc/profile中读取并执行命令(如果该文件存在)。读取该文件后,它将按该顺序查找~/.bash_profile~/.bash_login~/.profile,并从存在且可读的第一个命令中读取并执行命令。

    并继续指定当运行非登录的交互式货架时读取~/.bashrc。因此,我添加了显式采购。

    在我的测试中,在未作为登录外壳运行的情况下采购.bashrc(添加了shopt)不起作用,我不确定为什么。

    这有点笨拙。同样,从脚本中运行初始化可能也是不可取的。

  • ~/.bashrc并发出shopt命令,先发出然后换行

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

    真的。可以。

最后,这有必要吗?它会带来麻烦,并且可能会有更好的设计。

其他评论

  • 反引号(qx)是上下文感知的。如果在列表上下文中使用它(例如,将其返回值分配给数组),则该命令的输出将作为行列表返回。当您将其用作split的参数时,它在标量上下文中为 is ,因此它会将所有输出返回为一个字符串。只需放下split

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

    我还使用history N来获取最后N行。在这种情况下,换行符会保留。

  • history 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)[ ]?/ } ...
    

    现在需要使用括号,但由于仅用于对?:进行分组,因此它无法捕获匹配项。我使用[ ]来强调存在预期的空间;这是没有必要的。

    我还添加了?以使空间可选,因为history(和r?)可能没有空间。

答案 1 :(得分:2)

正确的解决方案是让您的Perl脚本仅 print 命令,并使当前的交互式shell eval成为您历史记录中打印的字符串。 (我可能会完全摆脱Perl,但这不在这里。)

如果在当前shell中对命令进行了评估,则可以避免许多上下文问题,这些问题对于system()或通常涉及新进程的任何事情都是非常困难甚至难以解决的。例如,子流程无法访问当前外壳程序中的非导出变量。 var="foo", echo "$var"; r 1将很难用您当前的方法正确解决。使用当前的交互式shell还将自然而轻松地解决您试图使非交互式子shell像交互式对象一样遇到的问题。

无论如何别名还是很烂,所以我们将r重新定义为一个函数:

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

...其中将runlast重构为不同的脚本printlast是一个琐碎的附加要求。或者也许只是将其变成一个(简单得多!)shell函数:

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

这样,您还可以摆脱history -w定义中的r

在有用的地方注意我们如何使用Perl;但是当您处理外壳程序时,主要功能应该保留在外壳程序中。

答案 2 :(得分:1)

您无法将Bash脚本的源代码转化为Perl脚本。 bash_profile必须由执行命令的外壳提供。 Perl运行system时,每次都会派生一个新的Shell。

您必须在bash_profile中为通过system运行的每个命令提供源:

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

还有另一件事,system调用了一个非交互式外壳。因此,除非您调用以下命令,否则.bash_profile中定义的Bash别名将不起作用:

shopt -s expand_aliases

在该脚本内