2009-03-04 107 views
8

我知道如何将sedgrep一起使用,但在Perl中以下失败。如何获得sed在Perl程序中工作?你如何从Perl使用sed?

chomp (my @lineNumbers=`grep -n "textToFind" $fileToProcess | sed -n 's/^\([0-9]*\)[:].*/\1/p'`) 

回答

8

我很惊讶,没有人提到s2p实用程序,它将sed“脚本”(你知道,大部分时间在oneliners上)转换为有效的perl。 (还有一个awk的a2p工具...)

25

建议:使用Perl正则表达式和替换代替grep或sed。

它几乎是相同的语法,但功能更强大。另外最后它会比调用额外的sed过程更有效率。

+0

grep也是不必要的 – dsm 2009-03-04 16:36:31

+0

谢谢@dsm!我更新了答案 – 2009-03-04 16:37:09

+0

同意,最好不要在语言的可用API之外进行不必要的绕行。 – 2009-03-04 16:45:12

12

任何你需要用grep或sed做的事情都可以在perl中更简单地完成。例如(这大致是正确的,但可能是错误的):

my @linenumbers; 
open FH "<$fileToProcess"; 
while (<FH>) 
{ 
    next if (!m/textToFind/); 
    chomp; 
    s/^\([0-9]*\)[:].*/\1/; 
    push @lineNumbers, $_; 
} 
0

编辑:好的,我现在修好了。

use File::Grep qw/fmap/; 

my @lineNumbers = fmap { /$pattern/ ? $_[1] :() } $fileToProcess; 
4

假设Larry Wall写了Perl,因为他发现了一些无法用sed和awk做的事情。其他答案有这个权利,而是使用Perl正则表达式。你的代码将会减少外部依赖,对于更多人来说是可以理解的(Perl的用户基础比sed用户基础大得多),你的代码将是跨平台的,没有额外的工作。

编辑:Paul Tomblin在他评论我的答案时提到了一个很好的故事。我把它放在这里来增加它的重要性。

“与Awk做了一些令人惊奇的事情的Henry Spencer声称,在向Larry Wall演示了一些awk的东西之后,Larry说如果他知道的话他不会为Perl烦恼。保罗 - 汤布林

2

使用电源卢克:

$ echo -e "a\nb\na"|perl -lne'/a/&&print$.' 
1 
3 

因此,当你想同认为,因为这缓慢和过于复杂的grepsed组合,你可以做到这一点更简单,更快的在Perl本身:

my @linenumbers; 
open my $fh, '<', $fileToProcess or die "Can't open $fileToProcess: $!"; 
while (<$fh>) 
{ 
    /textToFind/ and push @lineNumbers, $.; 
} 
close $fh; 

或具有与原始解决方案相同的内存元凶

my @linenumbers = do { 
    open my $fh, '<', $fileToProcess or die "Can't open $fileToProcess: $!"; 
    my $i; 
    map { (++$i) x /textToFind/ } <$fh> 
}; 
1

如果您有大的sed表达式,则可以使用s2p将其转换为perl程序。

如果运行  < s2p 's/^\([0-9]*\)[:].*/\1/p'>,这是你会得到什么:

#!/opt/perl/bin/perl -w 
eval 'exec /opt/perl/bin/perl -S $0 ${1+"[email protected]"}' 
    if 0; 
$0 =~ s/^.*?(\w+)[\.\w+]*$/$1/; 

use strict; 
use Symbol; 
use vars qw{ $isEOF $Hold %wFiles @Q $CondReg 
     $doAutoPrint $doOpenWrite $doPrint }; 
$doAutoPrint = 1; 
$doOpenWrite = 1; 
# prototypes 
sub openARGV(); 
sub getsARGV(;\$); 
sub eofARGV(); 
sub printQ(); 

