我需要对一个文件夹(及其子文件夹)中的所有文件进行正则表达式查找和替换。那么 Linux shell 命令是如何做到这一点的呢?
例如,我想在所有文件上运行它,并用新的替换文本覆盖旧文件。
sed 's/old text/new text/g'
我可以建议(在备份你的文件之后) :
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文件。
.bak
备注:
sed
-i
find
-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脚本。
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,因为它更容易记住。
find | xargs cmd
find -exec
这个例子在全局范围内用.txt 文件中的“ bar”替换你工作目录下面的“ foo”:
find . -type f -name "*.txt" -print0 | xargs -0 sed -i "s/foo/bar/g"
如果您的文件名不包含时髦的字符(如空格) ,则可以省略 -print0和 -0选项。
-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) :
'' -e
sed -i '' -e 's/old text/new text/g' `grep 'old text' -rl *`
命令 grep 'old text' -rl *列出了存在“旧文本”的工作目录(和子目录)中的所有文件。然后在 sed 中传递这个。
grep 'old text' -rl *
如果您担心会删除您意外地没有考虑到的文件,那么您可以首先使用递归选项运行 grep来查看哪些文件可能会被更改:
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 兼容性) :