如何将 shell 脚本翻译成 Perl?
Posted
技术标签:
【中文标题】如何将 shell 脚本翻译成 Perl?【英文标题】:How can I translate a shell script to Perl? 【发布时间】:2010-11-12 06:00:48 【问题描述】:我有一个 shell 脚本,相当大。现在我的老板说我必须用 Perl 重写它。 有什么方法可以编写 Perl 脚本并使用我的 Perl 脚本中的现有 shell 代码。类似于Inline::C。
有类似 Inline::Shell 的东西吗?我看过内联模块,但它只支持语言。
【问题讨论】:
使用假设的 inline::bash 会不会错过重写它的意义?我能想到进行这种重写的唯一原因是努力使组织中的语言标准化。在 bash 脚本周围有一个小的 Perl 包装器根本无法实现这一点。 我的意思是重用现有的 shell 代码,并将一些新的 perl 代码添加到我新迁移的 perl 脚本中。 我赞同大卫的观点:不要与 Perl 对抗,使用它!开始转换你的脚本,它可能不是那么难......然后将结果与你的 bash-script 进行比较。 问题归结为:我的老板让我用 Perl 重写一个 bash 脚本;如果不使用 Perl,我怎么能做到这一点?这不是一个好问题。 他并没有试图避开 Perl。他试图重用任何可能已经存在的工作来开始。 【参考方案1】:您可以使用以下命令开始您的“Perl”脚本:
#!/bin/bash
然后,假设 bash 安装在该位置,perl
将自动调用 bash 解释器来运行它。
编辑: 或者操作系统会拦截调用并阻止它进入 Perl。我发现很难找到有关其实际工作原理的文档。欢迎对文档发表评论。
【讨论】:
那不叫 Perl 脚本。 She-bang 处理不取决于 perl 解释器,而是取决于内核。 是的.. which 命令读取第一行脚本以查看它是什么类型的脚本..因此使用 she bang 脚本 bin 位置.. 真的吗?即使您使用以下命令调用脚本: perl myscript.sh? @David:不。如果你执行 perl myscript.sh 那么接下来的内容将被传递给 shebang 行中指定的解释器。 perl 读取 shebang 并执行其中的任何内容。试试看。【参考方案2】:我同意学习 Perl 并尝试编写 Perl 而不是 shell 是为了更大的利益。我借助记事本++的“替换”功能进行了一次传输。
但是,当我尝试围绕 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;
【讨论】:
努力+1!但是从快速浏览中我发现了很多问题:它会错误地解析嵌套块命令,如if
... fi
,以及包含多于一对反引号的命令(从第一个 `
到最后一个将交给外壳)。我怀疑引用有很多不同,尽管我没有检查过。【参考方案3】:
我会认真回答。我不知道有任何程序可以将 shell 脚本翻译成 Perl,而且我怀疑任何解释器模块都会提供性能优势。所以我将概述我将如何去做。
现在,您希望尽可能多地重用您的代码。在这种情况下,我建议选择该代码的片段,编写该代码的 Perl 版本,然后从主脚本调用 Perl 脚本。这将使您能够以小步骤进行转换,断言转换后的部分正在工作,并逐渐提高您的 Perl 知识。
由于您可以从 Perl 脚本调用外部程序,因此您甚至可以用 Perl 替换一些更大的逻辑,并从 Perl 调用更小的 shell 脚本(或其他命令)来做一些您还不愿意转换的事情。因此,您将有一个 shell 脚本调用一个 perl 脚本调用另一个 shell 脚本。而且,事实上,我用我自己的第一个 Perl 脚本就是这样做的。
当然,选择好要转换的内容很重要。我将在下面解释 shell 脚本中常见的模式有多少是用 Perl 编写的,以便您可以在脚本中识别它们,并通过尽可能多的剪切和粘贴来创建替换。
首先,Perl 脚本和 Shell 脚本都是代码+函数。即,任何不是函数声明的东西都按照遇到的顺序执行。不过,您不需要在使用前声明函数。这意味着可以保留脚本的总体布局,尽管将内容保存在内存中(如整个文件或它的处理形式)的能力可以简化任务。
一个 Perl 脚本,在 Unix 中,开头是这样的:
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
#other libraries
(rest of the code)
显然,第一行指向用于运行脚本的命令,就像普通的 shell 一样。以下两个“使用”行使语言更加严格,这应该会减少您遇到的错误数量,因为您不太了解该语言(或明显做错了什么)。第三个 use 行导入“Data”模块的“Dumper”函数。它对于调试目的很有用。如果您想知道数组或哈希表的值,只需打印 Dumper(whatever)。
还要注意,cmets 就像 shell 一样,行以“#”开头。
现在,您调用外部程序并通过管道传输或从它们传输。例如:
open THIS, "cat $ARGV[0] |";
这将运行 cat,传递“$ARGV[0]
”,这将是 shell 上的 $1——传递给它的第一个参数。其结果将通过“THIS”传送到您的 Perl 脚本中,您可以使用它从中读取结果,稍后我将展示。
您可以使用“|”在行首或行尾,表示模式“pipe to”或“pipe from”,并指定要运行的命令,也可以在开头使用“>”或“>>”,打开一个用于写入的文件,带或不带截断,“”用于读取和写入。请注意,后者将首先截断文件。
“open”的另一种语法可以避免文件名称中包含此类字符的问题,它将打开模式作为第二个参数:
open THIS, "-|", "cat $ARGV[0]";
这将做同样的事情。模式“-|”代表“管道来自”,“|-”代表“管道到”。其余模式可以照原样使用 (>, >>, <, +>, +<
)。虽然还有更多要打开的内容,但对于大多数事情来说应该足够了。
但您应该尽可能避免调用外部程序。你可以直接打开文件,比如open THIS, "$ARGV[0]";
,性能会更好。
那么,您可以删除哪些外部程序?好吧,几乎所有东西。但让我们保持基础:cat、grep、cut、head、tail、uniq、wc、sort。
猫
嗯,关于这个,没什么好说的。请记住,如果可能的话,只读取一次文件并将其保存在内存中。如果文件很大,当然不会这样做,但几乎总有办法避免多次读取文件。
无论如何,cat 的基本语法是:
my $filename = "whatever";
open FILE, "$filename" or die "Could not open $filename!\n";
while(<FILE>)
print $_;
close FILE;
这会打开一个文件,并打印它的所有内容(“while(<FILE>)
”将循环到 EOF,将每一行分配给“$_
”),然后再次关闭它。
如果我想将输出定向到另一个文件,我可以这样做:
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(即空字符串、空引用、0 等)时才会执行以下命令。 die 命令停止脚本并显示错误消息。
“or
”和“||
”之间的主要区别在于优先级。如果在上面的示例中将“or
”替换为“||
”,它将无法按预期工作,因为该行将被解释为:
open FILE, ("$filename" || die "Could not open $filename!\n");
这根本不是预期的。由于“or
”的优先级较低,因此可以正常工作。在使用“||
”的行中,open
的参数在括号之间传递,从而可以使用“||
”。
唉,有些东西和猫差不多:
while(<>)
print $_;
这将打印命令行中的所有文件,或通过 STDIN 传递的任何内容。
GREP
那么,我们的“grep”脚本将如何工作?我将假设“grep -E”,因为在 Perl 中这比简单的 grep 更容易。无论如何:
my $pattern = $ARGV[0];
shift @ARGV;
while(<>)
print $_ if /$pattern/o;
传递给 $patttern 的 "o" 指示 Perl 只编译该模式一次,从而加快速度。不是“条件允许的东西”的风格。这意味着它只会在条件为真时执行“某事”。最后,单独的“/$pattern/
”与“$_ =~ m/$pattern/
”相同,这意味着将 $_ 与指示的正则表达式模式进行比较。如果你想要标准的grep
行为,即只是子串匹配,你可以这样写:
print $_ if $_ =~ "$pattern";
剪切
通常,您最好使用正则表达式组来获得确切的字符串而不是剪切。例如,你会用“sed”做什么。无论如何,这里有两种复制剪切的方法:
while(<>)
my @array = split ",";
print $array[3], "\n";
这将为您提供每行的第四列,使用“,”作为分隔符。注意@array
和$array[3]
。 @
sigil 意味着“数组”应该被视为一个数组。它将接收由当前处理行中的每一列组成的数组。接下来,$
印记意味着array[3]
是一个标量值。它将返回您要求的 列。
不过,这不是一个好的实现,因为“split”会扫描整个字符串。我曾经通过不使用 split 将一个过程从 30 分钟缩短到 2 秒——不过,这些行相当大。无论如何,如果预期行很大,而您想要的列很低,则以下内容具有优越的性能:
while(<>)
my ($column) = /^(?:[^,]*,)3([^,]*),/;
print $column, "\n";
这利用正则表达式来获取所需的信息,而且仅此而已。
如果你想要位置列,你可以使用:
while(<>)
print substr($_, 5, 10), "\n";
将从第六个字符开始打印 10 个字符(同样,0 表示第一个字符)。
头部
这个很简单:
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;
关键字“next”和“last”非常有用。首先,"next" 将告诉 Perl 回到循环条件,如果适用则获取下一个元素。在这里,我们使用它来跳过文件,除非它确实是文件(不是目录)并且可读。如果我们无法打开文件,它也会跳过。
然后“last”用于立即跳出循环。一旦达到所需的行数,我们就使用它来停止读取文件。确实我们读了一行太多,但是在那个位置有“last”清楚地表明它后面的行不会被执行。
还有“重做”,它会回到循环的开头,但不会重新评估条件,也不会获取下一个元素。
尾巴
我会在这里做一个小技巧。
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$_ = ""
作为在哈希表中存储内容的一种方式。注意 用于哈希表,而不是
[]
用于数组。
厕所
这实际上会用到很多我们见过的东西:
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”会将数组的每个元素分配给指定的变量。 “我的”是用来声明变量的,尽管如果之前声明它是不需要的。我可以在这些括号内有一个@array 变量。 "keys %lines" 表达式将返回作为哈希表 "%lines" 的键(文件名)的数组。其余的应该是显而易见的。
我实际上添加的只是修改答案的第三件事是“grep”。这里的格式是:
grep code array
它将为数组的每个元素运行“代码”,将元素作为“$_”传递。然后 grep 将返回代码计算结果为“true”的所有元素(不是 0,不是“”等)。这样可以避免计算由连续空格产生的空字符串。
与“grep”类似,还有“map”,这里我就不演示了。它不会过滤,而是返回一个由每个元素的“代码”结果形成的数组。
排序
最后,排序。这个也很简单:
my @lines;
my $current = "";
while(<>)
if($ARGV ne $current)
print sort @lines;
undef @lines;
$current = $ARGV;
push @lines, $_;
print sort @lines;
在这里,“排序”将对数组进行排序。注意 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";
如果你把它和“chdir”结合起来,你也可以模拟“find”:
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;
”,并让它返回“某物”。
其余的应该很明显了。
混合一切
现在我将展示一个更复杂的示例。我会展示一些糟糕的代码来解释它有什么问题,然后再展示更好的代码。
对于第一个示例,我有两个文件,names.txt 文件,其中包含姓名和电话号码,systems.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 循环)上给出错误,抱怨关闭文件句柄上的 readline。这里发生的是“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;
这行得通,但它也恰好效率低下。我们为名称文件中的每一行读取整个系统文件。为避免这种情况,我们将从系统中读取所有数据一次,然后使用它来处理名称。
现在,有时文件太大了,您无法将其读入内存。发生这种情况时,您应该尝试将处理它所需的任何 other 文件读入内存,以便您可以一次完成每个文件的所有操作。无论如何,这是它的第一个优化版本:
#!/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;
不幸的是,它不起作用。从来没有出现过系统!发生了什么?好吧,让我们看看“%systems
”包含什么,使用Data::Dumper
:
#!/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;
现在正则表达式的名称和系统至少需要一个字符,我们在使用前测试是否定义了“$name
”。
结论
那么,这些是翻译 shell 脚本的基本工具。你可以用 Perl 做更多更多,但这不是你的问题,它也不适合这里。
正如一些重要主题的基本概述,
可能会被黑客攻击的 Perl 脚本需要使用 -T 选项运行,这样 Perl 就会抱怨任何未正确处理的易受攻击的输入。
有一些库,称为模块,用于数据库访问、XML&cia 处理、Telnet、HTTP 和其他协议。事实上,有很多模块可以在CPAN 找到。
正如其他人所说,如果您使用 AWK 或 SED,您可以使用 A2P 和 S2P 将它们翻译成 Perl。
Perl 可以以面向对象的方式编写。
Perl 有多个版本。在撰写本文时,稳定版是 5.8.8,并且有 5.10.0 可用。还有一个 Perl 6 正在开发中,但经验告诉大家不要急于等待它。
有一本关于 Perl 的免费、优秀、实用、硬而快速的书,名为 Learning Perl The Hard Way。它的风格与这个答案非常相似。从这里出发可能是个好地方。
我希望这会有所帮助。
免责声明
我不是要教 Perl,您将至少需要一些参考资料。有一些关于良好 Perl 习惯的指南,例如在脚本开头使用“use strict;
”和“use warnings;
”,以减少对糟糕代码的宽容,或者在打印行使用 STDOUT 和 STDERR,以指出正确的输出管道。
这是我同意的,但我认为它会偏离显示常见 shell 脚本实用程序模式的基本目标。
【讨论】:
我必须投赞成票,如果只是为了展示出纯粹的全面性和努力! 这是对一个不那么奇妙的问题的绝妙答案。 请尽可能使用open()
的3 个参数形式。 perldoc.perl.org/functions/open.html
您可能还想为命令/子例程提供一些链接。
@Daniel:出于同样的原因(稳健性),使用“open my $file, ...”而不是“open FILE, ...”是一个好主意,并且本地化$_ 在使用它之前。然后你的各种方便的实用程序(cat、grep 等)可以成为不践踏全局状态的子例程。【参考方案4】:
我很惊讶没有人提到核心 Perl 中包含的Shell module,它允许您使用函数调用语法执行外部命令。例如(改编自概要):
use Shell qw(cat ps cp);
$passwd = cat '</etc/passwd';
@pslines = ps '-ww';
cp "/etc/passwd", "/tmp/passwd";
如果你使用了括号,你甚至可以在$PATH
中调用你在use
行中没有提到的其他程序,例如:
gcc('-o', 'foo', 'foo.c');
请注意,Shell
收集子进程的 STDOUT 并将其作为字符串或数组返回。这简化了脚本,但它不是最有效的方法,如果您依赖未缓冲的命令输出,可能会导致麻烦。
模块文档提到了一些缺点,例如不能使用相同的语法调用 shell 内部命令(例如 cd
)。事实上,他们建议不要将该模块用于生产系统!但在您将代码移植到“正确的”Perl 之前,它肯定是一个有用的依靠。
【讨论】:
【参考方案5】:内联外壳程序称为system
。如果您有尝试向 Perl 公开的用户定义函数,那么您就不走运了。但是,您可以使用与运行 Perl 程序相同的环境来运行一小段 shell。您也可以逐渐用 Perl 替换部分 shell 脚本。开始编写一个复制 shell 脚本功能的模块,并将 Perly 位插入到 shell 脚本中,直到您最终拥有大部分 Perl。
没有 shell 到 Perl 的翻译器。有一个关于 csh-to-Perl 翻译器的长期笑话,您可以通过电子邮件将您的脚本发送给它,但这实际上只是 Tom Christtainsen 为您翻译它,以向您展示 Perl 在 90 年代初期是多么酷。 Randal Schwartz 上传了一个 sh-to-Perl 翻译器,但您必须检查上传日期:那是愚人节。他的脚本只是将所有内容都包含在system
中。
无论你做什么,都不要丢失原始的 shell 脚本。 :)
【讨论】:
【参考方案6】:我不知道你的 shell 脚本里有什么,但别忘了有类似的工具
-
a2p - awk 到 perl
s2p - sed 到 perl
也许还有更多。值得一看。
您可能会发现,由于 Perl 的强大/特性,这并不是一项艰巨的工作,因为您可能一直在使用各种 bash 特性和实用程序来做一些 Perl 原生的事情。
与任何迁移项目一样,对两种解决方案都运行一些预设回归测试很有用,所以如果您没有这些,我会先生成这些。
【讨论】:
没有。但我不敢相信这有那么难。以上是关于如何将 shell 脚本翻译成 Perl?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 shell 脚本拦截 perl 脚本的执行,然后实际执行它们?