2013-07-04 11 views
5

sto imparando Perl e ho scritto un piccolo script per aprire i file perl e rimuovere i commenti

# Will remove this comment

my $name = ""; # Will not remove this comment

#!/usr/bin/perl -w < - wont rimuovere questo commento speciale

Il nome dei file da modificare vengono passati come argomenti tramite il terminale

die "You need to a give atleast one file-name as an arguement\n" unless (@ARGV); 

foreach (@ARGV) { 
    $^I = ""; 
    (-w && open FILE, $_) || die "Oops: $!"; 
    /^\s*#[^!]/ || print while(<>); 
    close FILE; 
    print "Done! Please see file: $_\n"; 
} 

Ora, quando ho eseguito tramite Terminal: perl removeComments file1.pl file2.pl file3.pl

ho avuto l'output: Done! Please see file:

Questo script funziona esattamente come mi aspetto, ma

Problema 1: Perché $_ non ha stampato il nome del file?

Problema 2: Poiché il ciclo viene eseguito per 3 volte, perché Done! Please see file: è stato stampato una sola volta?

Come scrivere questo script nel minor numero possibile di righe?

Si prega di commentare anche il mio codice, se avete tempo.

Grazie.

+0

Suggerimento: '/ stuff /;' è veramente '$ _ = ~/stuff /;'. – cHao

+0

Non c'è davvero alcun motivo per usare '<>' qui se hai aperto esplicitamente un handle di file (che dovrebbe essere fatto usando anche il modo 3-arg). – squiguy

+0

Il modo forse più breve per scrivere il tuo programma: 'perl -i -ne '/^\ s * # [^!]/|| print'' tranne che per i messaggi completati. – TLP

risposta

9

La mentre memorizza le linee lette dall'operatore diamante <> in $ _, in modo che si sta scrivendo sopra la variabile che memorizza il nome del file.

D'altra parte, si apre il file con open ma in realtà non si utilizza l'handle per leggere; usa invece l'operatore diamante vuoto. L'operatore diamante vuoto esegue un ciclo implicito sui file in @ARGV, rimuovendo i nomi dei file mentre vengono eseguiti, quindi lo foreach viene eseguito solo una volta.

Per fissare il secondo problema si potrebbe usare while(<FILE>), o riscrivere il ciclo di sfruttare il ciclo implicita <> e scrivere l'intero programma come:

$^I = ""; 
/^\s*#[^!]/ || print while(<>); 
+0

Tecnicamente, è il ciclo 'while' che lo fa:' while (<>) 'è una speciale abbreviazione di magia per' while (defined ($ _ = <>)) '. In ogni altro contesto, però, '<>' restituisce semplicemente la/e riga/e che legge. –

+0

Grazie per averlo chiarito, ho davvero pensato che '<>' modificato '$ _'. – Joni

+1

È possibile scambiare "while' per' for', che funzionerà anche, perché 'for' usa l'aliasing nella sua condizione di ciclo, simile a' for local $ _ (...) '. – TLP

2

La sua non è sicuro da usare cicli multipli e provare per ottenere il diritto $_. Il ciclo while sta uccidendo il tuo $_. Prova a dare ai tuoi file nomi specifici all'interno di quel ciclo. È possibile farlo con così:

foreach my $filename(@ARGV) { 
    $^I = ""; 
    (-w && open my $FILE,'<', $filename) || die "Oops: $!"; 
    /^\s*#[^!]/ || print while(<$FILE>); 
    close FILE; 
    print "Done! Please see file: $filename\n"; 
} 

o in quel modo:

foreach (@ARGV) { 
    my $filename = $_; 
    $^I = ""; 
    (-w && open my $FILE,'<', $filename) || die "Oops: $!"; 
    /^\s*#[^!]/ || print while(<$FILE>); 
    close FILE; 
    print "Done! Please see file: $filename\n"; 
} 

