วิธี grep และแทนที่


254

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

ฉันรู้ว่าคำสั่งเพื่อค้นหามันอาจมีลักษณะเช่นนี้:

grep 'string_to_find' -r ./*

แต่ฉันจะแทนที่ทุกอินสแตนซ์ของstring_to_findสตริงอื่นได้อย่างไร


ฉันไม่เชื่อว่า grep สามารถทำสิ่งนี้ได้ (ฉันอาจผิด) วิธีที่ง่ายกว่าคือการใช้ sed หรือ perl เพื่อทำการแทน
Memento Mori

2
ลองใช้sed -i 's/.*substring.*/replace/'
Eddy_Em

2
@Eddy_Em ที่จะแทนที่ทั้งบรรทัดด้วยการแทนที่ คุณจำเป็นต้องใช้การจัดกลุ่มเพื่อจับภาพส่วนของบรรทัดก่อนและหลังสตริงย่อยจากนั้นใส่ลงในบรรทัดการแทนที่ sed -i 's/\(.*\)substring\(.*\)/\1replace\2/'
JStrahl

1
มีความเป็นไปได้ที่ซ้ำกันของการใช้ grep และ sed เพื่อค้นหาและแทนที่สตริง
jww

คำตอบ:


250

อีกทางเลือกหนึ่งคือใช้ find แล้วผ่านมันผ่าน sed

find /path/to/files -type f -exec sed -i 's/oldstring/new string/g' {} \;

34
บนเทอร์มินัล OS X 10.10 -iจำเป็นต้องมีสตริงส่วนขยายกับพารามิเตอร์ที่เหมาะสม ยกตัวอย่างเช่นfind /path/to/files -type f -exec sed -i "" "s/oldstring/new string/g" {} \;อย่างไรก็ตามให้สตริงที่ว่างเปล่ายังคงสร้างแฟ้มสำรองข้อมูลที่แตกต่างจากที่ระบุไว้ในคู่มือ ...
Eonil

10
ทำไมฉันถึงได้รับ "sed: RE error: ลำดับไบต์ที่ผิดกฎหมาย" และใช่ฉันเพิ่ม-i ""สำหรับ OS X มันทำงานเป็นอย่างอื่น
ทาโก้

2
ผมมีปัญหาลำดับไบต์ที่ผิดกฎหมายใน MacOS 10.12 และคำถามนี้ / คำตอบการแก้ไขปัญหาของฉัน: stackoverflow.com/questions/19242275/...
abeboparebop

3
ซึ่งจะแตะทุกไฟล์เพื่อแก้ไขเวลาไฟล์ และแปลงการสิ้นสุดบรรทัดจากCRLFเป็นLFบน Windows
jww

183

ฉันได้รับคำตอบ

grep -rl matchstring somedir/ | xargs sed -i 's/string1/string2/g'

14
นี้จะผ่านการสแกนไฟล์ที่ตรงกันสองครั้ง ... ครั้งด้วยและจากนั้นอีกครั้งกับgrep sedการใช้findวิธีนี้มีประสิทธิภาพมากกว่า แต่วิธีที่คุณพูดถึงนี้ใช้งานได้
cmevoli

41
ใน OS X คุณจะต้องเปลี่ยนsed -i 's/str1/str2/g'เพื่อsed -i "" 's/str1/str2/g'ให้มันใช้งานได้
jdf

6
@cmevoli ด้วยวิธีนี้grepจะต้องผ่านทุกไฟล์และเพียงสแกนไฟล์ที่จับคู่โดยsed grepด้วยfindวิธีการในคำตอบอื่น ๆfindอันดับแรกจะแสดงรายการไฟล์ทั้งหมดแล้วsedจะสแกนไฟล์ทั้งหมดในไดเรกทอรีนั้น ดังนั้นวิธีการนี้ไม่จำเป็นต้องช้าก็ขึ้นอยู่กับวิธีการหลายแมตช์ที่มีความแตกต่างและความเร็วในการค้นหาระหว่างsed, และgrep find
joelostblom

4
OTOH ด้วยวิธีนี้ช่วยให้คุณสามารถดูว่า grep พบอะไรมาก่อนแทนที่จริง ๆ ลดความเสี่ยงของความล้มเหลวอย่างมากโดยเฉพาะอย่างยิ่งสำหรับ regex n00bs เหมือนตัวฉันเอง
Lennart Rolland

