我有一个很长的shell脚本。现在我的老板要求我必须用Perl重写它。 是否有办法编写Perl脚本并在其中使用现有的shell代码,类似于Inline::C? 是否有类似于Inline::Shell的东西?我看过inline模块,但它只支持语言。
我有一个很长的shell脚本。现在我的老板要求我必须用Perl重写它。 是否有办法编写Perl脚本并在其中使用现有的shell代码,类似于Inline::C? 是否有类似于Inline::Shell的东西?我看过inline模块,但它只支持语言。
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
#other libraries
(rest of the code)
open THIS, "cat $ARGV[0] |";
$ARGV[0]
"传递参数,这将是shell中的$1——传递给它的第一个参数。其结果将通过"THIS"管道传输到您的Perl脚本中,您可以使用它来从中读取,稍后我会展示如何操作。open THIS, "-|", "cat $ARGV[0]";
>,>>,<,+>,+<
)。虽然还有更多关于打开文件的内容,但对于大多数事情来说,这应该足够了。open THIS, "$ARGV[0]";
来实现,从而获得更好的性能。my $filename = "whatever";
open FILE, "$filename" or die "Could not open $filename!\n";
while(<FILE>) {
print $_;
}
close FILE;
这将打开一个文件,并打印出它的所有内容("while(<FILE>)
" 会循环直到文件结尾,将每一行赋值给 "$_
"),然后再关闭文件。
如果我想将输出指向另一个文件,可以这样做:
my $filename = "whatever";
my $anotherfile = "another";
open (FILE, "$filename") || die "Could not open $filename!\n";
open OUT, ">", "$anotherfile" or die "Could not open $anotherfile for writing!\n";
while(<FILE>) {
print OUT $_;
}
close FILE;
OUT
"指示的文件中。您还可以在适当的位置使用STDIN
、STDOUT
和STDERR
,而无需先打开它们。实际上,"print
"默认为STDOUT
,"die
"默认为"STDERR
"。or die ...
"和"|| die ...
"。运算符or
和||
表示仅在第一个命令返回false(即空字符串、null引用、0等)时才执行以下命令。die命令会停止带有错误消息的脚本。or
"和"||
"的主要区别是优先级。如果在上面的示例中将"or
"替换为"||
",则不会按预期工作,因为该行将被解释为:open FILE, ("$filename" || die "Could not open $filename!\n");
这与预期的完全不同。由于"or
"的优先级较低,因此它起作用了。在使用"||
"的那一行中,将open
的参数放在括号中,使得可以使用"||
"。
可惜,有一些与cat
非常相似的东西:
while(<>) {
print $_;
}
这将打印出命令行中的所有文件或通过STDIN传递的任何内容。
GREP
那么,我们的“grep”脚本如何工作呢?我会假设使用“grep -E”,因为在Perl中比简单的grep更容易。无论如何:
my $pattern = $ARGV[0];
shift @ARGV;
while(<>) {
print $_ if /$pattern/o;
}
在 $pattern 中传递的 "o" 指示 Perl 仅编译该模式一次,从而提高速度。不要使用 "something if cond" 风格。它意味着只有当条件为真时才会执行 "something"。最后,"/$pattern/
" 单独使用与 "$_ =~ m/$pattern/
" 相同,这意味着将 $_ 与指定的正则表达式模式进行比较。如果您想要标准的 grep
行为,即仅进行子字符串匹配,可以编写以下内容:
print $_ if $_ =~ "$pattern";
剪切
通常,使用正则表达式组来获取精确字符串比使用"cut"命令更好。例如,你可以使用"sed"命令。无论如何,下面有两种重现"cut"命令的方法:
while(<>) {
my @array = split ",";
print $array[3], "\n";
}
@array
和$array[3]
。 @
标记意味着“array”应该被视为数组。它将接收由当前处理行中的每一列组成的数组。接下来,$
标记意味着array[3]
是一个标量值。它将返回您要求的列。while(<>) {
my ($column) = /^(?:[^,]*,){3}([^,]*),/;
print $column, "\n";
}
这利用正则表达式获取所需信息,仅限于此。
如果你想要位置列,可以使用:
while(<>) {
print substr($_, 5, 10), "\n";
}
my $printlines = abs(shift);
my $lines = 0;
my $current;
while(<>) {
if($ARGV ne $current) {
$lines = 0;
$current = $ARGV;
}
print "$_" if $lines < $printlines;
$lines++;
}
需要注意的是,我使用"ne"来比较字符串。现在,$ARGV将始终指向正在读取的当前文件,因此我跟踪它们以便在读取新文件时重新开始计数。还要注意传统的"if"语法,以及后缀形式。
我还使用了一种简化的语法来获取要打印的行数。当你单独使用"shift"时,它会假定"shift @ARGV"。此外,请注意,除了修改@ARGV之外,shift还会返回被移出的元素。
与shell一样,数字和字符串没有区别--你只需使用它。即使像"2"+"2"这样的东西也可以工作。事实上,Perl甚至更加宽容,愉快地将任何非数字视为0,所以你可能要小心。
然而,这个脚本非常低效,因为它读取了所有文件,而不仅仅是所需的行。让我们改进它,并在过程中看到一些重要的关键字:
my $printlines = abs(shift);
my @files;
if(scalar(@ARGV) == 0) {
@files = ("-");
} else {
@files = @ARGV;
}
for my $file (@files) {
next unless -f $file && -r $file;
open FILE, "<", $file or next;
my $lines = 0;
while(<FILE>) {
last if $lines == $printlines;
print "$_";
$lines++;
}
close FILE;
}
my $skiplines = abs(shift);
my @lines;
my $current = "";
while(<>) {
if($ARGV ne $current) {
print @lines;
undef @lines;
$current = $ARGV;
}
push @lines, $_;
shift @lines if $#lines == $skiplines;
}
print @lines;
好的,我将"push"(将值追加到数组)和"shift"(从数组开头获取某个元素)结合起来。如果你需要一个栈,可以使用push/pop或shift/unshift。混合使用它们,你就得到了一个队列。我用$#lines
保留了最多10个元素的队列,它会给我数组中最后一个元素的索引。你也可以使用scalar(@lines)
获取@lines
中的元素数量。
UNIQ
现在,uniq只能消除连续重复的行,使用之前所见的应该很容易。因此,我将消除它们所有:
my $current = "";
my %lines;
while(<>) {
if($ARGV ne $current) {
undef %lines;
$current = $ARGV;
}
print $_ unless defined($lines{$_});
$lines{$_} = "";
}
在这里,我将整个文件存储在内存中的%lines
中。符号%
表明这是一个哈希表。我使用文本行作为键,并且不存储任何值 - 因为我对值没有兴趣。我使用"defined($lines{$_})"检查键是否存在,这将测试与该键相关联的值是否已定义;关键字"unless"的作用与"if"相同,但具有相反的效果,因此它仅在该行未定义时才打印该行。
请注意,$lines{$_} = ""
的语法用于在哈希表中存储某些内容。请注意使用{}
表示哈希表,而不是使用[]
表示数组。
WC
实际上,这将使用我们已经学过的很多东西:
my $current;
my %lines;
my %words;
my %chars;
while(<>) {
$lines{"$ARGV"}++;
$chars{"$ARGV"} += length($_);
$words{"$ARGV"} += scalar(grep {$_ ne ""} split /\s/);
}
for my $file (keys %lines) {
print "$lines{$file} $words{$file} $chars{$file} $file\n";
}
有三个新的东西,其中两个是"+="运算符,应该很明显,另一个是"for"表达式。基本上,"for"会将数组的每个元素分配给指定的变量。 "my"用于声明变量,但如果之前已经声明,则不需要使用它。我可以在那些括号中使用@array变量。"keys %lines"表达式将作为数组返回哈希表"%lines"中存在的键(文件名)。其余部分应该很明显。
第三件事,实际上是在修改答案时添加的"grep"。格式如下:
grep { code } array
my @lines;
my $current = "";
while(<>) {
if($ARGV ne $current) {
print sort @lines;
undef @lines;
$current = $ARGV;
}
push @lines, $_;
}
print sort @lines;
在这里,“sort”将对数组进行排序。请注意,sort可以接收一个函数来定义排序标准。例如,如果我想对数字进行排序,则可以执行以下操作:
my @lines;
my $current = "";
while(<>) {
if($ARGV ne $current) {
print sort @lines;
undef @lines;
$current = $ARGV;
}
push @lines, $_;
}
print sort {$a <=> $b} @lines;
$a
”和“$b
”接收要比较的元素。“<=>
”根据数字是否小于、等于或大于另一个数字返回-1、0或1。对于字符串,“cmp”执行相同的操作。for my $file (@ARGV) {
print "$file is a file\n" if -f "$file";
print "$file is a directory\n" if -d "$file";
print "I can read $file\n" if -r "$file";
print "I can write to $file\n" if -w "$file";
}
我这里不想详尽列举,还有许多其他类似的测试。我也可以执行“glob”模式,就像shell中的“*”和“?”一样,例如:
for my $file (glob("*")) {
print $file;
print "*" if -x "$file" && ! -d "$file";
print "/" if -d "$file";
print "\t";
}
sub list_dir($$) {
my ($dir, $prefix) = @_;
my $newprefix = $prefix;
if ($prefix eq "") {
$newprefix = $dir;
} else {
$newprefix .= "/$dir";
}
chdir $dir;
for my $file (glob("*")) {
print "$prefix/" if $prefix ne "";
print "$dir/$file\n";
list_dir($file, $newprefix) if -d "$file";
}
chdir "..";
}
list_dir(".", "");
在这里,我们终于看到了一个函数。函数的声明语法如下:
sub name (params) { code }
严格来说,"(params)"是可选的。我使用的声明参数"($$)
"表示我接收到了两个标量参数。我也可以在其中加入"@
"或"%
"。数组"@_
"包含了所有传递的参数。行"my ($dir, $prefix) = @_
"只是一种将该数组的前两个元素分配给变量$dir
和$prefix
的简单方法。
这个函数不返回任何东西(实际上它是一个过程),但你可以通过添加"return something;
"使它返回"value",从而使它返回值。
其余的应该很明显了。
混合运用
现在我将展示一个更复杂的例子。我将展示一些糟糕的代码来解释其中的问题,然后展示更好的代码。
对于这个第一个例子,我有两个文件,names.txt文件,里面有名称和电话号码,system.txt文件,里面有系统和负责人的姓名。它们是:
names.txt
John Doe, (555) 1234-4321
Jane Doe, (555) 5555-5555
The Boss, (666) 5555-5555
systems.txt
Sales, Jane Doe
Inventory, John Doe
Payment, That Guy
#!/usr/bin/perl
use strict;
use warnings;
open FILE, "names.txt";
while(<FILE>) {
my ($name) = /^([^,]*),/;
my $system = get_system($name);
print $_ . ", $system\n";
}
close FILE;
sub get_system($) {
my ($name) = @_;
my $system = "";
open FILE, "systems.txt";
while(<FILE>) {
next unless /$name/o;
($system) = /([^,]*)/;
}
close FILE;
return $system;
}
然而,这段代码不起作用。Perl会抱怨函数被使用得太早以至于无法检查原型,但那只是一个警告。它会在第8行(第一个while循环)报错,抱怨文件句柄已关闭的读取操作。这里发生的情况是"FILE
"是全局变量,所以get_system
函数正在改变它。让我们重写它,修复两个问题:
#!/usr/bin/perl
use strict;
use warnings;
sub get_system($) {
my ($name) = @_;
my $system = "";
open my $filehandle, "systems.txt";
while(<$filehandle>) {
next unless /$name/o;
($system) = /([^,]*)/;
}
close $filehandle;
return $system;
}
open FILE, "names.txt";
while(<FILE>) {
my ($name) = /^([^,]*),/;
my $system = get_system($name);
print $_ . ", $system\n";
}
close FILE;
get_system
后我们对"$_
"进行了引用,但是通过读取文件,get_system
正在覆盖$_
的值!get_system
内部的$_
本地化。这将给它一个局部范围,一旦从get_system
返回,原始值就会被恢复:#!/usr/bin/perl
use strict;
use warnings;
sub get_system($) {
my ($name) = @_;
my $system = "";
local $_;
open my $filehandle, "systems.txt";
while(<$filehandle>) {
next unless /$name/o;
($system) = /([^,]*)/;
}
close $filehandle;
return $system;
}
open FILE, "names.txt";
while(<FILE>) {
my ($name) = /^([^,]*),/;
my $system = get_system($name);
print $_ . ", $system\n";
}
close FILE;
但这仍然不起作用!它在名称和系统之间打印一个换行符。好吧,Perl读取包括可能有的任何换行符的行。有一个很好的命令可以从字符串中删除换行符,"chomp
",我们将使用它来解决这个问题。由于并非每个名称都有一个系统,当发生这种情况时,我们也可以避免打印逗号:
#!/usr/bin/perl
use strict;
use warnings;
sub get_system($) {
my ($name) = @_;
my $system = "";
local $_;
open my $filehandle, "systems.txt";
while(<$filehandle>) {
next unless /$name/o;
($system) = /([^,]*)/;
}
close $filehandle;
return $system;
}
open FILE, "names.txt";
while(<FILE>) {
my ($name) = /^([^,]*),/;
my $system = get_system($name);
chomp;
print $_;
print ", $system" if $system ne "";
print "\n";
}
close FILE;
这个代码可以工作,但效率极低。我们为了读取名字文件中的每一行,都要读取整个系统文件。为了避免这种情况,我们将先读取系统文件中所有的数据,然后再使用这些数据处理名字文件。
有时候一个文件非常大,无法将其全部读入内存。如果遇到这种情况,你应该尝试将任何需要处理的其他文件读入内存,以便每个文件只需进行一次单独的通行。以下是第一个优化版本的代码:
#!/usr/bin/perl
use strict;
use warnings;
our %systems;
open SYSTEMS, "systems.txt";
while(<SYSTEMS>) {
my ($system, $name) = /([^,]*),(.*)/;
$systems{$name} = $system;
}
close SYSTEMS;
open NAMES, "names.txt";
while(<NAMES>) {
my ($name) = /^([^,]*),/;
chomp;
print $_;
print ", $systems{$name}" if defined $systems{$name};
print "\n";
}
close NAMES;
很遗憾,它不起作用。 没有系统出现! 发生了什么? 好吧,让我们通过使用 Data::Dumper
来查看 "%systems
" 包含的内容:
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
our %systems;
open SYSTEMS, "systems.txt";
while(<SYSTEMS>) {
my ($system, $name) = /([^,]*),(.*)/;
$systems{$name} = $system;
}
close SYSTEMS;
print Dumper(%systems);
open NAMES, "names.txt";
while(<NAMES>) {
my ($name) = /^([^,]*),/;
chomp;
print $_;
print ", $systems{$name}" if defined $systems{$name};
print "\n";
}
close NAMES;
$VAR1 = ' Jane Doe';
$VAR2 = 'Sales';
$VAR3 = ' That Guy';
$VAR4 = 'Payment';
$VAR5 = ' John Doe';
$VAR6 = 'Inventory';
John Doe, (555) 1234-4321
Jane Doe, (555) 5555-5555
The Boss, (666) 5555-5555
$VAR1/$VAR2/etc
是Dumper
显示哈希表的方式。奇数是键,相邻的偶数是值。现在我们可以看到%systems
中每个名称前都有一个空格!这是愚蠢的正则表达式错误,让我们来修复它:
#!/usr/bin/perl
use strict;
use warnings;
our %systems;
open SYSTEMS, "systems.txt";
while(<SYSTEMS>) {
my ($system, $name) = /^\s*([^,]*?)\s*,\s*(.*?)\s*$/;
$systems{$name} = $system;
}
close SYSTEMS;
open NAMES, "names.txt";
while(<NAMES>) {
my ($name) = /^\s*([^,]*?)\s*,/;
chomp;
print $_;
print ", $systems{$name}" if defined $systems{$name};
print "\n";
}
close NAMES;
因此,在这里,我们从名称和系统的开头或结尾积极地删除任何空格。有其他形成该正则表达式的方法,但那不是重点。这个脚本仍然存在一个问题,如果您的“names.txt”和/或“systems.txt”文件末尾有空行,那么您将会看到以下警告:
Use of uninitialized value in hash element at ./exemplo3e.pl line 10, <SYSTEMS> line 4.
Use of uninitialized value in hash element at ./exemplo3e.pl line 10, <SYSTEMS> line 4.
John Doe, (555) 1234-4321, Inventory
Jane Doe, (555) 5555-5555, Sales
The Boss, (666) 5555-5555
Use of uninitialized value in hash element at ./exemplo3e.pl line 19, <NAMES> line 4.
$name
”变量中。有许多解决方法,但我选择以下方法:#!/usr/bin/perl
use strict;
use warnings;
our %systems;
open SYSTEMS, "systems.txt" or die "Could not open systems.txt!";
while(<SYSTEMS>) {
my ($system, $name) = /^\s*([^,]+?)\s*,\s*(.+?)\s*$/;
$systems{$name} = $system if defined $name;
}
close SYSTEMS;
open NAMES, "names.txt" or die "Could not open names.txt!";
while(<NAMES>) {
my ($name) = /^\s*([^,]+?)\s*,/;
chomp;
print $_;
print ", $systems{$name}" if defined($name) && defined($systems{$name});
print "\n";
}
close NAMES;
open()
函数的三个参数形式。http://perldoc.perl.org/functions/open.html - Brad Gilbert我很惊讶还没有人提到在核心Perl中包含的Shell模块,它可以让你使用函数调用语法来执行外部命令。例如(改编自概要):
use Shell qw(cat ps cp);
$passwd = cat '</etc/passwd';
@pslines = ps '-ww';
cp "/etc/passwd", "/tmp/passwd";
只要使用括号,你甚至可以在use
行中没有提到的$PATH
中调用其他程序,例如:
gcc('-o', 'foo', 'foo.c');
Shell
会将子进程的标准输出收集并作为字符串或数组返回。这简化了脚本编写,但不是最有效的方法,并且可能会在您依赖于命令的未缓冲输出时出现问题。cd
)。实际上,他们建议不要在生产系统中使用该模块!但它肯定可以成为一个有用的支撑,直到您将代码移植到“正确”的Perl上。system
。如果您有自定义函数要公开给Perl使用,那么就没有办法了。但是,您可以在与运行Perl程序相同的环境中运行短小的shell代码段。您还可以逐步用Perl替换shell脚本的部分内容。开始编写一个模块来复制shell脚本功能,并将Perly位插入shell脚本中,直到最终大部分都是Perl。system
中。我认为学习Perl并尝试使用Perl而不是shell是为了更大的利益。我曾经借助于Notepad++的“替换”功能进行过一次转换。
然而,当我试图创建一个可以执行shell脚本的Perl包装器时,我遇到了与最初提出的问题类似的问题。
针对我的情况,我编写了下面的代码,它能够正常工作。
希望这能有所帮助。
#!perl
use strict;
use Data::Dumper;
use Cwd;
#Variables read from shell
our %VAR;
open SH, "<$ARGV[0]" or die "Error while trying to read $ARGV[0] ($!)\n";
my @SH=<SH>;
close SH;
sh2perl(@SH);
#Subroutine to execute shell from Perl (read from array)
sub sh2perl {
#Variables
my %case; #To store data from conditional block of "case"
my %if; #To store data from conditional block of "if"
foreach my $line (@_) {
#Remove blanks at the beginning and EOL character
$line=~s/^\s*//;
chomp $line;
#Comments and blank lines
if ($line=~/^(#.*|\s*)$/) {
#Do nothing
}
#Conditional block - Case
elsif ($line=~/case.*in/..$line=~/esac/) {
if ($line=~/case\s*(.*?)\s*\in/) {
$case{'var'}=transform($1);
} elsif ($line=~/esac/) {
delete $case{'curr_pattern'};
#Run conditional block
my $case;
map { $case=$_ if $case{'var'}=~/$_/ } @{$case{'list_patterns'}};
$case ? sh2perl(@{$case{'patterns'}->{$case}}) : sh2perl(@{$case{'patterns'}->{"*"}});
} elsif ($line=~/^\s*(.*?)\s*\)/) {
$case{'curr_pattern'}=$1;
push(@{$case{'list_patterns'}}, $case{'curr_pattern'}) unless ($line=~m%\*\)%)
} else {
push(@{$case{'patterns'}->{ $case{'curr_pattern'} }}, $line);
}
}
#Conditional block - if
elsif ($line=~/^if/..$line=~/^fi/) {
if ($line=~/if\s*\[\s*(.*\S)\s*\];/) {
$if{'condition'}=transform($1);
$if{'curr_cond'}="TRUE";
} elsif ($line=~/fi/) {
delete $if{'curr_cond'};
#Run conditional block
$if{'condition'} ? sh2perl(@{$if{'TRUE'}}) : sh2perl(@{$if{'FALSE'}});
} elsif ($line=~/^else/) {
$if{'curr_cond'}="FALSE";
} else {
push(@{$if{ $if{'curr_cond'} }}, $line);
}
}
#echo
elsif($line=~/^echo\s+"?(.*?[^"])"?\s*$/) {
my $str=$1;
#echo with redirection
if ($str=~m%[>\|]%) {
eval { system(transform($line)) };
if ($@) { warn "Error while evaluating $line: $@\n"; }
#print new line
} elsif ($line=~/^echo ""$/) {
print "\n";
#default
} else {
print transform($str),"\n";
}
}
#cd
elsif($line=~/^\s*cd\s+(.*)/) {
chdir $1;
}
#export
elsif($line=~/^export\s+((\w+).*)/) {
my ($var,$exported)=($2,$1);
if ($exported=~/^(\w+)\s*=\s*(.*)/) {
while($exported=~/(\w+)\s*=\s*"?(.*?\S)"?\s*(;(?:\s*export\s+)?|$)/g) { $VAR{$1}=transform($2); }
}
# export($var,$VAR{$var});
$ENV{$var}=$VAR{$var};
print "Exported variable $var = $VAR{$var}\n";
}
#Variable assignment
elsif ($line=~/^(\w+)\s*=\s*(.*)$/) {
$1 eq "" or $VAR{$1}=""; #Empty variable
while($line=~/(\w+)\s*=\s*"?(.*?\S)"?\s*(;|$)/g) {
$VAR{$1}=transform($2);
}
}
#Source
elsif ($line=~/^source\s*(.*\.sh)/) {
open SOURCE, "<$1" or die "Error while trying to open $1 ($!)\n";
my @SOURCE=<SOURCE>;
close SOURCE;
sh2perl(@SOURCE);
}
#Default (assuming running command)
else {
eval { map { system(transform($_)) } split(";",$line); };
if ($@) { warn "Error while doing system on \"$line\": $@\n"; }
}
}
}
sub transform {
my $src=$_[0];
#Variables $1 and similar
$src=~s/\$(\d+)/$ARGV[$1-1]/ge;
#Commands stored in variables "$(<cmd>)"
eval {
while ($src=~m%\$\((.*)\)%g) {
my ($cmd,$new_cmd)=($1,$1);
my $curr_dir=getcwd;
$new_cmd=~s/pwd/echo $curr_dir/g;
$src=~s%\$\($cmd\)%`$new_cmd`%e;
chomp $src;
}
};
if ($@) { warn "Wrong assessment for variable $_[0]:\n=> $@\n"; return "ERROR"; }
#Other variables
$src=~s/\$(\w+)/$VAR{$1}/g;
#Backsticks
$src=~s/`(.*)`/`$1`/e;
#Conditions
$src=~s/"(.*?)"\s*==\s*"(.*?)"/"$1" eq "$2" ? 1 : 0/e;
$src=~s/"(.*?)"\s*!=\s*"(.*?)"/"$1" ne "$2" ? 1 : 0/e;
$src=~s/(\S+)\s*==\s*(\S+)/$1 == $2 ? 1 : 0/e;
$src=~s/(\S+)\s*!=\s*(\S+)/$1 != $2 ? 1 : 0/e;
#Return Result
return $src;
}
if
... fi
,以及包含多个反引号对的命令(从第一个 ``` 到最后一个都将被传递给 shell)。我怀疑在引用方面存在无数差异,尽管我还没有检查过。 - j_random_hacker#!/bin/bash
假设bash已安装在该位置,perl
将自动调用bash解释器来运行它。
编辑:或者操作系统会拦截调用并阻止其到达Perl。我很难找到有关实际工作原理的文档。欢迎对文档进行评论。