Si prega di non utilizzare mai le bareword per filehandles e fare utilizzare un 3-argomentazione open.

  • open my $FILE, '<', $filename - buona

  • open FILE $filename - bad

+0

Non si è notato che '<>' non legge dall'handle del file '$ FILE'. – TLP

+0

ah esatto! Grazie –

3

Ecco un approccio più leggibile.

#!/usr/bin/perl 

# always!! 
use warnings; 
use strict; 

use autodie; 
use File::Copy; 

# die with some usage message 
die "usage: $0 [ files ]\n" if @ARGV < 1; 


for my $filename (@ARGV) { 
    # create tmp file name that we are going to write to 
    my $new_filename = "$filename\.new"; 

    # open $filename for reading and $new_filename for writing 
    open my $fh, "<", $filename; 
    open my $new_fh, ">", $new_filename; 

    # Iterate over each line in the original file: $filename, 
    # if our regex matches, we bail out. Otherwise we print the line to 
    # our temporary file. 
    while(my $line = <$fh>) { 
     next if $line =~ /^\s*#[^!]/; 
     print $new_fh $line; 
    } 

    close $fh; 
    close $new_fh; 

    # use File::Copy's move function to rename our files. 
    move($filename, "$filename\.bak"); 
    move($new_filename, $filename); 

    print "Done! Please see file: $filename\n"; 
} 

Esempio di output:

$ ./test.pl a.pl b.pl 
Done! Please see file: a.pl 
Done! Please see file: b.pl 

$ cat a.pl 
#!/usr/bin/perl 

print "I don't do much\n"; # comments dont' belong here anyways 

exit; 

print "errrrrr"; 

$ cat a.pl.bak 
#!/usr/bin/perl 

# this doesn't do much 
print "I don't do much\n"; # comments dont' belong here anyways 

exit; 

print "errrrrr"; 
0

Più semplice soluzione: non utilizzare $_.

Quando Perl è stato scritto per la prima volta, è stato concepito come sostituto di Awk e shell, e Perl ha fortemente preso a prestito da quella sintassi. Perl anche per migliorare la leggibilità creato la variabile speciale $_ che vi ha permesso di utilizzare vari comandi, senza dover creare variabili:

while (<INPUT>) { 
    next if /foo/; 
    print OUTPUT; 
} 

Il problema è che se tutto è usando $_, allora tutto sarà effact $_ in molti effetti collaterali spiacevoli.

Ora, Perl è un linguaggio molto più sofisticato, e ha cose come con ambito locale variabili (suggerimento: Non si utilizza local per creare queste variabili - che si limita dà variabili _package (variabili globali aka) un locale valore.)

Dato che stai imparando Perl, potresti anche imparare correttamente Perl. Il problema è che ci sono troppi libri che sono ancora basati su Perl 3.x. Trova un libro o web page che incorpori la pratica moderna.

Nel programma, $_ passa dal nome file alla riga nel file e torna al file successivo. È quello che ti sta confondendo. Se hai usato variabili con nome, potresti distinguere tra file e righe.

Ho riscritto il programma utilizzando la sintassi più moderna, ma la tua stessa logica:

use strict; 
use warnings; 
use autodie; 
use feature qw(say); 

if (not $ARGV[0]) { 
    die "You need to give at least one file name as an argument\n"; 
} 

for my $file (@ARGV) { 
    # Remove suffix and copy file over 
    if ($file =~ /\..+?$/) { 
     die qq(File "$file" doesn't have a suffix); 
    } 
    my ($output_file = $file) =~ s/\..+?$/./; #Remove suffix for output 
    open my $input_fh, "<", $file; 
    open my $output_fh, ">", $output_file; 
    while (my $line = <$input_fh>) { 
     print {$output_fh} $line unless /^\s*#[^!]/; 
    } 
    close $input_fh; 
    close $output_fh; 
} 

Questo è un po 'più di battitura che la vostra versione del programma, ma è più facile vedere cosa sta succedendo e mantenere .

Problemi correlati