sed beginner: thay đổi tất cả các lần xuất hiện trong một thư mục


98

Tôi cần thực hiện tìm và thay thế regex trên tất cả các tệp trong một thư mục (và các thư mục con của nó). Lệnh shell linux để làm điều đó là gì?

Ví dụ: tôi muốn chạy điều này trên tất cả các tệp và ghi đè tệp cũ bằng văn bản mới được thay thế.

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

Câu trả lời:


148

Không có cách nào để làm điều đó chỉ sử dụng sed. Bạn sẽ cần sử dụng ít nhất tiện ích tìm thấy cùng nhau:

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

Lệnh này sẽ tạo một .baktệp cho mỗi tệp đã thay đổi.

Ghi chú:

  • Đối -isố cho sedlệnh là một phần mở rộng GNU, vì vậy, nếu bạn đang chạy lệnh này với BSD, sedbạn sẽ cần chuyển hướng đầu ra đến một tệp mới rồi đổi tên nó.
  • Các findtiện ích không thực hiện -execlập luận trong hộp UNIX cũ, vì vậy, bạn sẽ cần phải sử dụng một | xargsđể thay thế.

4
Để làm gì \;?
Andriy Makukha,

4
Chúng ta cần cho biết để tìm nơi lệnh của đối số -exec kết thúc bằng dấu ”;”. Nhưng shell sử dụng cùng một ký hiệu (;) làm dấu phân tách lệnh shell, vì vậy, chúng ta cần thoát khỏi dấu ”;” từ shell để chuyển nó tới đối số -exec của find.
osantana

2
Điều đáng chú ý là -ibản thân nó không tạo tệp sao lưu và là nguyên nhân khiến sed thực hiện thao tác trên tệp tại chỗ.
Kyle

1
Để làm gì {}?
somenickname

1
Các {}sẽ được thay thế bởi mỗi tên tập tin tìm thấy bằng find\;kể để thấy rằng lệnh rằng ông cần phải thực thi kết thúc vào thời điểm này.
osantana

51

Tôi thích sử dụng find | xargs cmdhơn find -execvì nó dễ nhớ hơn.

Ví dụ này trên toàn cầu thay thế "foo" bằng "bar" trong tệp .txt tại hoặc bên dưới thư mục hiện tại của bạn:

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

Có thể bỏ qua các tùy chọn -print0-0nếu tên tệp của bạn không chứa các ký tự thú vị như dấu cách.


3
Nếu bạn đang sử dụng OSX, hãy thử find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' "s/foo/bar/g"(lưu ý cung cấp một chuỗi trống cho -iđối số).
Jakub Kukul

6

Đối với tính di động, tôi không dựa vào các tính năng của sed dành riêng cho linux hoặc BSD. Thay vào đó, tôi sử dụng overwritetập lệnh từ cuốn sách của Kernighan và Pike về Môi trường lập trình Unix.

Lệnh sau đó là

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

overwritekịch bản (mà tôi sử dụng khắp nơi) là

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

Ý tưởng là nó chỉ ghi đè lên một tệp nếu một lệnh thành công. Hữu ích ở những findnơi bạn không muốn sử dụng

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

bởi vì shell cắt ngắn tệp trước khi sedcó thể đọc nó.


3

Tôi có thể đề xuất (sau khi sao lưu các tệp của bạn):

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

0

Ví dụ: replase {AutoStart} bằng 1 cho tất cả các tệp ini trong thư mục / app / config / và các thư mục con của nó:

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
Hãy giải thích câu trả lời của bạn.
Bỏ học

Mặc dù mã này có thể giải quyết vấn đề của OP, nhưng tốt hơn hết bạn nên bao gồm giải thích về cách mã của bạn giải quyết vấn đề của OP. Bằng cách này, khách truy cập trong tương lai có thể học từ bài đăng của bạn và áp dụng nó vào mã của riêng họ. SO không phải là một dịch vụ mã hóa, mà là một nguồn kiến ​​thức. Câu trả lời đầy đủ, chất lượng cao củng cố ý tưởng này và có nhiều khả năng được ủng hộ hơn. Những tính năng này, cộng với yêu cầu tất cả các bài đăng phải khép kín, là một số điểm mạnh của SO như một nền tảng phân biệt chúng tôi với các diễn đàn. Bạn có thể chỉnh sửa để thêm thông tin bổ sung & / hoặc để bổ sung giải thích của bạn với tài liệu nguồn.
SherylHohman

1
Nếu bạn không thể đọc điều này, chỉ cần quên câu trả lời của tôi. Nó chỉ là những điều cơ bản.
DimiDak

-1

Có thể muốn thử tìm kiếm hàng loạt / thay thế tập lệnh Perl của tôi . Có một số ưu điểm so với các giải pháp tiện ích theo chuỗi (như không phải đối phó với nhiều cấp độ diễn giải siêu ký tự vỏ).

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

Trong trường hợp tên tệp trong thư mục có một số tên thông thường (như tệp1, tệp2 ...) tôi đã sử dụng cho chu kỳ.

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

điều này không liên quan đến câu hỏi được hỏi. Câu hỏi không đề cập bất cứ điều gì về cùng một mẫu tên tệp / thư mục. Hãy tránh câu trả lời như vậy
Kunal Parekh
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.