2008-10-29 30 views
11

Tôi đã cố gắng mã một kịch bản Perl để thay thế một số văn bản trên tất cả các tệp nguồn của dự án của tôi. Tôi cần một cái gì đó như:Có cách nào đơn giản để thay thế văn bản tệp hàng loạt tại chỗ không?

perl -p -i.bak -e "s/thisgoesout/thisgoesin/gi" *.{cs,aspx,ascx} 

Nhưng đó phân tích tất cả các tập tin của một thư mục đệ quy.

Tôi chỉ mới bắt đầu một kịch bản:

use File::Find::Rule; 
use strict; 

my @files = (File::Find::Rule->file()->name('*.cs','*.aspx','*.ascx')->in('.')); 

foreach my $f (@files){ 
    if ($f =~ s/thisgoesout/thisgoesin/gi) { 
      # In-place file editing, or something like that 
    } 
} 

Nhưng bây giờ tôi đang mắc kẹt. Có một cách đơn giản để chỉnh sửa tất cả các tập tin tại chỗ bằng cách sử dụng Perl?

Xin lưu ý rằng tôi không cần giữ bản sao của mọi tệp đã sửa đổi; Tôi có 'em tất cả subversioned =)

Cập nhật: Tôi cố gắng này trên Cygwin,

perl -p -i.bak -e "s/thisgoesout/thisgoesin/gi" {*,*/*,*/*/*}.{cs,aspx,ascx 

Nhưng có vẻ như danh sách đối số của tôi bùng nổ với kích thước tối đa cho phép. Trong thực tế, tôi nhận được lỗi rất lạ trên Cygwin ...

+0

Bạn có thể lưu ý rằng bạn đang chạy Windows. –

Trả lời

13

Nếu bạn gán @ARGV trước khi sử dụng *ARGV (còn gọi là kim cương <>), $^I/-i sẽ làm việc trên các tập tin thay vì những gì đã được chỉ định trên dòng lệnh.

use File::Find::Rule; 
use strict; 

@ARGV = (File::Find::Rule->file()->name('*.cs', '*.aspx', '*.ascx')->in('.')); 
$^I = '.bak'; # or set `-i` in the #! line or on the command-line 

while (<>) { 
    s/thisgoesout/thisgoesin/gi; 
    print; 
} 

Điều này nên làm chính xác những gì bạn muốn.

Nếu mẫu của bạn có thể mở rộng nhiều dòng, hãy thêm vào undef $/; trước <> để Perl hoạt động trên toàn bộ tệp tại một thời điểm thay vì theo từng dòng.

+0

Chính xác những gì tôi cần! – Seiti

2

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

find . -name '*.{cs,aspx,ascx}' | xargs perl -p -i.bak -e "s/thisgoesout/thisgoesin/gi" 

này sẽ liệt kê tất cả các tên tập tin một cách đệ quy, sau đó xargs sẽ đọc stdin của nó và chạy phần còn lại của dòng lệnh với tên tập tin được nối vào cuối. Một điều tốt đẹp về xargs là nó sẽ chạy dòng lệnh nhiều hơn một lần nếu dòng lệnh nó xây dựng được quá lâu để chạy trong một lần.

Lưu ý rằng tôi không chắc chắn liệu find hoàn toàn hiểu được tất cả các phương pháp vỏ lựa chọn tác phẩm, vì vậy nếu ở trên không làm việc thì có lẽ thử:

find . | grep -E '(cs|aspx|ascx)$' | xargs ... 

Khi sử dụng đường ống như thế này, tôi thích để xây dựng dòng lệnh và chạy từng phần riêng lẻ trước khi tiếp tục, để đảm bảo mỗi chương trình nhận được đầu vào mà nó muốn. Vì vậy, bạn có thể chạy phần mà không cần xargs trước tiên để kiểm tra nó.

Nó chỉ xảy ra với tôi rằng mặc dù bạn không nói như vậy, bạn có thể trên Windows do hậu tố tập tin bạn đang tìm kiếm. Trong trường hợp đó, đường dẫn trên có thể chạy bằng Cygwin. Có thể viết một kịch bản Perl để làm điều tương tự, khi bạn bắt đầu làm, nhưng bạn sẽ phải tự mình chỉnh sửa tại chỗ vì bạn không thể tận dụng lợi thế của công tắc -i trong tình huống đó.

+0

Đã thử tìm. -name '*. {cs, aspx, ascx}' không có may mắn, nhưng phiên bản grep liệt kê các tệp. Tốt đẹp! Nhưng khi tôi chạy tất cả các lệnh, tôi nhận được điều này: xargs: perl: Danh sách đối số quá dài – Seiti

+0

xargs cũng có thể giới hạn số đối số được truyền trên mỗi dòng lệnh, nếu không thể xác định độ dài tối đa của dòng lệnh . Sử dụng tùy chọn -L hoặc -n để xargs tùy thuộc vào phiên bản nào (xem trang hướng dẫn). –

+0

Nếu bạn định sử dụng find & xargs, hãy sử dụng -print0 và -0 để tránh các vấn đề với tên tệp có dấu cách. find -print0 ... | xargs -0 ... – Schwern

4

Thay đổi

foreach my $f (@files){ 
    if ($f =~ s/thisgoesout/thisgoesin/gi) { 
      #inplace file editing, or something like that 
    } 
} 

Để

foreach my $f (@files){ 
    open my $in, '<', $f; 
    open my $out, '>', "$f.out"; 
    while (my $line = <$in>){ 
     chomp $line; 
     $line =~ s/thisgoesout/thisgoesin/gi 
     print $out "$line\n"; 
    } 
} 

này giả định rằng mô hình không span nhiều dòng. Nếu mẫu có thể mở rộng các dòng, bạn sẽ cần phải slurp trong nội dung tập tin. ("slurp" là một thuật ngữ Perl khá phổ biến).

Các chomp là không thực sự cần thiết, tôi vừa bị cắn bởi dòng không chomp ed một quá nhiều lần (nếu bạn thả chomp, thay đổi print $out "$line\n"; để print $out $line;).

Tương tự, bạn có thể thay đổi open my $out, '>', "$f.out"; thành open my $out, '>', undef; để mở tệp tạm thời và sau đó sao chép tệp đó trở lại bản gốc khi thực hiện thay thế. Trong thực tế, và đặc biệt là nếu bạn slurp trong toàn bộ tập tin, bạn chỉ có thể làm cho sự thay thế trong bộ nhớ và sau đó viết trên tập tin gốc. Nhưng tôi đã thực hiện đủ những sai lầm khi làm điều đó mà tôi luôn ghi vào một tập tin mới, và kiểm tra nội dung.


Note, ban đầu tôi đã có một câu lệnh if trong mã đó. Điều đó rất có thể là sai. Điều đó sẽ chỉ được sao chép trên các dòng phù hợp với cụm từ thông dụng "thisgoesout" (thay thế nó bằng "thisgoesin" tất nhiên) trong khi âm thầm gobbling lên phần còn lại.

7

Bạn có thể quan tâm đến File::Transaction::Atomic hoặc File::Transaction

Các SYNOPSIS cho F :: T :: Một trông rất giống với những gì bạn đang cố gắng để làm:

# In this example, we wish to replace 
    # the word 'foo' with the word 'bar' in several files, 
    # with no risk of ending up with the replacement done 
    # in some files but not in others. 

    use File::Transaction::Atomic; 

    my $ft = File::Transaction::Atomic->new; 

    eval { 
     foreach my $file (@list_of_file_names) { 
      $ft->linewise_rewrite($file, sub { 
       s#\bfoo\b#bar#g; 
      }); 
     } 
    }; 

    if ([email protected]) { 
     $ft->revert; 
     die "update aborted: [email protected]"; 
    } 
    else { 
     $ft->commit; 
    } 

Couple rằng với File :: Tìm bạn đã viết, và bạn nên làm tốt.

6

Bạn có thể sử dụng Tie :: Tệp để truy cập rộng rãi các tệp lớn và thay đổi chúng tại chỗ. Xem manpage (man 3perl Tie :: File).

+0

Tại sao chỉ cho họ (3perl) thay vì Perldoc? – ephemient

+0

Có, Tie :: Tệp đã được tạo cho chỉ loại điều này. – Schwern

+0

http://perldoc.perl.org/Tie/File.html –

1

Nhờ ephemient về câu hỏi này và trên this answer, tôi nhận điều này:

use File::Find::Rule; 
use strict; 

sub ReplaceText { 
    my $regex = shift; 
    my $replace = shift; 

    @ARGV = (File::Find::Rule->file()->name('*.cs','*.aspx','*.ascx')->in('.')); 
    $^I = '.bak'; 
    while (<>) { 
     s/$regex/$replace->()/gie; 
     print; 
    } 
} 

ReplaceText qr/some(crazy)regexp/, sub { "some $1 text" }; 

Bây giờ tôi thậm chí có thể lặp qua một băm chứa regexp => tàu ngầm mục!

+0

Có lẽ bạn nên 'local'ize' @ ARGV' và '$^I' trong thói quen này, vì các biến này có hiệu ứng toàn cục hơn. – ephemient

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