有没有办法编写Perl脚本,并使用现有的shell代码,就像我的Perl脚本一样.类似于 Inline::C的东西.
有没有像Inline :: Shell?我看了一下内联模块,但它只支持语言.
现在,您要尽可能重用您的代码.在这种情况下,我建议选择该代码片段,编写一个Perl版本,然后从主脚本调用Perl脚本.这将使您能够以小步骤进行转换,断言转换的部分正在运行,并逐渐改进您的Perl知识.
您可以从Perl脚本调用外部程序,甚至可以用Perl替换一些更大的逻辑,并从Perl调用较小的shell脚本(或其他命令)来执行您不舒服的转换.所以你将有一个shell脚本调用一个调用另一个shell脚本的perl脚本.而实际上,我完全是用自己的第一个Perl脚本.
当然,重要的是选择什么转换.下面我将解释一下在shell脚本中常用的Perl模式,这样可以在脚本中识别它们,并通过尽可能多的剪切和粘贴来创建替换.
首先,Perl脚本和Shell脚本都是代码函数.即,不是函数声明的任何东西都是按照遇到的顺序执行的.不需要在使用前声明功能.这意味着可以保留脚本的一般布局,尽管将内容保存在内存中(如整个文件或其处理形式)的功能使得简化任务成为可能.
Unix中的Perl脚本以这样的形式开始:
#!/usr/bin/perl use strict; use warnings; use Data::Dumper; #other libraries (rest of the code)
第一行显然是指向要用于运行脚本的命令,就像正常的shell一样.以下两个“使用”行使语言更严格,这样可以减少你遇到的错误,因为你不太了解语言(或者简单的做错了).第三个使用行导入“数据”模块的“Dumper”功能.它对于调试目的很有用.如果要知道数组或哈希表的值,只需打印Dumper(无论如何).
还要注意,注释就像shell的,以“#”开头的行.
现在,您调用外部程序和管道或从中管道.例如:
open THIS,"cat $ARGV[0] |";
这将运行猫,传递“$ARGV [0]”,这将是$1在shell – 传递给它的第一个参数.其结果将通过“THIS”管道传输到您的Perl脚本中,您可以使用它来阅读,因为我将在后面显示.
你可以使用“|”在行的开始或结尾,指示模式“管道到”或“管道从”,并指定要运行的命令,您还可以使用“>”或“>>”在开始时,要打开一个文件用于写入或不截断,“<”显式地指示打开一个文件进行阅读(默认),或者“<”和“>”用于读写.请注意,稍后将截断该文件.
“open”的另一个语法,它将避免在其名称中使用这些字符的文件出现问题,将打开模式作为第二个参数:
open THIS,"-|","cat $ARGV[0]";
这将做同样的事情.模式“ – |”代表“管道”,“| – ”表示“管道”.其余的模式可以被使用(>>>,<>,>,<).虽然有更多的开放,但这对大多数事情都是足够的. 但是您应该尽可能避免调用外部程序.你可以直接打开文件,打开这个“$ARGV [0]”,例如,并且有更好的性能. 那么你可以删除哪些外部程序?好吧,几乎一切.但是让我们留下基本知识:cat,grep,cut,head,tail,uniq,wc,sort. 猫 那么这个没有什么可说的.只要记住,如果可能,只读一次文件并保存在内存中.如果文件是巨大的,你不会这样做,当然,但是几乎总是有一些方法来避免多次读取文件. 无论如何,猫的基本语法是:
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,而无需先打开它们.实际上,“打印”默认为STDOUT,“die”默认为“STDERR”.
还要注意“或死…”和“||死…”.运算符或||意味着如果第一个返回false(这意味着空字符串,空引用,0等),它将只执行以下命令. die命令停止脚本并显示错误消息.
“或”和“||”之间的主要区别是优先事项.如果“或”被“||”替换在上面的例子中,它将无法按预期的方式工作,因为该行将被解释为:
open FILE,("$filename" || die "Could not open $filename!\n");
哪个不是预期的.由于“或”具有较低的优先级,因此它可以工作.在“||”行中被使用,要打开的参数在括号之间传递,使得可以使用“||”.
唉,猫有什么事情呢?
while(<>) { print $_; }
这将打印命令行中的所有文件,或通过STDIN传递的任何文件.
GREP
那么,我们的“grep”脚本怎么工作?我会假设“grep -E”,因为Perl比简单的grep更容易.无论如何:
my $pattern = $ARGV[0]; shift @ARGV; while(<>) { print $_ if /$pattern/o; }
传递给$patttern的“o”指示Perl仅将该模式编译一次,从而获得速度.不是风格“如果cond”.这意味着如果条件为真,它将只执行“某事”.最后,“/ $pattern /”一样,与“$_ =〜m / $pattern /”相同,这意味着比较$_和所指示的正则表达式模式.如果你想要标准的grep行为,即只是子串匹配,你可以写:
print $_ if $_ =~ "$pattern";
切
通常,您使用正则表达式组来获得确切的字符串比cut更好.例如,你可以用“sed”做什么.无论如何,这里有两种复制方法:
while(<>) { my @array = split ","; print $array[3],"\n"; }
这将让你得到每一行的第四列,使用“,”作为分隔符.注意@array和$array [3]. @ sigil表示“数组”应该被视为一个好的数组.它将接收由当前处理行中的每个列组成的数组.接下来,$sigil表示数组[3]是标量值.它将返回您要求的列.
这不是一个很好的实现,但是,“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”将告诉Perl回到循环条件,获取下一个元素(如果适用).这里我们使用它来跳过一个文件,除非它是一个真正的文件(而不是一个目录)和可读的.如果即使这样我们也无法打开文件,它也将跳过.
然后“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.混合它们,你有一个队列.我保持我的队列最多10个元素与$#行,这将给我的数组中的最后一个元素的索引.您还可以使用标量(@lines)获取@lines中的元素数量.
UNIQ
现在,uniq只能消除重复的连续线,这应该很简单,你到目前为止看到的.所以我会消除所有这些:
my $current = ""; my %lines; while(<>) { if($ARGV ne $current) { undef %lines; $current = $ARGV; } print $_ unless defined($lines{$_}); $lines{$_} = ""; }
现在在这里,我将整个文件保存在内存的%行内.使用%sigil表示这是一个哈希表.我将这些行用作关键字,并且不存储任何值 – 因为我对这些值没有兴趣.我使用“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”,有“地图”,这里我不会演示.而不是过滤,它将返回由每个元素的“代码”的结果形成的数组.
最后,排序.这个也很简单:
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"; }
如果将它与“chdir”相结合,您可以模仿“查找”:
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 }
严格来说,“(参数)”是可选的.我使用的声明参数“($$)”表示我收到两个标量参数.那里也可以有“@”或“%”.数组“@_”具有传递的所有参数.行“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,$system\n"; } close FILE;
这不会给出任何错误或警告,也不会工作.它只返回sysems,但不是名字和电话号码!发生了什么?那么发生了什么事情是在调用get_system之后引用“$_”,但是通过读取文件,get_system将覆盖$_!
为了避免这种情况,我们将在$_ local内部get_system.这将给它一个局部作用域,然后从get_system返回时将恢复原始值:
#!/usr/bin/perl use strict; use warnings; sub get_system($) { my ($name) = @_; my $system = ""; local $_; open my $filehandle,$system\n"; } close FILE;
那还不行!它在名称和系统之间打印一个换行符.那么Perl读取的行包括它可能有的任何换行符.有一个整洁的命令,将从字符串“chomp”中删除换行符,我们将用它来解决这个问题.由于并不是每个名字都有一个系统,所以我们也可以避免在发生这种情况时打印逗号:
#!/usr/bin/perl use strict; use warnings; sub get_system($) { my ($name) = @_; my $system = ""; local $_; open my $filehandle,/; 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;
不幸的是,它不起作用没有系统出现!发生了什么?那么,我们来看看“%systems”包含的内容,通过使用Data :: Dumper:
#!/usr/bin/perl use strict; use warnings; use Data::Dumper; our %systems; open SYSTEMS,(.*)/; $systems{$name} = $system; } close SYSTEMS; print Dumper(%systems); open NAMES,$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,(666) 5555-5555
那些$VAR1 / $VAR2 / etc是Dumper显示哈希表的方式.奇数是键,后面的偶数是数值.现在我们可以看到,%系统中的每个名称都有一个前所未有的空间!愚蠢的正则表达式错误,让我们来解决它:
#!/usr/bin/perl use strict; use warnings; our %systems; open SYSTEMS,$name) = /^\s*([^,]*?)\s*,\s*(.*?)\s*$/; $systems{$name} = $system; } close SYSTEMS; open NAMES,"names.txt"; while(<NAMES>) { my ($name) = /^\s*([^,$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,]+?)\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*([^,$systems{$name}" if defined($name) && defined($systems{$name}); print "\n"; } close NAMES;
正则表达式现在需要至少一个字符用于名称和系统,我们测试以查看在使用它之前是否定义了“$name”.
结论
那么,这些是翻译shell脚本的基本工具.您可以使用Perl做更多的事情,但这不是您的问题,而且无论如何也不适合.
作为一些重要话题的基本概述,
>可能被黑客攻击的Perl脚本需要使用-T选项运行,以便Perl将对任何未妥善处理的漏洞输入进行投诉.
>有一些库,称为模块,用于数据库访问,XML& cia处理,Telnet,HTTP&其他协议.事实上,在CPAN可以找到mariads的模块.
>如其他人所提到的,如果您使用AWK或SED,则可以使用A2P和S2P将其转换为Perl.
> Perl可以用面向对象的方式编写.
>有多个版本的Perl.在撰写本文时,稳定版本为5.8.8,有5.10.0可用.还有一个Perl 6在开发中,但是经验告诉大家不要太急切地等待.
有一个免费的,好的,动手的,坚硬的关于Perl的快速书叫Learning Perl The Hard Way.它的风格类似于这个答案.这可能是一个很好的地方.
我希望这有帮助.
免责声明
我不是在教Perl,而是至少需要一些参考资料.有良好的Perl习惯的指导方针,例如使用“严格使用”和“使用警告”;在脚本开始时,使得较不宽松的写入不正确的代码,或者在打印行上使用STDOUT和STDERR来指示正确的输出管道.
这是我同意的东西,但是我决定这样做会降低显示常用shell脚本实用程序模式的基本目标.