2009-03-04 35 views
8

Tôi biết cách sử dụng sed với grep, nhưng bên trong Perl bên dưới không thành công. Làm cách nào để có được sed để hoạt động trong chương trình Perl?Làm thế nào để bạn sử dụng sed từ Perl?

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

Trả lời

8

Tôi ngạc nhiên khi không ai đề cập đến tiện ích s2p, dịch các tập lệnh sed "" (bạn biết đấy, phần lớn thời gian oneliner) thành perl hợp lệ. (Và có một tiện ích a2p cho awk quá ...)

25

Đề xuất: Sử dụng biểu thức chính quy Perl và thay thế thay cho grep hoặc sed.

Đó là cú pháp tương tự, nhưng mạnh mẽ hơn. Ngoài ra cuối cùng nó sẽ hiệu quả hơn việc gọi quá trình bổ sung sed.

+0

grep cũng không cần thiết – dsm

+0

cảm ơn @dsm! Tôi đã cập nhật câu trả lời –

+0

Đồng ý, tốt hơn là không thực hiện các đường vòng không cần thiết bên ngoài API có sẵn của ngôn ngữ. –

12

Bất cứ điều gì bạn cần làm với grep hoặc sed có thể được thực hiện một cách tự nhiên trong perl dễ dàng hơn. Ví dụ (điều này gần đúng, nhưng có thể sai):

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

bạn cần một cuối cùng/trên của bạn nếu, nhưng nếu không có vẻ tốt. – Mez

+0

Cảm ơn, Martin. Bây giờ nó đã được sửa. –

+1

Một vấn đề về hương vị, nhưng: Tôi sẽ sử dụng 'next trừ' thay vì! M /// – slim

0

Đã chỉnh sửa: OK, tôi đã sửa nó ngay bây giờ.

use File::Grep qw/fmap/; 

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

Được cho là Larry Wall đã viết Perl vì ông đã tìm thấy điều gì đó không thể làm với sed và awk. Các câu trả lời khác có quyền này, sử dụng biểu thức chính quy Perl thay thế. Mã của bạn sẽ có ít phụ thuộc bên ngoài hơn, có thể hiểu được đối với nhiều người hơn (cơ sở người dùng của Perl lớn hơn nhiều so với cơ sở người dùng sed) và mã của bạn sẽ là nền tảng chéo mà không có thêm công việc.

Chỉnh sửa: Paul Tomblin liên quan đến một câu chuyện tuyệt vời trong nhận xét của ông về câu trả lời của tôi. Tôi đặt nó ở đây để tăng sự nổi bật của nó.

"Henry Spencer, người đã làm một số điều tuyệt vời với Awk, tuyên bố rằng sau khi giới thiệu một số công cụ awk cho Larry Wall, Larry nói rằng ông sẽ không làm phiền với Perl nếu anh ta biết." - Paul Tomblin

+0

Tìm 's2p', bộ chuyển đổi sed sang Perl; đi kèm với Perl. –

+1

Henry Spencer, người đã làm một số điều tuyệt vời với Awk, tuyên bố rằng sau khi giới thiệu một số công cụ awk để Larry Wall, Larry nói rằng ông sẽ không làm phiền với Perl nếu anh ta biết. –

+4

Tôi cực kỳ vui vì Larry * đã không * biết. :) – Marcus

2

Sử dụng sức mạnh Luke:

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

Như vậy khi bạn muốn cùng nghĩ như grepsed kết hợp chậm và overcomplicated này bạn có thể làm điều đó đơn giản hơn nhiều và nhanh hơn trong perl chính nó:

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

Hoặc với cùng một thủ phạm bộ nhớ như giải pháp ban đầu

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

Bạn đã bỏ qua các khoảng trống trong "perl -lne '/ a/&& print $.'" Có chủ đích không? Tôi đã bị cám dỗ để chỉnh sửa nó để thêm không gian nhưng không biết nếu có bất kỳ lý do cho họ. –

+0

Không, không có mục đích nào nhưng chỉ ngắn hơn bình thường khi chơi gôn. Trong perl 5.10 bạn có thể thậm chí ngắn hơn perl -nE '/ a/&& nói $.' Nó sẽ hoạt động chính xác giống như perl -ne '/ a/và in "$. \ N"' hoặc perl -ne 'if ($ _ = ~ m/a /) {in $., "\ N"}' v.v. –

1

Nếu bạn có một biểu thức lớn sed, bạn có thể sử dụng s2p, để chuyển đổi nó thành một chương trình perl.

Nếu bạn chạy   < s2p 's/^\([0-9]*\)[:].*/\1/p'>, đây là những gì bạn sẽ nhận được:

#!/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); 
} 

Không chính xác giá trị thực hiện trên biểu thức nhỏ.

+0

Có vẻ như s2p là một nơi tốt cho những ai muốn thực hiện một số tối ưu hóa và cải thiện phần mềm nguồn mở hiện có. –

1

Bạn có thể sử dụng

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

ở vị trí của

sed 's/search/replace/' 

.. Tuy nhiên ..

Những có nghĩa là cho dòng lệnh hoặc vỏ kịch bản. Vì bạn đã có trong một kịch bản perl, câu trả lời đúng được đưa ra bởi "Paul Tomblin" ở trên.

Hãy vui vẻ, eKerner.com

+0

Cảm ơn, đây chính xác là những gì tôi đang tìm kiếm. Trong trường hợp của tôi (một kịch bản bash), tôi cần một cái gì đó như 'sed', nhưng với sự hỗ trợ PCRE thích hợp, và không có gì hỗ trợ PCRE tốt hơn bản thân Perl. – selurvedu

0

Đây là cách bạn có thể sử dụng Perl như một sự thay thế cho Sed:

Thay vì:

sed "s/xxx/yyy/g" files_to_process 

Sử dụng:

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

Điều này sẽ sửa đổi các tệp tại chỗ và tạo bản sao lưu (.bak) của mỗi tệp được sửa đổi.

0

Dễ sử dụng Perl hơn là sử dụng grep và sed; xem another answer.

Mã của bạn không thành công vì Perl gây rối với các dấu gạch chéo ngược trong mã sed của bạn. Để ngăn chặn điều này, hãy viết mã sed của bạn trong 'a single-quoted Perl string', sau đó sử dụng \Q$sedCode\E để nội suy mã vào lệnh trình bao. (Về \Q...E, xem perldoc -f quotemeta. Mục đích thông thường của nó là để trích dẫn ký tự cho biểu thức thông thường, nhưng 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); 

Với example.txt với

this has textToFind 
this doesn't 
textToFind again 
textNotToFind 

đầu ra là 1, 3.

Các vấn đề liên quan