更改文件夹中的所有匹配项

我需要对一个文件夹(及其子文件夹)中的所有文件进行正则表达式查找和替换。那么 Linux shell 命令是如何做到这一点的呢?

例如,我想在所有文件上运行它,并用新的替换文本覆盖旧文件。

sed 's/old text/new text/g'
121843 次浏览

我可以建议(在备份你的文件之后) :

find /the/folder -type f -exec sed -ibak 's/old/new/g' {} ';'

只使用 sed 是不可能完成的,您至少需要同时使用 find 实用程序:

find . -type f -exec sed -i.bak "s/foo/bar/g" {} \;

此命令将为每个更改的文件创建一个 .bak文件。

备注:

  • sed命令的 -i参数是 GNU 扩展名,因此,如果使用 BSD 的 sed运行此命令,则需要将输出重定向到一个新文件,然后重命名它。
  • find实用程序没有在旧的 UNIX 框中实现 -exec参数,因此,您将需要使用 | xargs来代替。

试试 我的大规模搜索/替换 Perl 脚本。与链式实用程序解决方案相比,它有一些优势(比如不必处理多级 shell 元字符解释)。

#!/usr/bin/perl


use strict;


use Fcntl qw( :DEFAULT :flock :seek );
use File::Spec;
use IO::Handle;


die "Usage: $0 startdir search replace\n"
unless scalar @ARGV == 3;
my $startdir = shift @ARGV || '.';
my $search = shift @ARGV or
die "Search parameter cannot be empty.\n";
my $replace = shift @ARGV;
$search = qr/\Q$search\E/o;


my @stack;


sub process_file($) {
my $file = shift;
my $fh = new IO::Handle;
sysopen $fh, $file, O_RDONLY or
die "Cannot read $file: $!\n";
my $found;
while(my $line = <$fh>) {
if($line =~ /$search/) {
$found = 1;
last;
}
}
if($found) {
print "  Processing in $file\n";
seek $fh, 0, SEEK_SET;
my @file = <$fh>;
foreach my $line (@file) {
$line =~ s/$search/$replace/g;
}
close $fh;
sysopen $fh, $file, O_WRONLY | O_TRUNC or
die "Cannot write $file: $!\n";
print $fh @file;
}
close $fh;
}


sub process_dir($) {
my $dir = shift;
my $dh = new IO::Handle;
print "Entering $dir\n";
opendir $dh, $dir or
die "Cannot open $dir: $!\n";
while(defined(my $cont = readdir($dh))) {
next
if $cont eq '.' || $cont eq '..';
# Skip .swap files
next
if $cont =~ /^\.swap\./o;
my $fullpath = File::Spec->catfile($dir, $cont);
if($cont =~ /$search/) {
my $newcont = $cont;
$newcont =~ s/$search/$replace/g;
print "  Renaming $cont to $newcont\n";
rename $fullpath, File::Spec->catfile($dir, $newcont);
$cont = $newcont;
$fullpath = File::Spec->catfile($dir, $cont);
}
if(-l $fullpath) {
my $link = readlink($fullpath);
if($link =~ /$search/) {
my $newlink = $link;
$newlink =~ s/$search/$replace/g;
print "  Relinking $cont from $link to $newlink\n";
unlink $fullpath;
my $res = symlink($newlink, $fullpath);
warn "Symlink of $newlink to $fullpath failed\n"
unless $res;
}
}
next
unless -r $fullpath && -w $fullpath;
if(-d $fullpath) {
push @stack, $fullpath;
} elsif(-f $fullpath) {
process_file($fullpath);
}
}
closedir($dh);
}


if(-f $startdir) {
process_file($startdir);
} elsif(-d $startdir) {
@stack = ($startdir);
while(scalar(@stack)) {
process_dir(shift(@stack));
}
} else {
die "$startdir is not a file or directory\n";
}

对于可移植性,我不依赖于特定于 linux 或 BSD 的 sed 特性。相反,我使用了 Kernighan 和 Pike 关于 Unix 编程环境的书中的 overwrite脚本。

命令就是

find /the/folder -type f -exec overwrite '{}' sed 's/old/new/g' {} ';'

overwrite脚本(我在各处都使用它)是

#!/bin/sh
# overwrite:  copy standard input to output after EOF
# (final version)


# set -x


case $# in
0|1)        echo 'Usage: overwrite file cmd [args]' 1>&2; exit 2
esac


file=$1; shift
new=/tmp/$$.new; old=/tmp/$$.old
trap 'rm -f $new; exit 1' 1 2 15    # clean up files


if "$@" >$new               # collect input
then
cp $file $old   # save original file
trap 'trap "" 1 2 15; cp $old $file     # ignore signals
rm -f $new $old; exit 1' 1 2 15   # during restore
cp $new $file
else
echo "overwrite: $1 failed, $file unchanged" 1>&2
exit 1
fi
rm -f $new $old

其思想是,只有当命令成功时,它才会覆盖文件。在 find中很有用,在您不想使用的地方也很有用

sed 's/old/new/g' file > file  # THIS CODE DOES NOT WORK

因为 shell 会在 sed读取文件之前截断它。

我更喜欢使用 find | xargs cmd而不是 find -exec,因为它更容易记住。

这个例子在全局范围内用.txt 文件中的“ bar”替换你工作目录下面的“ foo”:

find . -type f -name "*.txt" -print0 | xargs -0 sed -i "s/foo/bar/g"

如果您的文件名不包含时髦的字符(如空格) ,则可以省略 -print0-0选项。

如果文件夹中的文件名有一些常规名称(比如 file1、 file2...) ,我已经在循环中使用过了。

for i in {1..10000..100}; do sed 'old\new\g' 'file'$i.xml > 'cfile'$i.xml; done

示例: 在/app/config/文件夹及其子文件夹下的所有 ini 文件中,将{ AutoStart }替换为1:

sed -i 's/{AutoStart}/1/g' /app/config/**/*.ini
for i in $(ls);do sed -i 's/old_text/new_text/g' $i;done

这对我很有用(在 Mac 终端上,在 Linux 上你不需要 '' -e) :

sed -i '' -e 's/old text/new text/g' `grep 'old text' -rl *`

命令 grep 'old text' -rl *列出了存在“旧文本”的工作目录(和子目录)中的所有文件。然后在 sed 中传递这个。

如果您担心会删除您意外地没有考虑到的文件,那么您可以首先使用递归选项运行 grep来查看哪些文件可能会被更改:

grep -r 'searchstring' *

这样就不难组合一些东西来对每个文件运行替换:

for f in $(grep -r 'searchstring' *)
do
sed -i -e 's/searchstring/replacement/g' "$f"
done

(由于 -i inplace 选项是 GNU 扩展,因此只有 GNU sed-调整有效才能实现 POSIX 兼容性) :