web-dev-qa-db-fra.com

Perl: Rechercher et remplacer une chaîne spécifique dans plusieurs fichiers texte

Je dois obtenir tous les fichiers .config dans un répertoire donné et dans chacun de ces fichiers, je dois rechercher une chaîne spécifique et la remplacer par une autre en fonction du fichier.

Par exemple, si j'ai 3 fichiers dans le répertoire donné:

 for  my_foo.config - string to search "fooCommon >" replace with "~ /fooCommon[\/ >"
 for  my_bar.config - string to search "barCommon >" replace with "~ /barCommon[\/ >"
 for  my_file.config - string to search "someCommon >" replace with "~ /someCommon[\/ >"

S'il vous plaît laissez-moi savoir comment cela peut être fait en Perl?

Voici le code que j'ai essayé dans les scripts Shell:

OLD="\/fooCommon >"
NEW="~ \"\/fooCommon[^\/]*\" >"
DPATH="/myhome/aru/conf/Host*.conf"
BPATH="/myhome/aru/conf/bakup"
TFILE="/myhome/aru/out.tmp.$$"
[ ! -d $BPATH ] && mkdir -p $BPATH || :
for f in $DPATH
do
  if [ -f $f -a -r $f ]; then
   /bin/cp -f $f $BPATH
   echo sed \"s\/$OLD\/$NEW\/g\"
   sed "s/$OLD/$NEW/g" "$f" > $TFILE && mv $TFILE "$f"
  else
   echo "Error: Cannot read $f"

fi
done
/bin/rm $TFILE
10
user2589079

Si vous êtes sur Unix comme une plate-forme, vous pouvez le faire en utilisant Perl sur la ligne de commande; pas besoin d'écrire un script.

Perl -i -p -e 's/old/new/g;' *.config

Pour être plus sûr, vous pouvez utiliser la commande avec l'option de sauvegarde.

Perl -i.bak  -p -e 's/old/new/g;' *.config
23
Pankaj Vaidya

Perl n’est là que pour modifier des fichiers ... Je ne comprends pas pourquoi l’écrire tout en Perl si vous pouvez le faire beaucoup plus simplement comme ceci:

find . -maxdepth 1 -type f -name '*.conf' | \
    xargs Perl -i.bak -pe 's/localhost/example.com/;'
10
jirib

Au cas où vous auriez vraiment besoin de faire cela avec Perl uniquement, ce que je ne recommande pas, car il existe déjà des réponses simples et excellentes, voici ce qui suit:

#!/usr/bin/Perl

# take the directory to be processed from first command line argument
opendir($dh, $ARGV[0]);
# take only relevant files ie. "*.config"
@cfgs = grep { /\.config$/ } readdir($dh);
# loop through files
foreach(@cfgs) {
  # generate source string from the filename
  ($s) = ($_ =~ /.*_(\w+)\.config.*/);
  $s = "${s}Common";
  # generate replacement string from the filename
  $r = "~ /${s}[/ >";
  # move original file to a backup
  rename("${ARGV[0]}${_}", "${ARGV[0]}${_}.bak");
  # open backup file for reading
  open(I, "< ${ARGV[0]}${_}.bak");
  # open a new file, with original name for writing
  open(O, "> ${ARGV[0]}${_}");
  # go through the file, replacing strings
  while(<I>) { $_ =~ s/$s/$r/g; print O $_; }
  # close files
  close(I);
  close(O);
}

# end of file.

Notez que cela est beaucoup plus simple. Mais prenez cela comme un petit tutoriel sur la façon de traiter les fichiers avec Perl de toute façon.

2
Sami Laine

Bien que cela puisse être fait à partir de la ligne de commande, vous voulez parfois un script facilement utilisable qui fournit un résultat un peu plus utile. Dans cet esprit, voici une solution Perl avec une sortie conviviale pour tous ceux qui rencontrent cette question.

#!/usr/bin/env Perl5.8.3

# subst [-v] [-f] "re/string to find" "string to replace" -- list of files
#  optional -v flag shows each line with replacement, must be 1st arg to script
#  optional -f flag says to disable regexp functionality and make the strings match exactly
#  replacement string may include back references ($1, $2, etc) to items in "string to find" if they are surrounded by grouping parenthesis

use strict;
use warnings;
use List::Util;
use IO::File;
use Fcntl;
use Getopt::Long qw(GetOptions);

my $verbose = 0;
my $fixed   = 0;

GetOptions("v" => \$verbose,
           "f" => \$fixed);

my $find    = shift @ARGV;
my $replace = shift @ARGV;

die "Error: missing 1st arg, string to find\n"         if not defined $find;
die "Error: missing 2nd arg, string to replace with\n" if not defined $replace;
die "No files were specified\n"                        if @ARGV == 0;

# open a temp file for writing changes to
my $TEMP = IO::File->new_tmpfile;
if (not defined $TEMP)
{
    print STDERR "ERROR: failed to create temp file: $!\n";
    exit 1;
}

