Domanda

Ho provato a codificare uno script Perl per sostituire del testo su tutti i file sorgente del mio progetto. Ho bisogno di qualcosa del tipo:

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

Ma questo analizza tutti i file di una directory ricorsivamente .

Ho appena iniziato uno script:

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
    }
}

Ma ora sono bloccato. C'è un modo semplice per modificare tutti i file in atto usando Perl?

Si noti che non è necessario conservare una copia di ogni file modificato; Li ho tutti sottovalutati =)

Aggiornamento : l'ho provato su Cygwin ,

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

Ma sembra che la mia lista di argomenti sia esplosa alla dimensione massima consentita. In effetti, sto riscontrando errori molto strani su Cygwin ...

È stato utile?

Soluzione

Se si assegna @ARGV prima di utilizzare *ARGV (aka il diamante <>), $^I / -i funzionerà su quei file invece di quanto specificato sulla riga di comando.

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;
}

Questo dovrebbe fare esattamente quello che vuoi.

Se il tuo modello può estendersi su più righe, aggiungi un undef $/; prima del <=> in modo che Perl operi su un intero file alla volta anziché riga per riga.

Altri suggerimenti

Potresti essere interessato a File :: Transaction :: Atomic o File :: Transaction

La SINOSSI per F :: T :: A sembra molto simile a ciò che stai cercando di fare:

  # 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 ($@) {
      $ft->revert;
      die "update aborted: $@";
  }
  else {
      $ft->commit;
  }

Abbinalo al File :: Trova che hai già scritto e dovresti essere pronto per andare.

È possibile utilizzare Tie :: File per accedere in modo scalabile a file di grandi dimensioni e modificarli in posizione. Vedi la manpage (man 3perl Tie :: File).

Cambia

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

Per

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";
    }
}

Ciò presuppone che il modello non si estenda su più righe. Se il modello potrebbe estendersi sulle linee, dovrai assorbire il contenuto del file. (" slurp " è un termine Perl abbastanza comune).

Il chomp non è in realtà necessario, sono appena stato morso da linee che non sono state chomp modificate una volta troppe volte (se lasci cadere print $out "$line\n";, cambia print $out $line; in open my $out, '>', "$f.out";).

Allo stesso modo, è possibile modificare open my $out, '>', undef; in <=> per aprire un file temporaneo e quindi copiarlo sull'originale al termine della sostituzione. In effetti, e specialmente se si assorbe l'intero file, è possibile semplicemente effettuare la sostituzione in memoria e quindi scrivere sul file originale. Ma ho fatto abbastanza errori nel farlo che scrivo sempre in un nuovo file e ne verifico il contenuto.


Nota , inizialmente avevo un'istruzione if in quel codice. Molto probabilmente era sbagliato. Ciò sarebbe stato copiato solo sulle righe che corrispondevano all'espressione regolare & Quot; thisgoesout & Quot; (sostituendolo con " thisgoesin " ovviamente) mentre divorando silenziosamente il resto.

Puoi usare find:

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

Questo elencherà tutti i nomi dei file in modo ricorsivo, quindi xargs leggerà il suo stdin ed eseguirà il resto della riga di comando con i nomi dei file aggiunti alla fine. Una cosa bella di -i è che eseguirà la riga di comando più di una volta se la riga di comando che costruisce diventa troppo lunga per essere eseguita in una volta sola.

Nota che non sono sicuro che <=> comprenda completamente tutti i metodi shell di selezione dei file, quindi se quanto sopra non funziona, allora prova:

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

Quando utilizzo pipeline come questa, mi piace costruire la riga di comando ed eseguire ciascuna parte singolarmente prima di procedere, per assicurarmi che ogni programma ottenga l'input desiderato. Quindi è possibile eseguire la parte senza <=> prima verificarla.

Mi è appena venuto in mente che, anche se non lo hai detto, probabilmente sei su Windows a causa dei suffissi dei file che stai cercando. In tal caso, la pipeline sopra potrebbe essere eseguita utilizzando Cygwin. È possibile scrivere uno script Perl per fare la stessa cosa, come hai iniziato a fare, ma dovrai fare tu stesso la modifica sul posto perché non puoi sfruttare l'opzione <=> in quella situazione.

Grazie a effimero su questa domanda e su questo risposta , ho capito:

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" };

Ora posso persino passare in rassegna un hash contenente regexp = > iscrizioni subs!

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top