如何将shell脚本转换为Perl?

前端之家收集整理的这篇文章主要介绍了如何将shell脚本转换为Perl?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我有一个 shell脚本,很大的一个.现在我的老板说我必须在Perl中重写它.
有没有办法编写Perl脚本,并使用现有的shell代码,就像我的Perl脚本一样.类似于 Inline::C的东西.

有没有像Inline :: Shell?我看了一下内联模块,但它只支持语言.

我会认真回答我不知道任何程序将shell脚本转换为Perl,我怀疑任何解释器模块将提供性能优势.所以我会给出一个我将如何去做的大纲.

现在,您要尽可能重用您的代码.在这种情况下,我建议选择该代码片段,编写一个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,则可以使用A2PS2P将其转换为Perl.
> Perl可以用面向对象的方式编写.
>有多个版本的Perl.在撰写本文时,稳定版本为5.8.8,有5.10.0可用.还有一个Perl 6在开发中,但是经验告诉大家不要太急切地等待.

有一个免费的,好的,动手的,坚硬的关于Perl的快速书叫Learning Perl The Hard Way.它的风格类似于这个答案.这可能是一个很好的地方.

我希望这有帮助.

免责声明

我不是在教Perl,而是至少需要一些参考资料.有良好的Perl习惯的指导方针,例如使用“严格使用”和“使用警告”;在脚本开始时,使得较不宽松的写入不正确的代码,或者在打印行上使用STDOUT和STDERR来指示正确的输出管道.

这是我同意的东西,但是我决定这样做会降低显示常用shell脚本实用程序模式的基本目标.

原文链接:https://www.f2er.com/bash/385163.html

猜你在找的Bash相关文章