# Fix max file name width for printing
my $fwidth = List::Util::max map { length $_ } @ARGV;

# Process each file
my $unchanged = 0;
my $changed   = 0;
foreach my $file (@ARGV)
{
    if (open(my $FILE, '<', $file))
    {
        # Reset temp file
        seek $TEMP, 0, SEEK_SET or die "ERROR: seek in temp file failed: $!";
        truncate $TEMP, 0       or die "ERROR: truncate of temp file failed: $!";

        # go through the file, replacing strings
        my $changes = 0;
        while(defined(my $line = <$FILE>))
        {
            if ($line =~ m/$find/g)
            {
                print "-" . $line if $verbose;
                print "\n" if $verbose and $line !~ m/\n$/;

                if ($fixed)
                {
                    my $index = index($line, $find);
                    substr($line, $index, length($find)) = $replace;
                }
                else
                {
                    $line =~ s/$find/replacebackrefs($replace)/eg;
                }

                $changes++;
                print "+" . $line if $verbose;
                print "\n" if $verbose and $line !~ m/\n$/;
            }

            print $TEMP $line;
        }
        close $FILE;

        if ($changes == 0)
        {
            $unchanged++;
            unlink("/tmp/subst$$");
            next;
        }

        # Move new contents into old file
        $changed++;
        printf "%*s - %3d changes\n", -$fwidth, $file, $changes;

        seek $TEMP, 0, SEEK_SET or die "ERROR: rewind of temp file failed: $!";
        open $FILE, '>', $file or die "ERROR: failed to re-write $file: $!\n";
        while (<$TEMP>) { print $FILE $_ }
        close $FILE;

        print "\n" if $verbose;
    }
    else
    {
        print STDERR "Error opening $file: $!\n";
    }
}

close $TEMP;

print "\n";
print "$changed files changed, $unchanged files unchanged\n";

exit 0;

sub replacebackrefs
{
    # 1st/only argument is the text matched
    my $matchedtext = shift @_;

    my @backref;
    # @- is a dynamic variable that holds the offsets of submatches in
    # the currently active dynamic scope (i.e. within each regexp
    # match), corresponding to grouping parentheses. We use the count
    # of entrees in @- to determine how many matches there were and
    # store them into an array. Note that @- index [0] is not
    # interesting to us because it has a special meaning (see man
    # perlvar for @-)\, and that backrefs start with $1 not $0.
    # We cannot do the actual replacement within this loop.
    do
    {
        no strict 'refs'; # turn of warnings of dynamic variables
        foreach my $matchnum (1 .. $#-)
        {
            $backref[$matchnum] = ${$matchnum}; # i.e. $1 or $2 ...
        }
    } while(0);

    # now actually replace each back reference in the matched text
    # with the saved submatches.
    $matchedtext =~ s/\$(\d+)/$backref[$1]/g;

    # return a scalar string to actually use as the replacement text,
    # with all the backreferences in the matched text replaced with
    # their submatch text.
    return $matchedtext;
}
1
simpleuser

Peut-être que ce qui suit sera utile:

use strict;
use warnings;

my %replacements =
  map { chomp; my @x = split /\|/; $x[0] => [ $x[1], $x[2] ] } <DATA>;

local $^I = '.bak';

for my $file (<*.config>) {
    Push @ARGV, $file;

    while (<>) {
        s/\b\Q$replacements{$file}[0]/$replacements{$file}[1]/g;
        print;
    }
}

__DATA__
my_foo.config|fooCommon >|~ /fooCommon[/ >
my_bar.config|barCommon >|~ /barCommon[/ >
my_file.config|someCommon >|~ /someCommon[/ >

Un hachage de tableaux (HoA) est généré par splitte les lignes de données délimitées par |-, où la clé est le nom du fichier et la valeur est une référence à un tableau anonyme dont les deux éléments servent à la substitution sur le fichier. La notation local $^I = '.bak' crée des sauvegardes des fichiers originaux.

Vous devrez peut-être ajuster la substitution. Par exemple, les limites de Word sont observées dans la substitution en utilisant \b dans s/\b\Q$replacements{$file}[0]/$replacements{$file}[1]/g;. Vous pouvez ou non avoir besoin (ou vouloir) cela.

Je suggérerais de l'essayer d'abord sur un seul fichier de travail, afin de vous assurer d'obtenir les résultats souhaités avant de les implémenter, même si les fichiers d'origine sont sauvegardés.

0
Kenosis

Votre script est une bonne tentative.

Il contient quelques redondances:

  • il est inutile de cp$f
  • $TFILE est également inutile (écrivez simplement la sortie sed dans le fichier cible directement)

Vous pouvez construire $NEW et le nom du fichier cible à partir de la valeur de $f sans le chemin du répertoire, que vous pouvez obtenir comme suit: 

bf=`basename "$f"`
0
reinierpost