我需要对文件夹(及其子文件夹)中的所有文件进行正则表达式查找和替换。执行此操作的 linux shell 命令是什么?
例如,我想在所有文件上运行它,并用新的替换文本覆盖旧文件。
sed 's/old text/new text/g'
仅使用 sed 是无法做到的。您至少需要一起使用 find 实用程序:
find . -type f -exec sed -i.bak "s/foo/bar/g" {} \;
此命令将为每个更改的文件创建一个 .bak
文件。
笔记:
sed 命令的 -i 参数是一个 GNU 扩展,因此,如果您使用 BSD 的 sed 运行此命令,您需要将输出重定向到一个新文件,然后重命名它。
find 实用程序没有在旧的 UNIX 框中实现 -exec 参数,因此,您需要使用 | xargs 代替。
我更喜欢使用 find | xargs cmd
而不是 find -exec
,因为它更容易记住。
此示例在当前目录或当前目录下的 .txt 文件中将“foo”全局替换为“bar”:
find . -type f -name "*.txt" -print0 | xargs -0 sed -i "s/foo/bar/g"
如果您的文件名不包含空格等时髦字符,则可以省略 -print0
和 -0
选项。
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' "s/foo/bar/g"
(注意为 -i
参数提供一个空字符串)。
sed -i.bak
而不是 sed -i
。我认为正如@JakubKukul 所提到的,sed -i ''
也有效。
对于可移植性,我不依赖 sed 特定于 linux 或 BSD 的特性。相反,我使用 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
因为在 sed
可以读取文件之前,shell 会截断文件。
我可以建议(备份文件后):
find /the/folder -type f -exec sed -ibak 's/old/new/g' {} ';'
示例:对于 /app/config/ 文件夹及其子文件夹下的所有 ini 文件,将 {AutoStart} 内联替换为 1:
sed -i 's/{AutoStart}/1/g' /app/config/**/*.ini
sed -i 's/{AutoStart}/1/g' /app/config/**/*.ini
这对我有用(在 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
(仅对 GNU sed 有效 - POSIX 兼容性需要进行调整,因为 -i
就地选项是 GNU 扩展):
可能想试试 my mass search/replace Perl script。与链式实用程序解决方案相比具有一些优势(例如不必处理多个级别的 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";
}
for i in $(ls);do sed -i 's/old_text/new_text/g' $i;done
如果文件夹中的文件名有一些常规名称(如 file1、file2...),我已将其用于循环。
for i in {1..10000..100}; do sed 'old\new\g' 'file'$i.xml > 'cfile'$i.xml; done
不定期副业成功案例分享
\;
有什么用?-i
本身不会创建备份文件,它是导致 sed 对文件执行就地操作的原因。{}
是做什么用的?{}
将替换为find
找到的每个文件名,并且\;
告诉找到他需要执行的命令此时完成。