9

我知道如何使用sedwith grep,但在 Perl 中以下失败。如何sed在 Perl 程序中工作?

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

10 回答 10

26

建议:使用 Perl 正则表达式和替换,而不是 grep 或 sed。

它的语法大致相同,但功能更强大。最后,它也会比调用额外的 sed 进程更有效。

于 2009-03-04T16:34:11.673 回答
15

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

my @linenumbers;
open FH "<$fileToProcess";
while (<FH>)
{
   next if (!m/textToFind/);
   chomp;
   s/^\([0-9]*\)[:].*/\1/;
   push @lineNumbers, $_;
}
于 2009-03-04T16:36:27.860 回答
8

我很惊讶没有人提到s2p实用程序,它将 sed “脚本”(你知道,大多数时候是 oneliners)转换为有效的 perl。(还有一个用于 awk 的 a2p 实用程序......)

于 2009-03-04T21:43:13.130 回答
4

据推测,Larry Wall 编写 Perl 是因为他发现了 sed 和 awk 无法解决的问题。其他答案有这个权利,请改用 Perl 正则表达式。您的代码将具有更少的外部依赖,更多人可以理解(Perl 的用户群比 sed 用户群大得多),并且您的代码将是跨平台的,无需额外工作。

编辑:Paul Tomblin 在他对我的回答的评论中讲述了一个精彩的故事。我把它放在这里是为了增加它的知名度。

“Henry Spencer 用 awk 做了一些了不起的事情,他声称在向 Larry Wall 演示了一些 awk 的东西之后,Larry 说如果他知道的话他就不会打扰 Perl。” ——保罗·汤布林

于 2009-03-04T17:05:51.207 回答
3

使用权力卢克:

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

grep因此,当您想要与这种缓慢且过于复杂的组合相同的想法时,sed您可以在 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>
};
于 2009-03-04T18:53:03.087 回答
2

您可以使用

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

代替

sed 's/search/replace/'

.. 然而 ..

这些适用于命令行或 shell 脚本。由于您已经在 perl 脚本中,因此上面的“Paul Tomblin”给出了正确答案。

玩得开心,eKerner.com

于 2013-03-26T12:34:36.943 回答
1

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

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

#!/opt/perl/bin/perl -w
eval 'exec /opt/perl/bin/perl -S $0 ${1+"$@"}'
  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 );
}

不完全值得在小表情上做。

于 2009-03-04T21:29:00.793 回答
1

以下是使用 Perl 替代 Sed 的方法:

代替:

sed "s/xxx/yyy/g" files_to_process

采用:

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

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

于 2016-04-06T09:31:49.380 回答
0

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

use File::Grep qw/fmap/;

my @lineNumbers = fmap { /$pattern/ ? $_[1] : () } $fileToProcess;
于 2009-03-04T16:42:13.760 回答
0

使用 Perl 比使用 grep 和 sed 更容易;看到另一个答案

您的代码失败了,因为 Perl 弄乱了 sed 代码中的反斜杠。为防止这种情况发生,请将您的 sed 代码写入'a single-quoted Perl string',然后使用\Q$sedCode\E将代码插入到 shell 命令中。(关于\Q...E,请参阅perldoc -f quotemeta。它通常的目的是为正则表达式引用字符,但它也适用于 shell 命令。)

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

于 2017-11-09T22:28:57.537 回答