2013-11-15 104 views
8

我需要让所有的config文件中给定的目录,并在这些文件我需要寻找特定字符串,并与其他基于文件替换替换特定字符串。的Perl:查找和mulitiple文本文件

对于e.g如果我有3个文件,在给定的目录:

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

请让我知道如何能在Perl做什么?

下面是我在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 
+2

你试过了什么? – devnull

+0

我尝试了shell脚本..但我再次无法为string_to_search和string_to_match声明数组。我对shell和perl脚本完全陌生。我将在这里发布我的shell脚本 – user2589079

+2

请更新您的帖子,而不是评论您的尝试。 – devnull

回答

9

的Perl这里只是修改文件...我不明白为什么把它写全在Perl中,如果你能做到这一点多少这样更简单:

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

我的要求不同,请参阅我的问题中的示例。这就是我不能和oneliner一起去的原因。 – user2589079

+0

@Jiri是否可以在上面的脚本中获取文件的名称作为变量? –

20

如果你在Unix上就像平台一样,你可以在命令行上用Perl来完成;不需要编写脚本。

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

为了安全起见,您可能希望将该命令与备份选项一起使用。

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

我认为“Jiri Xichtkniha”的答案更全面。似乎我只是重复他的努力 –

+0

我的要求是不同的请参阅我的问题中的示例。 – user2589079

2

如果你真正需要的只有perl的,我不建议,因为已经发布的优秀和更简单的答案,要做到这一点,这里有云:

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

请注意,这样做使用简单的查找和/或外壳通配符要简单得多。但是,把它作为一个关于如何用perl处理文件的小教程。

+0

感谢您的回复。但我的用例是不同的每个文件,我有不同的字符串搜索和替换。想想一个数组,我可以有一个文件名和字符串使用。请参考我的问题 – user2589079

0

也许以下将是有用的:

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

阵列的哈希(HOA)由split| -delimited DATA线,其中关键是文件名和值内置是参考到一个匿名数组,其两个元素用于替换文件。该记号创建原始文件的备份。

您可能需要调整的替代。例如,在s/\b\Q$replacements{$file}[0]/$replacements{$file}[1]/g;中使用\b代替字符边界。你可能会也可能不需要(或想要)这个。

我建议首先在尝试它只是一个“从无到有”的文件,以确保你得到你想要的结果,充分实现它 - 即使原始文件进行备份。

1

虽然它可以通过命令行来完成,有时你只是想要一个容易使用的脚本,提供更多的有用的输出。考虑到这一点,这里有一个perl解决方案,可以为任何遇到此问题的人提供友好的输出。

#!/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; 
} 
0

你的脚本是一个很好的尝试。

它包含了一些冗余:

  • 是没用的,cp$f
  • $TFILE是无用的,以及(只写sed输出直接目标文件)

你可以构造$NEW和目标文件名的值为$f而没有目录路径,您可以获得如下:

bf=`basename "$f"`