我写了一个简单的命令,使我可以运行终端历史记录中的最后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
命令别名中,但是没有效果。不过,我不确定我是否做得正确。
答案 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
在该脚本内