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

Changing all occurrences in a folder

提问人:nickf 提问时间:5/25/2009 最后编辑:Dharmannickf 更新时间:6/29/2022 访问量:138000

问:

我需要对文件夹(及其子文件夹)中的所有文件进行正则表达式查找和替换。linux shell 命令是什么?

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

sed 's/old text/new text/g' 
正则表达式 Linux Shell SED

评论

0赞 Vijay 5/13/2013
theunixshell.blogspot.com/2012/12/......

答:

3赞 paxdiablo 5/25/2009 #1

我可以建议(备份文件后):

find /the/folder -type f -exec sed -ibak 's/old/new/g' {} ';'
204赞 osantana 5/25/2009 #2

仅使用 sed 是没有办法做到的。您至少需要同时使用 find 实用程序:

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

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

笔记:

  • command 的参数是 GNU 扩展,因此,如果您使用 BSD 运行此命令,则需要将输出重定向到新文件,然后重命名它。-isedsed
  • 该实用程序不会在旧的 UNIX 框中实现该参数,因此,您需要改用 。find-exec| xargs

评论

12赞 Andriy Makukha 7/25/2019
这是做什么用的?\;
9赞 osantana 7/29/2019
我们需要告诉要找到参数 -exec 的命令以“;”结尾的位置。但是 shell 使用相同的符号 (;) 作为 shell 命令分隔符,因此,我们需要从 shell 中转义 “;” 以将其传递给 find 的 -exec 参数。
5赞 Kyle 8/29/2019
值得注意的是,它本身不会创建备份文件,并且是导致 sed 就地对文件执行操作的原因。-i
2赞 somenickname 6/6/2020
什么是干什么用的?{}
4赞 osantana 6/7/2020
将替换为找到的每个文件名,并告诉找到他需要执行的命令此时完成。{}find\;
-1赞 chaos 5/25/2009 #3

可能想尝试我的大规模搜索/替换 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";
}
8赞 Norman Ramsey 5/25/2009 #4

为了便携性,我不依赖特定于 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

80赞 Dennis 1/21/2015 #5

我更喜欢使用over,因为它更容易记住。find | xargs cmdfind -exec

此示例全局将当前目录或下方的 .txt 文件中的“foo”替换为“bar”:

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

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

评论

8赞 Jakub Kukul 3/27/2017
如果您使用的是 OSX,请尝试(请注意,为参数提供空字符串)。find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' "s/foo/bar/g"-i
1赞 forzagreen 10/29/2020
在 MacOS 上,运行而不是 .我认为正如@JakubKukul所提到的,也有效。sed -i.baksed -ised -i ''
-2赞 tereza 8/21/2016 #6

如果文件夹中的文件名有一些常规名称(如 file1、file2...我用过循环。

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

评论

0赞 Kunal Parekh 10/16/2017
这与提出的问题无关。该问题没有提到任何关于相同文件/文件夹名称模式的内容。请避免这样的答案
7赞 Alan Hu 6/8/2020 #7

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

sed -i 's/{AutoStart}/1/g' /app/config/**/*.ini

评论

0赞 Arcsector 6/25/2022
这不会替换它,它只是打印出替换 - 如果要替换它,请添加内联标志:sed -i 's/{AutoStart}/1/g' /app/config/**/*.ini
-1赞 DimiDak 6/16/2020 #8
for i in $(ls);do sed -i 's/old_text/new_text/g' $i;done 

评论

6赞 Dropout 6/16/2020
请解释你的答案。
2赞 SherylHohman 6/16/2020
虽然此代码可以解决 OP 的问题,但最好包含有关代码如何解决 OP 问题的说明。这样,未来的访问者可以从您的帖子中学习,并将其应用于他们自己的代码。SO 不是编码服务,而是知识资源。高质量、完整的答案强化了这一想法,并且更有可能被点赞。这些功能,加上所有帖子都自包含的要求,是 SO 作为一个平台的一些优势,使我们与论坛区分开来。您可以编辑以添加其他信息和/或使用源文档补充您的解释。
0赞 armourbear 8/24/2021
这不会解决问题,因为它只列出当前工作目录,而不列出子文件夹
2赞 Foivos 9/16/2020 #9

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

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

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

0赞 moo 1/29/2022 #10

如果您担心不小心没有考虑的文件被破坏,您可以先使用递归选项运行,以查看哪些文件可能会被更改:grep

grep -r 'searchstring' *

然后,将一些东西放在一起,以在每个文件上运行替换并不难:

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

(仅对 GNU sed 有效 - POSIX 兼容性需要调整,因为 inplace 选项是 GNU 扩展):-i