[英]How do you load bash_profile for bash commands run from perl script?
我编写了简单的命令,使我可以运行终端历史记录中的最后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
命令别名,但是没有效果。 不过,我不确定我是否做得正确。
系统派生一个进程,该进程在其中运行非登录且非交互的外壳程序; 因此无需进行初始化,也不会获得别名。 还要注意,使用的外壳程序是/bin/sh
,通常是到另一个外壳程序的链接。 这通常是bash
但并非总是如此,因此请显式运行bash
。
为了避免这种情况,您需要使用别名来获取文件,但是正如bash
手册页所述
如果外壳不是交互式的,则别名不会扩展,除非使用shopt设置expand_aliases shell选项(请参阅下面的SHELL BUILTIN COMMANDS下的shopt说明)。
因此,如上所述,您需要shopt -s expand_aliases
。 但是还有另外一个螺丝:在相同的物理线路上别名还不可用。 因此它不会像一线式那样工作。
我还建议将别名放在.bashrc
或源的单独文件中。
解决方案
将shopt -s expand_aliases
添加到您的~/.bashrc
,并在定义别名 (或带有别名的文件)之前,将bash
作为登录shell运行
system('/bin/bash', '-cl', 'source ~/.bashrc; command');
-l
是--login
缩写。
在我的测试中,不需要source ~/.bashrc
。 但是,手册页说
当bash作为交互式登录shell或使用--login选项作为非交互式shell调用时,它首先从文件
/etc/profile
(如果存在)读取并执行命令。 读取该文件后,它将~/.bash_login
顺序查找~/.bash_profile
,~/.bash_login
和~/.profile
,并从存在且可读的第一个命令中读取并执行命令。
并指定当运行非登录的交互式列表时读取~/.bashrc
。 因此,我添加了显式采购。
在我的测试中,在未作为登录shell运行的情况下采购.bashrc
(添加了shopt
)无法正常工作,我不确定为什么。
这有点笨拙。 同样,初始化可能不希望从脚本运行。
源~/.bashrc
并发出shopt
命令, 然后在命令前换行
system('/bin/bash', '-c', 'source ~/.bashrc; shopt -s expand_aliases\\ncommand');
真。 有用。
最后,这是否必要? 它要求麻烦,并且可能有更好的设计。
其他的建议
反引号( qx )是上下文感知的。 如果在列表上下文中使用它(例如,将其返回值分配给数组),则该命令的输出将作为行列表返回。 当你使用它作为参数split
,然后是在标量上下文所以它返回一个字符串中的所有输出。 只需split
my @last_commands = `bash -ic 'set -o history; history $lines`;
在这里我还使用history N
来获取最后N
行。 在这种情况下,换行符将保留。
history N
返回最后N
历史的线,所以没有必要管last
可以在不更改原始map
情况下完成map
则表达式替换
map { s/^\\s+\\d+\\s+//r } @last_commands;
使用/r
修饰符, s///
运算符将返回新字符串,而不更改原始字符串。
无需在最后一个grep
显式使用$_
,也无需在正则表达式中使用括号
grep { not /^r |^history |^rm ?/ } ...
要么
grep { not /^(?:r|history|rm)[ ]?/ } ...
现在需要使用括号,但这只是为了对?:
进行分组,因此它无法捕获匹配项。 我用[ ]
强调有一个预期的空间; 这是没有必要的。
我还加了?
由于history
(和r
?)可能没有空间,因此使空间为可选。
正确的解决方案是让您的Perl脚本仅打印命令,并使当前的交互式shell eval
从历史记录中打印的字符串。 (我可能会完全摆脱Perl,但这不在这里。)
如果在当前Shell中对命令进行了评估,则可以避免许多上下文问题,这些问题对于system()
或通常涉及新进程的任何事情都是非常困难甚至难以解决的。 例如,子流程无法访问当前外壳程序中的非导出变量。 var="foo", echo "$var"; r 1
用您当前的方法很难正确解决var="foo", echo "$var"; r 1
。 使用当前的交互式外壳还将自然而轻松地解决您试图获得非交互式子外壳(如交互式外壳)所遇到的问题。
别名还是很烂,所以让我们将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)'
}
这样,您还可以摆脱r
定义中的history -w
。
注意在有用的地方我们如何使用Perl。 但是在处理外壳程序时,主要功能应该保留在外壳程序中。
您无法将Bash脚本的源代码转化为Perl脚本。 bash_profile必须由执行命令的外壳提供。 当Perl运行system
,它每次都会派生一个新的shell。
您必须在bash_profile中获取通过system
运行的每个命令的来源:
system('source ~/.bash_profile; ' + $cmd);
还有一件事, system
调用了非交互式外壳。 因此,除非您调用以下命令,否则.bash_profile中定义的Bash别名将不起作用:
shopt -s expand_aliases
在该脚本中
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.