简体   繁体   English

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

[英]Changing all occurrences in a folder

I need to do a regex find and replace on all the files in a folder (and its subfolders).我需要对文件夹(及其子文件夹)中的所有文件进行正则表达式查找和替换。 What would be the linux shell command to do that?执行此操作的 linux shell 命令是什么?

For example, I want to run this over all the files and overwrite the old file with the new, replaced text.例如,我想在所有文件上运行它,并用新的替换文本覆盖旧文件。

sed 's/old text/new text/g' 

There is no way to do it using only sed.仅使用 sed 是无法做到的。 You'll need to use at least the find utility together:您至少需要一起使用 find 实用程序:

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

This command will create a .bak file for each changed file.此命令将为每个更改的文件创建一个.bak文件。

Notes:笔记:

  • The -i argument for sed command is a GNU extension, so, if you are running this command with the BSD's sed you will need to redirect the output to a new file then rename it. sed命令的-i参数是一个 GNU 扩展,因此,如果您使用 BSD 的sed运行此命令,您需要将输出重定向到一个新文件,然后重命名它。
  • The find utility does not implement the -exec argument in old UNIX boxes, so, you will need to use a | xargs find实用程序没有在旧的 UNIX 框中实现-exec参数,因此,您需要使用| xargs | xargs instead. | xargs代替。

I prefer to use find | xargs cmd我更喜欢使用find | xargs cmd find | xargs cmd over find -exec because it's easier to remember. find | xargs cmd优于find -exec因为它更容易记住。

This example globally replaces "foo" with "bar" in .txt files at or below your current directory:此示例在当前目录或当前目录下的 .txt 文件中将“foo”全局替换为“bar”:

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

The -print0 and -0 options can be left out if your filenames do not contain funky characters such as spaces.如果您的文件名不包含空格等时髦字符,则可以省略-print0-0选项。

For portability, I don't rely on features of sed that are specific to linux or BSD.对于可移植性,我不依赖 sed 特定于 linux 或 BSD 的特性。 Instead I use the overwrite script from Kernighan and Pike's book on the Unix Programming Environment.相反,我使用 Kernighan 和 Pike 关于 Unix 编程环境的书中的overwrite脚本。

The command is then然后命令是

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

And the overwrite script (which I use all over the place) is 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

The idea is that it overwrites a file only if a command succeeds.这个想法是它仅在命令成功时才会覆盖文件。 Useful in find and also where you would not want to usefind以及您不想使用的地方很有用

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

because the shell truncates the file before sed can read it.因为在sed可以读取文件之前,shell 会截断文件。

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

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

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

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

This worked for me (on mac terminal, on Linux you don't need '' -e ):这对我有用(在 mac 终端上,在 Linux 上你不需要'' -e ):

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

the command grep 'old text' -rl * lists all files in the working directory (and subdirectories) where "old text" exists.命令grep 'old text' -rl *列出工作目录(和子目录)中存在“旧文本”的所有文件。 This then is passed in sed.然后在 sed 中传递。

If you are worried about clobbering files that you accidentally haven't considered, you can run grep with the recursive option first to see which files will be potentially changed:如果您担心会破坏您不小心没有考虑到的文件,您可以先使用递归选项运行grep以查看哪些文件可能会被更改:

grep -r 'searchstring' *

It's then not hard to put together something that will run the replacement on each of these files:然后不难将可以在每个文件上运行替换的东西放在一起:

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

(Only valid with GNU sed - adjustments would be needed for POSIX compatibility as the -i inplace option is a GNU extension): (仅对GNU sed有效 - POSIX 兼容性需要进行调整,因为-i inplace 选项是 GNU 扩展):

for i in $(ls);do sed -i 's/old_text/new_text/g' $i;done 

Might want to try my mass search/replace Perl script .可能想试试我的批量搜索/替换 Perl 脚本 Has some advantages over chained-utility solutions (like not having to deal with multiple levels of shell metacharacter interpretation).与链式实用程序解决方案相比具有一些优势(例如不必处理多个级别的 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";
}

如果文件夹中的文件名有一些常规名称(如 file1、file2...),我已将其用于循环。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM