Perl: find and replace a specific line in a text file with multiple headers

I need to get the entire .config file in a given directory, and in each of these files I need to find a specific line and replace it with another.

For example, if I have 3 files in this directory:

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

Please let me know how this can be done in Perl?

The following is the code I tried in shell scripts:

 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 
+8
regex shell replace perl
source share
6 answers

If you are on a Unix platform, you can do this using Perl on the command line; no need to write a script.

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

To be on the safer side, you can use the command with the backup option.

 perl -i.bak -p -e 's/old/new/g;' *.config 
+20
source share

Perl is just for modifying files here ... I donโ€™t understand why writing it in whole in perl if you can make it much easier:

 find . -maxdepth 1 -type f -name '*.conf' | \ xargs perl -i.bak -pe 's/localhost/example.com/;' 
+9
source share

If you really need to do this only with perl, which I do not recommend, as there are excellent and simpler answers already published, here goes:

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

Note that doing this with simple find and / shell substitutions is a lot easier. But take this as a little tutorial on how to handle perl files anyway.

+2
source share

Although this can be done from the command line, sometimes you just need an easily used script that provides a slightly more useful output. With that in mind, here is a perl solution with a friendly way out for anyone who comes across this issue.

 #!/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 (ie 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}; # ie $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
source share

Perhaps the following will be useful:

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

An Array Hash (HoA) is built using split ting strings | -delimited DATA, where the key is the name of the file, and the value is a reference to an anonymous array for which two elements are intended to be replaced by file. The notation local $^I = '.bak' backs up the source files.

You may need to change the lookup. For example, word boundaries are observed during substitution using \b in s/\b\Q$replacements{$file}[0]/$replacements{$file}[1]/g; . You may or should not (or want) it.

First, I will try to try it on only one โ€œscratchโ€ file, to make sure that you get the desired results before you fully implement it, even if the source files are copied.

0
source share

Your script is a good try.

It contains several layoffs:

  • useless cp $f
  • $TFILE also useless (just write sed output to the target file directly)

You can build $NEW and the name of the target file from the value of $f without the directory path, which you can get as follows:

 bf=`basename "$f"` 
0
source share

All Articles