2008-10-29 17 views
11

J'ai essayé de coder un script Perl pour remplacer du texte sur tous les fichiers source de mon projet. Je suis dans le besoin de quelque chose comme:Existe-t-il un moyen simple de mettre en place la substitution de texte de fichier en bloc?

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

Mais que tous les Parsis les fichiers d'un répertoire récursive.

Je viens de commencer un 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 
    } 
} 

Mais maintenant, je suis coincé. Existe-t-il un moyen simple d'éditer tous les fichiers en place en utilisant Perl?

Veuillez noter que je n'ai pas besoin de conserver une copie de chaque fichier modifié; Je suis ai « em all = subversioned)

Mise à jour: J'ai essayé sur Cygwin,

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

Mais il semble que ma liste d'arguments a explosé à la taille maximale autorisée. En fait, je reçois des erreurs étranges sur Cygwin ...

+0

Vous devriez probablement noter que vous exécutez Windows. –

Répondre

13

Si vous attribuez @ARGV avant d'utiliser *ARGV (alias le diamant <>), $^I/-i travaillera sur ces fichiers au lieu de ce qui a été spécifié sur la ligne de commande.

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

Cela devrait faire exactement ce que vous voulez.

Si votre motif peut s'étendre sur plusieurs lignes, ajoutez un undef $/; avant le <> afin que Perl fonctionne sur un fichier entier à la fois et non ligne par ligne.

+0

Exactement ce dont j'avais besoin! – Seiti

2

Vous pouvez utiliser find:

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

Ceci listera tous les noms de fichiers récursive, puis xargs lira son stdin et exécuter le reste de la ligne de commande avec les noms de fichiers ajoutés à la fin. Une bonne chose à propos de xargs est qu'il va exécuter la ligne de commande plus d'une fois si la ligne de commande qu'il construit devient trop longue pour s'exécuter en une seule fois.

Notez que je ne suis pas sûr que find comprend complètement toutes les méthodes shell de sélection de fichiers, donc si la procédure ne fonctionne pas alors peut-être essayer:

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

Lors de l'utilisation des pipelines comme ça, j'aime pour construire la ligne de commande et exécuter chaque partie individuellement avant de continuer, pour s'assurer que chaque programme obtient l'entrée qu'il veut. Donc, vous pouvez exécuter la partie sans xargs d'abord pour le vérifier. Il me vient à l'esprit que même si vous ne l'avez pas dit, vous êtes probablement sur Windows en raison des suffixes de fichiers que vous recherchez. Dans ce cas, le pipeline ci-dessus pourrait être exécuté en utilisant Cygwin. Il est possible d'écrire un script Perl pour faire la même chose, comme vous avez commencé à le faire, mais vous devrez le modifier vous-même, car vous ne pouvez pas profiter du commutateur -i dans cette situation.

+0

Essayé trouver. -name '*. {cs, aspx, ascx}' sans chance, mais la version grep répertoriait les fichiers. Agréable! Mais quand j'exécute toutes les commandes, j'obtiens ceci: xargs: perl: Liste d'arguments trop longue – Seiti

+0

xargs peut également limiter le nombre d'arguments passés sur chaque ligne de commande, si elle ne peut pas déterminer la longueur maximale de la ligne de commande . Utilisez l'option -L ou -n pour xargs en fonction de la version (voir la page man). –

+0

Si vous utilisez find & xargs, utilisez -print0 et -0 pour éviter les problèmes avec les noms de fichiers contenant des espaces. trouver -print0 ... | xargs -0 ... – Schwern

4

changement

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

Pour

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

Cela suppose que le modèle ne couvre pas plusieurs lignes. Si le motif peut s'étendre sur des lignes, vous devez utiliser le contenu du fichier. ("slurp" est un terme Perl assez commun).

Le Chomp est pas vraiment nécessaire, je viens d'être mordue par des lignes qui n'étaient pas chomp ed trop de fois (si vous laissez tomber le chomp, changer print $out "$line\n"; à print $out $line;).

De même, vous pouvez remplacer open my $out, '>', "$f.out"; par open my $out, '>', undef; pour ouvrir un fichier temporaire, puis le recopier sur l'original une fois la substitution effectuée. En fait, et surtout si vous slurp dans le fichier entier, vous pouvez simplement faire la substitution en mémoire et ensuite écrire sur le fichier original. Mais j'ai fait assez d'erreurs en faisant cela que j'écris toujours dans un nouveau fichier, et vérifie le contenu.


Remarque, j'avais à l'origine une instruction if dans ce code. C'était probablement faux. Cela aurait seulement copié sur des lignes qui correspondaient à l'expression régulière "thisgoesout" (en la remplaçant par "thisgoesin" bien sûr) tout en engloutissant silencieusement le reste.

7

Vous pouvez être intéressé par File::Transaction::Atomic ou File::Transaction

Le SYNOPSIS F :: T :: A ressemble beaucoup à ce que vous essayez de faire:

# 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 qu'avec le fichier :: Vous avez déjà écrit, et vous devriez être prêt à partir.

6

Vous pouvez utiliser Tie :: File pour accéder de manière évolutive à des fichiers volumineux et les modifier. Voir la page de manuel (man 3perl Tie :: File).

+0

Pourquoi les pointer vers l'homme (3perl) au lieu de Perldoc? – ephemient

+0

Oui, Tie :: File a été créé pour ce genre de chose. – Schwern

+0

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

1

Merci à ephemient sur cette question et sur this answer, je suis arrivé ceci:

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

Maintenant, je peux même boucle à travers un hachage contenant regexp => entrées Subs!

+0

Vous devriez probablement 'local' mettre' @ ARGV' et '$^I' dans cette routine, car ces variables ont plutôt des effets globaux. – ephemient