sed для початківців: зміна всіх випадків у папці


98

Мені потрібно зробити регулярний вираз пошуку та заміни всіх файлів у папці (та її підпапках). Якою буде команда оболонки Linux для цього?

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

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

Відповіді:


148

Неможливо зробити це, використовуючи лише 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.
Осантана

2
Варто зазначити, що -iсам по собі не створює файл резервної копії, і саме це змушує sed виконувати операцію з цим файлом на місці.
Kyle

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

1
{}Буде замінено кожного файлу , знайденого findі \;каже , щоб знайти , що команда , яку він повинен виконати обробку в цій точці.
Осантана,

51

Я вважаю за краще використовувати find | xargs cmdбільше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аргумент містить порожній рядок ).
Якуб Кукуль

6

Для перенесення я не покладаюся на особливості 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

1
Якщо ви не можете прочитати це, просто забудьте мою відповідь. Це просто базові основи.
DimiDak

-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

Якщо ім'я файлів у папці має деякі звичайні імена (наприклад, file1, file2 ...), які я використовував для циклу.

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.