sed новичок: изменение всех вхождений в папке


98

Мне нужно найти и заменить регулярное выражение для всех файлов в папке (и ее подпапках). Какой была бы команда оболочки Linux для этого?

Например, я хочу запустить это для всех файлов и перезаписать старый файл новым замененным текстом.

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

Ответы:


150

Невозможно сделать это, используя только sed. Вам нужно будет использовать вместе хотя бы утилиту find:

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

Эта команда создаст .bakфайл для каждого измененного файла.

Примечания:

  • -iАргумент sedкоманды является расширением GNU, поэтому, если вы работаете в эту команду с BSD это sedвам нужно будет перенаправить вывод в новый файл , то переименовать его.
  • findУтилита не реализует -execаргумент в старых коробках UNIX, поэтому вам нужно будет использовать | xargsвместо.

4
Для чего \;?
Андрей Макуха

4
Нам нужно указать, где команда аргумента -exec заканчивается на ";". Но оболочка использует тот же символ (;) в качестве разделителя команд оболочки, поэтому нам нужно экранировать ";" из оболочки, чтобы передать его аргументу find -exec.
osantana

2
Стоит отметить, что -iсам по себе файл резервной копии не создается, и именно это заставляет sed выполнить операцию с файлом на месте.
Кайл

1
Для чего {}?
somenickname

1
Он {}будет заменен каждым именем файла, найденным findи \;сообщает, что команда, которую он должен выполнить, завершается на этом этапе.
osantana

53

Я предпочитаю использовать find | xargs cmdover, find -execпотому что это легче запомнить.

В этом примере глобально заменяется «foo» на «bar» в файлах .txt в текущем каталоге или ниже него:

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

Параметры -print0и -0можно не указывать, если ваши имена файлов не содержат необычных символов, таких как пробелы.


3
Если вы используете OSX, попробуйте find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' "s/foo/bar/g"(обратите внимание на то, что в -iаргументе указывается пустая строка ).
Якуб

В MacOS запустите sed -i.bakвместо sed -i. Я думаю, что, как упоминал @JakubKukul, sed -i ''тоже работает.
forzagreen

7

Что касается переносимости, я не полагаюсь на особенности sed, специфичные для Linux или BSD. Вместо этого я использую overwriteсценарий из книги Кернигана и Пайка о среде программирования Unix.

Затем команда

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сможет его прочитать.


3

Могу я предложить (после резервного копирования ваших файлов):

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

0

Пример: замените {AutoStart} на 1 для всех ini-файлов в папке / app / config / и ее дочерних папках:

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

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

5
Пожалуйста, объясните свой ответ.
Отсев

Хотя этот код может решить проблему OP, лучше включить объяснение того, как ваш код решает проблему OP. Таким образом, будущие посетители смогут извлечь уроки из вашего сообщения и применить его к своему собственному коду. SO - это не сервис кодирования, а ресурс знаний. Качественные и полные ответы подкрепляют эту идею и с большей вероятностью получат поддержку. Эти функции, а также требование, чтобы все сообщения были автономными, являются некоторыми сильными сторонами SO как платформы, которая отличает нас от форумов. Вы можете редактировать, чтобы добавить дополнительную информацию и / или дополнить свои пояснения исходной документацией.
SherylHohman

2
Если вы не можете это прочитать, просто забудьте мой ответ. Это просто основы bash.
DimiDak

0

Это сработало для меня (на терминале Mac, в Linux вам не нужно '' -e):

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

команда grep 'old text' -rl *выводит список всех файлов в рабочем каталоге (и подкаталогах), где существует «старый текст». Затем это передается в sed.


-1

Возможно, захочется попробовать мой сценарий массового поиска / замены Perl . Имеет некоторые преимущества по сравнению с решениями на основе цепочки (например, отсутствие необходимости иметь дело с несколькими уровнями интерпретации метасимволов оболочки).

#!/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";
}

-3

В случае, если имя файлов в папке имеет некоторые обычные имена (например, файл1, файл2 ...), я использовал для цикла.

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

это не связано с заданным вопросом. В вопросе ничего не говорится об одном и том же шаблоне имени файла / папки. Пожалуйста, избегайте таких ответов
Кунал Парех
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.