sed Beginner: เปลี่ยนเหตุการณ์ทั้งหมดในโฟลเดอร์


98

ฉันต้องทำการ regex ค้นหาและแทนที่ไฟล์ทั้งหมดในโฟลเดอร์ (และโฟลเดอร์ย่อย) คำสั่ง linux shell จะทำอย่างไร

ตัวอย่างเช่นฉันต้องการเรียกใช้สิ่งนี้กับไฟล์ทั้งหมดและเขียนทับไฟล์เก่าด้วยข้อความใหม่ที่แทนที่

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

คำตอบ:


150

ไม่มีทางทำได้โดยใช้ sed อย่างเดียว คุณจะต้องใช้ยูทิลิตี้ค้นหาร่วมกันเป็นอย่างน้อย:

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

คำสั่งนี้จะสร้าง.bakไฟล์สำหรับไฟล์ที่เปลี่ยนแปลงแต่ละไฟล์

หมายเหตุ:

  • -iอาร์กิวเมนต์สำหรับsedคำสั่งเป็นส่วนขยายของ GNU ดังนั้นถ้าคุณกำลังเรียกใช้คำสั่งนี้กับ BSD ของsedคุณจะต้องเปลี่ยนเส้นทางออกไปยังแฟ้มใหม่แล้วเปลี่ยนชื่อ
  • findยูทิลิตี้ไม่ได้ดำเนินการ-execโต้แย้งในกล่อง UNIX เก่าเช่นนั้นคุณจะต้องใช้| xargsแทน

4
มี\;ไว้เพื่ออะไร?
Andriy Makukha

4
เราจำเป็นต้องบอกให้ค้นหาว่าคำสั่งของอาร์กิวเมนต์ -exec ลงท้ายด้วย a”;” แต่เชลล์ใช้สัญลักษณ์เดียวกัน (;) เป็นตัวคั่นคำสั่งเชลล์ดังนั้นเราต้องหนี ";" จากเชลล์เพื่อส่งต่อไปยังอาร์กิวเมนต์ find's -exec
osantana

2
เป็นที่น่าสังเกตว่า-iโดยตัวมันเองไม่ได้สร้างไฟล์สำรองและเป็นสิ่งที่ทำให้เกิดการดำเนินการกับไฟล์ในสถานที่
Kyle

1
มี{}ไว้เพื่ออะไร?
somenickname

1
{}จะถูกแทนที่ด้วยชื่อไฟล์แต่ละพบโดยfindและ\;บอกจะพบว่าคำสั่งที่เขาต้องการที่จะดำเนินการเสร็จสิ้นในจุดนี้
osantana

53

ฉันชอบใช้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อาร์กิวเมนต์)
Jakub Kukul

บน MacOS ให้เรียกใช้sed -i.bakแทนsed -iไฟล์. ฉันคิดว่าตามที่ @JakubKukul กล่าวไว้sed -i ''ก็ใช้ได้เช่นกัน
forzagreen

7

สำหรับการพกพาฉันไม่ได้พึ่งพาคุณสมบัติของ sed ที่เฉพาะสำหรับ linux หรือ BSD ฉันใช้overwriteสคริปต์จากหนังสือของ Kernighan และ Pike เรื่อง Unix Programming Environment แทน

จากนั้นคำสั่ง

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สามารถอ่านได้



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
ถ้าอ่านไม่ออกก็ลืมคำตอบของฉันไป มันเป็นเพียงพื้นฐานการทุบตี
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

ในกรณีที่ชื่อไฟล์ในโฟลเดอร์มีชื่อปกติ (เช่น file1, file2 ... ) ฉันใช้สำหรับวงจร

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

สิ่งนี้ไม่เกี่ยวข้องกับคำถามที่ถาม คำถามไม่ได้กล่าวถึงอะไรเกี่ยวกับรูปแบบชื่อไฟล์ / โฟลเดอร์เดียวกัน โปรดหลีกเลี่ยงคำตอบดังกล่าว
Kunal Parekh
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.