# Run: the sed loop reading input and applying the script 
# 
sub Run(){ 
    my($h, $icnt, $s, $n); 
    # hack (not unbreakable :-/) to avoid // matching an empty string 
    my $z = "\000"; $z =~ /$z/; 
    # Initialize. 
    openARGV(); 
    $Hold = ''; 
    $CondReg = 0; 
    $doPrint = $doAutoPrint; 
CYCLE: 
    while(getsARGV()){ 
    chomp(); 
    $CondReg = 0; # cleared on t 
BOS:; 
# s/^\([0-9]*\)[:].*/\1/p 
{ $s = s /^(\d*)[:].*/${1}/s; 
    $CondReg ||= $s; 
    print $_, "\n" if $s; 
} 
EOS: if($doPrint){ 
      print $_, "\n"; 
     } else { 
     $doPrint = $doAutoPrint; 
    } 
     printQ() if @Q; 
    } 

    exit(0); 
} 
Run(); 

# openARGV: open 1st input file 
# 
sub openARGV(){ 
    unshift(@ARGV, '-') unless @ARGV; 
    my $file = shift(@ARGV); 
    open(ARG, "<$file") 
    || die("$0: can't open $file for reading ($!)\n"); 
    $isEOF = 0; 
} 

# getsARGV: Read another input line into argument (default: $_). 
#   Move on to next input file, and reset EOF flag $isEOF. 
sub getsARGV(;\$){ 
    my $argref = @_ ? shift() : \$_; 
    while($isEOF || ! defined($$argref = <ARG>)){ 
    close(ARG); 
    return 0 unless @ARGV; 
    my $file = shift(@ARGV); 
    open(ARG, "<$file") 
    || die("$0: can't open $file for reading ($!)\n"); 
    $isEOF = 0; 
    } 
    1; 
} 

# eofARGV: end-of-file test 
# 
sub eofARGV(){ 
    return @ARGV == 0 && ($isEOF = eof(ARG)); 
} 

# makeHandle: Generates another file handle for some file (given by its path) 
#    to be written due to a w command or an s command's w flag. 
sub makeHandle($){ 
    my($path) = @_; 
    my $handle; 
    if(! exists($wFiles{$path}) || $wFiles{$path} eq ''){ 
     $handle = $wFiles{$path} = gensym(); 
    if($doOpenWrite){ 
     if(! open($handle, ">$path")){ 
     die("$0: can't open $path for writing: ($!)\n"); 
     } 
    } 
    } else { 
     $handle = $wFiles{$path}; 
    } 
    return $handle; 
} 

# printQ: Print queued output which is either a string or a reference 
#   to a pathname. 
sub printQ(){ 
    for my $q (@Q){ 
    if(ref($q)){ 
      # flush open w files so that reading this file gets it all 
     if(exists($wFiles{$$q}) && $wFiles{$$q} ne ''){ 
     open($wFiles{$$q}, ">>$$q"); 
     } 
      # copy file to stdout: slow, but safe 
     if(open(RF, "<$$q")){ 
     while(defined(my $line = <RF>)){ 
      print $line; 
     } 
     close(RF); 
     } 
    } else { 
     print $q; 
    } 
    } 
    undef(@Q); 
} 

不完全是值得做的小表情。

1

可以代替使用

perl -pe 's/search/replace/g' 

sed 's/search/replace/' 

..但是..

这些都是为了命令行或shell脚本。 既然你已经在Perl脚本中, 上面的“Paul Tomblin”给出了正确的答案。

玩得开心, eKerner.com

0

这里是你如何使用Perl作为桑达更换:

相反的:

sed "s/xxx/yyy/g" files_to_process 

用途:

perl -i.bak -pe "s/xxx/yyy/g" files_to_process 

这将在原地修改文件并对每个修改后的文件进行备份(.bak)。

0

使用Perl比使用grep和sed更容易;见another answer

你的代码失败了,因为Perl与sed代码中的反斜杠混淆了。为防止出现这种情况,请在'a single-quoted Perl string'中编写您的sed代码,然后使用\Q$sedCode\E将代码内插到shell命令中。 (关于\Q...E,见perldoc -f quotemeta,其通常的目的是引号字符的正则表达式,但it also works with shell commands

my $fileToProcess = "example.txt"; 
my $sedCode = 's/^\([0-9]*\)[:].*/\1/p'; 
chomp(my @linenumbers = 
     `grep -n "textToFind" \Q$fileToProcess\E | sed -n \Q$sedCode\E`); 
printf "%s\n", join(', ', @linenumbers); 

鉴于example.txt

this has textToFind 
this doesn't 
textToFind again 
textNotToFind 

输出为1, 3