2
สิ่งนี้ยังมีประโยชน์เมื่อการเปลี่ยน grep ของคุณฉลาดกว่า sed ตัวอย่างเช่น ripgrep เชื่อฟัง. gitignore ในขณะที่ sed ไม่
user31389

44

คุณสามารถทำได้เช่นนี้:

ตัวอย่าง

grep -rl 'windows' ./ | xargs sed -i 's/windows/linux/g'

วิธีนี้จะค้นหาสตริง ' windows ' ในไฟล์ทั้งหมดที่เกี่ยวข้องกับไดเรกทอรีปัจจุบันและแทนที่ ' windows ' ด้วย ' linux ' สำหรับการเกิดสตริงแต่ละครั้งในแต่ละไฟล์


2
สิ่งgrepนี้มีประโยชน์ก็ต่อเมื่อมีไฟล์ที่ไม่ควรแก้ไข การเรียกใช้sedไฟล์ทั้งหมดจะอัปเดตวันที่แก้ไขไฟล์ แต่จะไม่มีการเปลี่ยนแปลงเนื้อหาหากไม่มีข้อมูลตรงกัน
tripleee

@tripleee: ระวัง... แต่ [sed] ออกจากเนื้อหาเดิมถ้าไม่มีแมตช์"เมื่อใช้. -iผมเชื่อว่าsedการเปลี่ยนแปลงครั้งแฟ้มของแฟ้มที่มันสัมผัสทุกแม้เนื้อหาจะไม่เปลี่ยนแปลง. sedยังแปลงปลายสาย . ฉันไม่ได้ใช้sedบน Windows ใน repo Git เพราะทุกCRLFที่มีการเปลี่ยนแปลงไปLF.
jww

คำสั่งนี้จำเป็นต้องมี "" หลังจาก -i เพื่อแสดงว่าไม่มีไฟล์สำรองข้อมูลใด ๆ เกิดขึ้นหลังจากการแทนที่แบบแทนที่เกิดขึ้นอย่างน้อยใน macosx ตรวจสอบรายละเอียด man man หากคุณต้องการสำรองข้อมูลนี่คือที่ที่คุณวางส่วนขยายของไฟล์ที่จะสร้าง
spinyBabbler

31

สิ่งนี้ทำงานได้ดีที่สุดสำหรับฉันใน OS X:

grep -r -l 'searchtext' . | sort | uniq | xargs perl -e "s/matchtext/replacetext/" -pi

ที่มา: http://www.praj.com.au/post/23691181208/grep-replace-text-string-in-files


สมบูรณ์แบบ! ยังใช้งานร่วมกับ ag:ag "search" -l -r . | sort | uniq | xargs perl -e 's/search/replace' -pi

@sebastiankeller คำสั่ง Perl ของคุณไม่มีเครื่องหมายทับสุดท้ายซึ่งเป็นข้อผิดพลาดทางไวยากรณ์
tripleee

3
ทำไมถึงเป็นsort -uส่วนหนึ่งของเรื่องนี้? คุณคาดว่าgrep -rlจะสร้างชื่อไฟล์เดียวกันสองครั้งในสถานการณ์ใด
tripleee

5

โซลูชันอื่นผสมไวยากรณ์ของ regex ในการใช้รูปแบบ perl / PCRE สำหรับทั้งการค้นหาและแทนที่และประมวลผลไฟล์ที่ตรงกันเท่านั้นซึ่งทำได้ดี:

grep -rlZPi 'match1' | xargs -0r perl -pi -e 's/match2/replace/gi;'

ที่ไหนmatch1และmatch2มักจะเหมือนกัน แต่match1อาจจะง่ายขึ้นในการลบคุณสมบัติขั้นสูงเพิ่มเติมที่เกี่ยวข้องเฉพาะกับการทดแทนเช่นกลุ่มจับภาพ

การแปล: grepวนซ้ำและแสดงรายการไฟล์ที่ตรงกับรูปแบบ PCRE นี้คั่นด้วย nul เพื่อป้องกันอักขระพิเศษใด ๆ ในชื่อไฟล์จากนั้นไพพ์ชื่อไฟล์เหล่านั้นxargsที่คาดว่าจะมีรายการที่คั่นด้วย nul แต่จะไม่ทำอะไรเลยหากไม่มีชื่อ และperlไปที่บรรทัดที่พบการแข่งขันแทน

