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.