ฉันต้องทำการ regex ค้นหาและแทนที่ไฟล์ทั้งหมดในโฟลเดอร์ (และโฟลเดอร์ย่อย) คำสั่ง linux shell จะทำอย่างไร
ตัวอย่างเช่นฉันต้องการเรียกใช้สิ่งนี้กับไฟล์ทั้งหมดและเขียนทับไฟล์เก่าด้วยข้อความใหม่ที่แทนที่
sed 's/old text/new text/g'
ฉันต้องทำการ regex ค้นหาและแทนที่ไฟล์ทั้งหมดในโฟลเดอร์ (และโฟลเดอร์ย่อย) คำสั่ง linux shell จะทำอย่างไร
ตัวอย่างเช่นฉันต้องการเรียกใช้สิ่งนี้กับไฟล์ทั้งหมดและเขียนทับไฟล์เก่าด้วยข้อความใหม่ที่แทนที่
sed 's/old text/new text/g'
คำตอบ:
ไม่มีทางทำได้โดยใช้ sed อย่างเดียว คุณจะต้องใช้ยูทิลิตี้ค้นหาร่วมกันเป็นอย่างน้อย:
find . -type f -exec sed -i.bak "s/foo/bar/g" {} \;
คำสั่งนี้จะสร้าง.bak
ไฟล์สำหรับไฟล์ที่เปลี่ยนแปลงแต่ละไฟล์
หมายเหตุ:
-i
อาร์กิวเมนต์สำหรับsed
คำสั่งเป็นส่วนขยายของ GNU ดังนั้นถ้าคุณกำลังเรียกใช้คำสั่งนี้กับ BSD ของsed
คุณจะต้องเปลี่ยนเส้นทางออกไปยังแฟ้มใหม่แล้วเปลี่ยนชื่อfind
ยูทิลิตี้ไม่ได้ดำเนินการ-exec
โต้แย้งในกล่อง UNIX เก่าเช่นนั้นคุณจะต้องใช้| xargs
แทน\;
ไว้เพื่ออะไร?
-i
โดยตัวมันเองไม่ได้สร้างไฟล์สำรองและเป็นสิ่งที่ทำให้เกิดการดำเนินการกับไฟล์ในสถานที่
{}
ไว้เพื่ออะไร?
{}
จะถูกแทนที่ด้วยชื่อไฟล์แต่ละพบโดยfind
และ\;
บอกจะพบว่าคำสั่งที่เขาต้องการที่จะดำเนินการเสร็จสิ้นในจุดนี้
ฉันชอบใช้find | xargs cmd
มากกว่าfind -exec
เพราะจำง่ายกว่า
ตัวอย่างนี้แทนที่ "foo" ทั่วโลกด้วย "bar" ในไฟล์. txt ที่หรือด้านล่างไดเร็กทอรีปัจจุบันของคุณ:
find . -type f -name "*.txt" -print0 | xargs -0 sed -i "s/foo/bar/g"
-print0
และ-0
ตัวเลือกที่สามารถปล่อยออกมาถ้าชื่อไฟล์ของคุณไม่ได้มีตัวละครขี้ขลาดเช่นช่องว่าง
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' "s/foo/bar/g"
(หมายเหตุระบุสตริงว่างให้กับ-i
อาร์กิวเมนต์)
sed -i.bak
แทนsed -i
ไฟล์. ฉันคิดว่าตามที่ @JakubKukul กล่าวไว้sed -i ''
ก็ใช้ได้เช่นกัน
สำหรับการพกพาฉันไม่ได้พึ่งพาคุณสมบัติของ 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
สามารถอ่านได้
ฉันขอแนะนำ (หลังจากสำรองไฟล์ของคุณ):
find /the/folder -type f -exec sed -ibak 's/old/new/g' {} ';'
ตัวอย่าง: แทนที่ {AutoStart} ด้วย 1 สำหรับไฟล์ ini ทั้งหมดในโฟลเดอร์ / app / config / และโฟลเดอร์ย่อย:
sed 's/{AutoStart}/1/g' /app/config/**/*.ini
for i in $(ls);do sed -i 's/old_text/new_text/g' $i;done
สิ่งนี้ใช้ได้กับฉัน (บนเทอร์มินัล mac บน Linux คุณไม่ต้องการ'' -e
):
sed -i '' -e 's/old text/new text/g' `grep 'old text' -rl *`
คำสั่งgrep 'old text' -rl *
จะแสดงรายการไฟล์ทั้งหมดในไดเร็กทอรีการทำงาน (และไดเร็กทอรีย่อย) ซึ่งมี "ข้อความเก่า" อยู่ จากนั้นจะถูกส่งผ่านใน sed
อาจต้องการลองค้นหามวลของฉัน / แทนที่ 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";
}
ในกรณีที่ชื่อไฟล์ในโฟลเดอร์มีชื่อปกติ (เช่น file1, file2 ... ) ฉันใช้สำหรับวงจร
for i in {1..10000..100}; do sed 'old\new\g' 'file'$i.xml > 'cfile'$i.xml; done