เพิ่มIสวิตช์เป็นgrepเพื่อละเว้นไฟล์ไบนารี สำหรับการจับคู่กรณี ๆ ไปวางiสลับจากgrepและiธงที่แนบมากับการแสดงออกทดแทน แต่ไม่iสวิทช์ในperlตัวเอง


Perl เองมีความสามารถในการสร้างโครงสร้างไฟล์ซ้ำ ในความเป็นจริงมีเครื่องมือfind2perlที่มาพร้อมกับ Perl ซึ่งทำสิ่งนี้โดยไม่ต้องxargsใช้เล่ห์เหลี่ยมใด ๆ
tripleee

@tripleee findไม่ค้นหาเนื้อหาไฟล์และประเด็นคือการประมวลผลไฟล์ที่ตรงกันเท่านั้นโดยไม่ต้องเขียนโปรแกรม Perl
Walf

นี่เป็นทางออกที่ดีสำหรับ Windows เนื่องจากหลีกเลี่ยงปัญหาการแก้ไขปัญหาของการแปลงปลายสาย ขอบคุณ!
JamHandy

4

มักจะไม่ได้อยู่กับ grep แต่ด้วยหรือsed -i 's/string_to_find/another_string/g'perl -i.bak -pe 's/string_to_find/another_string/g'


3

ระวังให้มากเมื่อใช้findและsedใน repo คอมไพล์! หากคุณไม่ได้ยกเว้นไฟล์ไบนารีคุณสามารถจบลงด้วยข้อผิดพลาดนี้:

error: bad index file sha1 signature 
fatal: index file corrupt

ในการแก้ข้อผิดพลาดนี้คุณต้องเปลี่ยนกลับsedด้วยการแทนที่new_stringด้วยของคุณold_stringกับคุณสิ่งนี้จะคืนค่าสายอักขระที่ถูกแทนที่ของคุณดังนั้นคุณจะกลับไปที่จุดเริ่มต้นของปัญหา

วิธีที่ถูกต้องในการค้นหาสตริงและแทนที่คือการข้ามfindและใช้grepแทนเพื่อละเว้นไฟล์ไบนารี:

sed -ri -e "s/old_string/new_string/g" $(grep -Elr --binary-files=without-match "old_string" "/files_dir")

เครดิตสำหรับ @hobs


1

นี่คือสิ่งที่ฉันจะทำ:

find /path/to/dir -type f -iname "*filename*" -print0 | xargs -0 sed -i '/searchstring/s/old/new/g'

นี้จะมองหาไฟล์ทั้งหมดที่มีfilenameอยู่ในชื่อของไฟล์ที่อยู่ภายใต้/path/to/dirกว่าสำหรับทุกไฟล์พบการค้นหาบรรทัดที่มีsearchstringและแทนที่ด้วยoldnew

แม้ว่าคุณจะไม่ต้องการค้นหาไฟล์ใดไฟล์หนึ่งด้วยfilenameสตริงในชื่อของไฟล์

find /path/to/dir -type f -print0 | xargs -0 sed -i '/searchstring/s/old/new/g'

นี้จะทำสิ่งเดียวกันข้างต้น /path/to/dirแต่ไฟล์ทั้งหมดอยู่ภายใต้


0

อีกตัวเลือกหนึ่งคือใช้ Perl กับ globstar

การเปิดใช้งานshopt -s globstarใน.bashrc(หรือที่ใดก็ได้) ของคุณทำให้**รูปแบบ glob ตรงกับไดเรกทอรีย่อยและไฟล์ทั้งหมดซ้ำ ๆ

ดังนั้นการใช้perl -pXe 's/SEARCH/REPLACE/g' -i **ซ้ำจะแทนที่ด้วยSEARCHREPLACE

-Xธงบอก Perl เพื่อ "ปิดการใช้งานคำเตือนทั้งหมด" - ซึ่งหมายความว่ามันจะไม่บ่นเกี่ยวกับไดเรกทอรี

GLOBSTAR ยังช่วยให้คุณทำสิ่งต่างๆเช่นsed -i 's/SEARCH/REPLACE/g' **/*.extถ้าคุณอยากจะเปลี่ยนSEARCHกับในแฟ้มลูกทั้งหมดที่มีนามสกุลREPLACE.ext


"อีกทางเลือกหนึ่งคือใช้ Perl กับ globstar ... " - ไม่ใช้กับเครื่อง Posixy เช่น Solaris นั่นเป็นเหตุผลที่ฉันกำลังมองหาเฉพาะและgrep sed
jww
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.