it-swarm.com.de

Perl: Bestimmte Zeichenfolge in mehreren Textdateien suchen und ersetzen

Ich muss die gesamte .config-Datei in einem bestimmten Verzeichnis abrufen. In jeder dieser Dateien muss ich nach einer bestimmten Zeichenfolge suchen und sie durch eine andere ersetzen, die auf der Datei basiert.

Zum Beispiel, wenn ich 3 Dateien im angegebenen Verzeichnis habe:

 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[\/ >"

Bitte lassen Sie mich wissen, wie dies in Perl möglich ist.

Hier ist der Code, den ich beim Shell-Scripting ausprobiert habe:

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

Wenn Sie sich auf einer Unix-ähnlichen Plattform befinden, können Sie dies mit Perl in der Befehlszeile tun. keine Notwendigkeit, ein Skript zu schreiben.

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

Um auf der sicheren Seite zu sein, können Sie den Befehl mit der Sicherungsoption verwenden.

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

Perl ist hier nur zum Ändern von Dateien. Ich verstehe nicht, warum ich es in Perl schreiben soll, wenn Sie es viel einfacher machen können:

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

Für den Fall, dass Sie dies wirklich nur mit Perl tun müssen, was ich nicht empfehlen kann, da bereits hervorragende und einfachere Antworten veröffentlicht wurden.

#!/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.

Bitte beachten Sie, dass dies mit einfachen Such- und/oder Shell-Platzhaltern viel einfacher ist. Nehmen Sie dies jedoch als kleines Tutorial zur Verarbeitung von Dateien mit Perl auf.

2
Sami Laine

Obwohl dies über die Befehlszeile möglich ist, möchten Sie manchmal einfach ein einfach zu verwendendes Skript, das eine etwas nützlichere Ausgabe liefert. Hier ist eine Perl-Lösung mit freundlicher Ausgabe für alle, die sich dieser Frage stellen.

#!/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

Vielleicht wird folgendes hilfreich sein:

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[/ >

Ein Hash von Arrays (HoA) wird durch splitting der |-getrennten DATA-Zeilen erstellt, wobei der Schlüssel der Dateiname ist und der Wert eine Referenz auf ein anonymes Array ist, dessen zwei Elemente für die Ersetzung der Datei dienen. Die local $^I = '.bak'-Notation erstellt Sicherungen der Originaldateien.

Möglicherweise müssen Sie die Substitution anpassen. Beispielsweise werden Wortgrenzen bei der Ersetzung berücksichtigt, indem \b in s/\b\Q$replacements{$file}[0]/$replacements{$file}[1]/g; verwendet wird. Möglicherweise brauchen oder brauchen Sie dies nicht.

Ich empfehle Ihnen, es nur mit einer "Scratch" -Datei auszuprobieren, um sicherzustellen, dass Sie die gewünschten Ergebnisse erhalten, bevor Sie sie vollständig implementieren - auch wenn die Originaldateien gesichert sind.

0
Kenosis

Dein Skript ist ein guter Versuch.

Es enthält einige Entlassungen:

  • es ist nutzlos, cp$f
  • $TFILE ist auch unbrauchbar (schreibe einfach die sed-Ausgabe direkt in die Zieldatei)

Sie können $NEW und den Zieldateinamen aus dem Wert von $f ohne den Verzeichnispfad erstellen, den Sie wie folgt erhalten können: 

bf=`basename "$f"`
0
reinierpost