sed pemula: mengubah semua kejadian di folder

98

Saya perlu melakukan pencarian regex dan mengganti semua file dalam folder (dan subfoldernya). Apa perintah shell linux untuk melakukan itu?

Misalnya, saya ingin menjalankan ini di semua file dan menimpa file lama dengan teks baru yang diganti.

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

Jawaban:

150

Tidak ada cara untuk melakukannya hanya dengan menggunakan sed. Anda harus menggunakan setidaknya utilitas find bersama-sama:

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

Perintah ini akan membuat .bakfile untuk setiap file yang diubah.

Catatan:

  • The -iargumen untuk sedperintah adalah ekstensi GNU, jadi, jika Anda menjalankan perintah ini dengan BSD yang sedAnda akan perlu untuk mengarahkan output ke file baru kemudian mengubah nama itu.
  • The findutilitas tidak melaksanakan -execargumen dalam kotak UNIX lama, sehingga, Anda akan perlu menggunakan | xargssebagai gantinya.
osantana
sumber
4
Apa \;untuk?
Andriy Makukha
4
Kita perlu memberitahu untuk menemukan dimana perintah dari argumen -exec diakhiri dengan ";". Tapi shell menggunakan simbol yang sama (;) sebagai pemisah perintah shell, jadi, kita perlu keluar dari ";" dari shell untuk meneruskannya ke argumen -exec find.
osantana
2
Perlu dicatat bahwa -idengan sendirinya tidak membuat file cadangan, dan itulah yang menyebabkan sed untuk melakukan operasi pada file di tempat.
Kyle
1
Apa {}untuk?
julukan
1
Itu {}akan diganti dengan setiap nama file yang ditemukan oleh finddan \;memberitahu untuk menemukan bahwa perintah yang dia butuhkan untuk dieksekusi selesai pada titik ini.
osantana
53

Saya lebih suka menggunakan find | xargs cmdover find -execkarena lebih mudah diingat.

Contoh ini secara global menggantikan "foo" dengan "bar" dalam file .txt pada atau di bawah direktori Anda saat ini:

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

The -print0dan -0pilihan dapat ditinggalkan jika nama file Anda tidak berisi karakter yang funky seperti spasi.

Dennis
sumber
3
Jika Anda menggunakan OSX, coba find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' "s/foo/bar/g"(catatan memberikan string kosong ke -iargumen).
Jakub Kukul
Di MacOS, jalankan sed -i.baksebagai ganti sed -i. Saya pikir seperti yang disebutkan oleh @JakubKukul, sed -i ''juga berfungsi.
untukzagreen
7

Untuk portabilitas, saya tidak mengandalkan fitur sed yang khusus untuk linux atau BSD. Sebagai gantinya saya menggunakan overwriteskrip dari Kernighan dan buku Pike di Unix Programming Environment.

Perintahnya kemudian

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

Dan overwriteskrip (yang saya gunakan di semua tempat) adalah

#!/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

Idenya adalah bahwa itu menimpa file hanya jika perintah berhasil. Berguna di finddan juga di tempat yang tidak ingin Anda gunakan

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

karena shell memotong file sebelum seddapat membacanya.

Norman Ramsey
sumber
3

Mungkin saya sarankan (setelah membuat cadangan file Anda):

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

Contoh: ganti {AutoStart} dengan 1 untuk semua file ini di bawah folder / app / config / dan folder anaknya:

sed 's/{AutoStart}/1/g' /app/config/**/*.ini
Alan Hu
sumber
0
for i in $(ls);do sed -i 's/old_text/new_text/g' $i;done 
DimiDak
sumber
5
Tolong jelaskan jawaban Anda.
Putus sekolah
Meskipun kode ini dapat menyelesaikan masalah OP, lebih baik menyertakan penjelasan tentang bagaimana kode Anda mengatasi masalah OP. Dengan cara ini, pengunjung selanjutnya dapat belajar dari posting Anda, & menerapkannya ke kode mereka sendiri. SO bukanlah layanan pengkodean, tetapi sumber daya untuk pengetahuan. Jawaban lengkap dan berkualitas tinggi memperkuat gagasan ini, dan lebih mungkin untuk dipilih. Fitur-fitur ini, ditambah persyaratan bahwa semua posting akan berdiri sendiri, adalah beberapa kekuatan SO sebagai platform yang membedakan kami dari forum. Anda dapat mengedit untuk menambahkan info tambahan & / atau untuk melengkapi penjelasan Anda dengan dokumentasi sumber.
SherylHohman
2
Jika Anda tidak bisa membaca ini, lupakan saja jawaban saya. Itu hanya dasar-dasar pesta.
DimiDak
0

Ini berfungsi untuk saya (di terminal mac, di Linux Anda tidak perlu '' -e):

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

perintah grep 'old text' -rl *mencantumkan semua file di direktori kerja (dan subdirektori) di mana "teks lama" ada. Ini kemudian diteruskan dalam sed.

Foivos
sumber
-1

Mungkin ingin mencoba mencari / mengganti skrip Perl saya secara massal . Memiliki beberapa keunggulan dibandingkan solusi utilitas yang dirantai (seperti tidak harus berurusan dengan beberapa level interpretasi karakter metakarakter 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";
}
kekacauan
sumber
-3

Jika nama file dalam folder memiliki beberapa nama biasa (seperti file1, file2 ...) yang saya gunakan untuk siklus.

for i in {1..10000..100}; do sed 'old\new\g' 'file'$i.xml > 'cfile'$i.xml; done
tereza
sumber
ini tidak terkait dengan pertanyaan yang diajukan. Pertanyaan tidak menyebutkan apapun tentang pola nama file / folder yang sama. Harap hindari jawaban seperti itu
Kunal Parekh