แก้ไขไฟล์ในสถานที่


299

ฉันพยายามที่จะค้นหาว่ามันเป็นไปได้ที่จะแก้ไขไฟล์ในคำสั่ง sed เดียวโดยไม่ต้องสตรีมเนื้อหาที่แก้ไขด้วยตนเองเป็นไฟล์ใหม่แล้วเปลี่ยนชื่อไฟล์ใหม่เป็นชื่อไฟล์ต้นฉบับ ฉันลอง-iตัวเลือกแล้ว แต่ระบบ Solaris ของฉันบอกว่านั่น-iเป็นตัวเลือกที่ผิดกฎหมาย มีวิธีอื่นไหม


7
-iเป็นตัวเลือกใน gnu sed แต่ไม่ได้อยู่ใน sed มาตรฐาน อย่างไรก็ตามมันจะสตรีมเนื้อหาไปยังไฟล์ใหม่แล้วเปลี่ยนชื่อไฟล์ดังนั้นจึงไม่ใช่สิ่งที่คุณต้องการ
วิลเลียม Pursell

2
ที่จริงแล้วมันคือสิ่งที่ฉันต้องการฉันแค่ไม่อยากสัมผัสกับการเปลี่ยนชื่อไฟล์ใหม่ให้เป็นชื่อดั้งเดิม
amphibient

3
จากนั้นคุณต้องทบทวนคำถามอีกครั้ง
วิลเลียม Pursell

3
@amphibient: คุณจะคำนึงถึงคำนำหน้าชื่อคำถามของคุณด้วยคำว่า 'Solaris' หรือไม่ มูลค่าคำถามของคุณกำลังจะสูญหาย โปรดดูความคิดเห็นด้านล่างคำตอบของฉัน ขอบคุณ
Steve

1
@Steve: ฉันได้ลบคำนำหน้า Solaris ออกจากชื่ออีกครั้งเพราะนี่ไม่ใช่ Solaris
tripleee

คำตอบ:


476

-iตัวเลือกสตรีมเนื้อหาที่แก้ไขเป็นไฟล์ใหม่แล้วเปลี่ยนชื่อมันอยู่เบื้องหลังแล้ว

ตัวอย่าง:

sed -i 's/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g' filename

และ

sed -i '' 's/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g' filename

บนMacOS


3
อย่างน้อยมันก็ทำเพื่อฉันดังนั้นฉันไม่จำเป็นต้อง
สัตว์สะเทินน้ำสะเทินบก

2
คุณอาจลองคอมไพล์ GNU บนระบบของคุณ
choroba

3
ตัวอย่าง:sed -i "s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g" <file>
Thales Ceolin

2
นี่จะดีกว่าวิธี perl อย่างใดมันไม่ได้สร้างไฟล์. swap ใด ๆ .... ขอบคุณ!
คณิตศาสตร์

3
@ คณิตศาสตร์: มัน แต่บางทีอาจจะเป็นที่อื่นหรือในเวลาอันสั้น?
choroba

72

ในระบบที่sedไม่มีความสามารถในการแก้ไขไฟล์ฉันคิดว่าทางออกที่ดีกว่าคือperl:

perl -pi -e 's/foo/bar/g' file.txt

แม้ว่าสิ่งนี้จะสร้างไฟล์ชั่วคราว แต่มันจะแทนที่ต้นฉบับเพราะมีการให้คำต่อท้าย / นามสกุลว่างในสถานที่


14
ไม่เพียงสร้างไฟล์ชั่วคราวเท่านั้น แต่ยังแบ่งการเชื่อมโยงอย่างหนัก (เช่นแทนที่จะเปลี่ยนเนื้อหาของไฟล์มันจะแทนที่ไฟล์ด้วยไฟล์ใหม่ที่มีชื่อเหมือนกัน) บางครั้งนี่เป็นพฤติกรรมที่ต้องการบางครั้งก็เป็น ยอมรับได้ แต่เมื่อมีการเชื่อมโยงไปยังไฟล์หลายไฟล์มันมักจะเป็นพฤติกรรมที่ผิด
วิลเลียม Pursell

3
@DwightSpencer: ฉันเชื่อว่าทุกระบบที่ไม่มี Perl เสียหาย sudoคำสั่งเดียวปั้นห่วงโซ่ของคำสั่งเมื่อต้องการยกระดับสิทธิ์และการใช้งานที่ต้อง ในใจของฉันคำถามคือเกี่ยวกับวิธีหลีกเลี่ยงการสร้างและเปลี่ยนชื่อไฟล์ชั่วคราวด้วยตนเองโดยไม่ต้องติดตั้ง GNU หรือ BSD sedล่าสุด เพียงใช้ Perl
Steve

4
@PetrPeller: จริงเหรอ? ถ้าคุณอ่านคำถามอย่างคุณจะเข้าใจว่า OP sed 's/foo/bar/g' file.txt > file.tmp && mv file.tmp file.txtจะพยายามที่จะหลีกเลี่ยงสิ่งที่ต้องการ เนื่องจากการแก้ไขในสถานที่ทำการเปลี่ยนชื่อโดยใช้ไฟล์ชั่วคราวไม่ได้หมายความว่าเขา / เธอจะต้องทำการเปลี่ยนชื่อด้วยตนเองเมื่อไม่มีตัวเลือก มีเครื่องมืออื่น ๆ ออกมีที่สามารถทำสิ่งที่เขา / เธอต้องการและ Perl เป็นตัวเลือกที่ชัดเจน นี่ไม่ใช่คำตอบสำหรับคำถามต้นฉบับอย่างไร
Steve

2
@a_arias: ถูกต้อง; เขาทำ. แต่เขาไม่สามารถบรรลุสิ่งที่เขาต้องการโดยใช้โซลาริsedส คำถามนี้เป็นคำถามที่ดีมาก แต่คุณค่าของมันลดน้อยลงโดยคนที่ไม่สามารถอ่านหน้าคนหรือไม่ใส่ใจที่จะอ่านคำถามจริง ๆที่นี่
Steve

4
@EdwardGarson: IIRC, Solaris มาพร้อมกับ Perl แต่ไม่ใช่ Python หรือ Ruby และอย่างที่ฉันได้พูดไปก่อนหน้านี้ OP ไม่สามารถบรรลุผลลัพธ์ที่ต้องการได้โดยใช้ Solaris sed
Steve

56

โปรดทราบว่าใน OS X คุณอาจได้รับข้อผิดพลาดแปลก ๆ เช่น "รหัสคำสั่งไม่ถูกต้อง" หรือข้อผิดพลาดแปลก ๆ อื่น ๆ เมื่อใช้คำสั่งนี้ เพื่อแก้ไขปัญหานี้ให้ลอง

sed -i '' -e "s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g" <file>

นี้เป็นเพราะในรุ่น OSX ของsedที่-iตัวเลือกที่คาดว่าจะมีextensionการโต้แย้งเพื่อให้คำสั่งของคุณจะแยกจริงเป็นextensionข้อโต้แย้งและเส้นทางของแฟ้มถูกตีความว่าเป็นรหัสคำสั่ง ที่มา: https://stackoverflow.com/a/19457213


1
นี่เป็นอีกหนึ่งความแปลกประหลาดของ OS X โชคดีที่brew install gnu-sedพร้อมด้วย a PATHจะช่วยให้คุณใช้รุ่น GNU ของ sed ขอบคุณที่พูดถึง; scratcher หัวแน่นอน
Doug

23

งานต่อไปนี้ใช้ได้ดีบน mac ของฉัน

sed -i.bak 's/foo/bar/g' sample

เรากำลังแทนที่ foo ด้วยแถบในไฟล์ตัวอย่าง สำรองข้อมูลไฟล์ต้นฉบับจะถูกบันทึกไว้ใน sample.bak

สำหรับการแก้ไขแบบอินไลน์โดยไม่ต้องสำรองข้อมูลให้ใช้คำสั่งต่อไปนี้

sed -i'' 's/foo/bar/g' sample

มีวิธีป้องกันการเก็บไฟล์สำรองอย่างไร?
Petr Peller

@PetrPeller คุณสามารถใช้คำสั่งที่กล่าวถึงสำหรับตัวอย่าง ... sed -i '' 's / foo / bar / g' ตัวอย่าง
minhas23

18

สิ่งหนึ่งที่ควรทราบsedไม่สามารถเขียนไฟล์ด้วยตัวเองเป็นจุดประสงค์เพียงอย่างเดียวของ sed คือทำหน้าที่เป็นตัวแก้ไขใน "สตรีม" (เช่นไพพ์ไลน์ของ stdin, stdout, stderr และ>&nบัฟเฟอร์อื่น ๆซ็อกเก็ตและอื่น ๆ) เมื่อคำนึงถึงสิ่งนี้คุณสามารถใช้คำสั่งอื่นteeเพื่อเขียนเอาต์พุตกลับไปยังไฟล์ diffอีกตัวเลือกหนึ่งคือการสร้างแพทช์จากท่อเข้าไปในเนื้อหา

วิธีตี๋

sed '/regex/' <file> | tee <file>

วิธีการแก้ไข

sed '/regex/' <file> | diff -p <file> /dev/stdin | patch

UPDATE:

นอกจากนี้โปรดทราบว่าตัวแก้ไขจะทำให้ไฟล์เปลี่ยนจากบรรทัดที่ 1 ของเอาต์พุต diff:

Patch ไม่จำเป็นต้องรู้ไฟล์ที่จะเข้าถึงเนื่องจากพบได้ในบรรทัดแรกของเอาต์พุตจาก diff:

$ echo foobar | tee fubar

$ sed 's/oo/u/' fubar | diff -p fubar /dev/stdin
*** fubar   2014-03-15 18:06:09.000000000 -0500
--- /dev/stdin  2014-03-15 18:06:41.000000000 -0500
***************
*** 1 ****
! foobar
--- 1 ----
! fubar

$ sed 's/oo/u/' fubar | diff -p fubar /dev/stdin | patch
patching file fubar

2
/dev/stdin 2014-03-15, ตอบ 7 มกราคม - คุณเป็นนักท่องเที่ยวที่มีเวลาหรือไม่
Adrian Frühwirth

บน Windows โดยใช้ msysgit, /dev/stdinไม่ได้อยู่ดังนั้นคุณต้องเปลี่ยน/dev/stdinด้วย '-' ยัติภังค์เดียวโดยไม่มีคำพูดเพื่อให้คำสั่งดังต่อไปนี้ควรทำงาน: $ sed 's/oo/u/' fubar | diff -p fubar -และ$ sed 's/oo/u/' fubar | diff -p fubar - | patch
จิม Raden

@ AdrianFrühwirthฉันมีกล่องตำรวจสีฟ้าอยู่รอบ ๆ และด้วยเหตุผลบางอย่างที่พวกเขาเรียกฉันว่าหมอในที่ทำงาน แต่จริงๆแล้วดูเหมือนว่าระบบจะมีสัญญาณนาฬิกาไม่ดีในขณะนั้น
Dwight Spencer

วิธีแก้ปัญหาเหล่านี้ดูเหมือนว่าฉันจะต้องพึ่งพาพฤติกรรมการบัฟเฟอร์โดยเฉพาะ ฉันสงสัยว่าไฟล์ที่ใหญ่กว่านี้อาจแตกได้ในขณะที่ sed กำลังอ่านไฟล์อาจเริ่มเปลี่ยนภายใต้คำสั่ง patch หรือแย่กว่านั้นโดยคำสั่ง tee ที่จะตัดไฟล์ ที่จริงฉันไม่แน่ใจว่าpatchจะตัดทอนเช่นกัน ฉันคิดว่าการบันทึกผลลัพธ์ไปยังไฟล์อื่นจากนั้นการนำcatเข้าสู่ต้นฉบับจะปลอดภัยกว่า
akostadinov

คำตอบในสถานที่จริงจาก @ william-pursell
akostadinov

11

ไม่สามารถทำสิ่งที่คุณต้องการsedได้ แม้กระทั่งเวอร์ชันsedที่สนับสนุน-iตัวเลือกสำหรับการแก้ไขไฟล์ในสถานที่ทำสิ่งที่คุณได้ระบุไว้อย่างชัดเจนว่าคุณไม่ต้องการ: พวกเขาเขียนไปยังไฟล์ชั่วคราวแล้วเปลี่ยนชื่อไฟล์ edแต่บางทีคุณก็สามารถใช้ ตัวอย่างเช่นหากต้องการเปลี่ยนเหตุการณ์ทั้งหมดfooเป็นเป็นbarในไฟล์file.txtคุณสามารถทำได้:

echo ',s/foo/bar/g; w' | tr \; '\012' | ed -s file.txt

ไวยากรณ์คล้ายกับsedแต่ไม่เหมือนกันทุกประการ

แต่บางทีคุณควรพิจารณาว่าทำไมคุณไม่ต้องการใช้ไฟล์ชั่วคราวที่ถูกเปลี่ยนชื่อ แม้ว่าคุณจะไม่ได้รับการ-iสนับสนุนsedคุณสามารถเขียนสคริปต์เพื่อทำงานให้คุณได้อย่างง่ายดาย แทนที่จะsed -i 's/foo/bar/g' fileทำเช่นinline file sed 's/foo/bar/g'นั้น สคริปต์เช่นนี้เขียนได้เล็กน้อย ตัวอย่างเช่น:

#!/bin/sh -e
IN=$1
shift
trap 'rm -f $tmp' 0
tmp=$( mktemp )
<$IN "$@" >$tmp && cat $tmp > $IN  # preserve hard links

ควรจะเพียงพอสำหรับการใช้งานส่วนใหญ่


1
ตัวอย่างเช่นคุณจะตั้งชื่อสคริปต์นี้อย่างไรและคุณจะเรียกมันว่าอย่างไร
สัตว์สะเทินน้ำสะเทินบก

2
ฉันจะเรียกมันinlineและเรียกมันตามที่อธิบายข้างต้น:inline inputfile sed 's/foo/bar/g'
วิลเลียม Pursell

1
ว้าวedเพียงตอบว่าทำในสถานที่จริง edครั้งแรกที่ผมต้องการ ขอบคุณ. การปรับให้เหมาะสมเล็กน้อยคือการใช้echo -e ",s/foo/bar/g\012 w"หรือecho $',s/foo/bar/g\012 w'ตำแหน่งที่เชลล์อนุญาตให้หลีกเลี่ยงการtrโทรพิเศษ
akostadinov

2
edไม่ได้ทำการแก้ไขในสถานที่จริง ๆ จากการstraceส่งออก GNU edสร้างไฟล์ชั่วคราวเขียนการเปลี่ยนแปลงไปยังไฟล์ชั่วคราวจากนั้นเขียนไฟล์ต้นฉบับทั้งหมดใหม่รักษาฮาร์ดลิงก์ จากtrussเอาต์พุต Solaris 11 edยังใช้ไฟล์ temp แต่เปลี่ยนชื่อไฟล์ temp เป็นชื่อไฟล์ต้นฉบับเมื่อทำการบันทึกด้วยwคำสั่งทำลายฮาร์ดลิงก์ใด ๆ
Andrew Henle

@AndrewHenle ที่ท้อใจ การedจัดส่งที่มีมาโครปัจจุบัน (โมฮาวีหมายถึงศัพท์แสงทางการตลาด) ที่ยังคงรักษาลิงก์ถาวรไว้ YMMV
William Pursell


9

sedรองรับการแก้ไขในสถานที่ จากman sed:

-i[SUFFIX], --in-place[=SUFFIX]

    edit files in place (makes backup if extension supplied)

ตัวอย่าง :

สมมติว่าคุณมีไฟล์hello.txtพร้อมข้อความ:

hello world!

หากคุณต้องการสำรองไฟล์เก่าไว้ให้ใช้:

sed -i.bak 's/hello/bonjour' hello.txt

คุณจะจบลงด้วยสองไฟล์: hello.txtมีเนื้อหา:

bonjour world!

และhello.txt.bakมีเนื้อหาเก่า

หากคุณไม่ต้องการเก็บสำเนาเพียงแค่ไม่ผ่านพารามิเตอร์ส่วนขยาย


3
OP ระบุว่าแพลตฟอร์มของเขาไม่สนับสนุนตัวเลือกที่ไม่เป็นมาตรฐาน
tripleee

3

คุณไม่ได้ระบุเชลล์ที่คุณใช้ แต่ด้วย zsh คุณสามารถใช้=( )โครงสร้างเพื่อให้บรรลุสิ่งนี้ บางสิ่งบางอย่างตาม:

cp =(sed ... file; sync) file

=( )คล้ายกับ>( )แต่สร้างไฟล์ชั่วคราวซึ่งจะถูกลบโดยอัตโนมัติเมื่อcpยกเลิก


3
mv file.txt file.tmp && sed 's/foo/bar/g' < file.tmp > file.txt

ควรเก็บรักษาฮาร์ดลิงก์ทั้งหมดไว้เนื่องจากเอาต์พุตจะถูกนำกลับไปเพื่อเขียนทับเนื้อหาของไฟล์ต้นฉบับและหลีกเลี่ยงความต้องการรุ่นพิเศษใด ๆ


3

หากคุณกำลังแทนที่จำนวนอักขระที่เท่ากันและหลังจากอ่านการแก้ไขไฟล์ "แบบแทนที่"อย่างระมัดระวัง...

คุณยังสามารถใช้โอเปอเรเตอร์การเปลี่ยนเส้นทาง<>เพื่อเปิดไฟล์เพื่ออ่านและเขียน:

sed 's/foo/bar/g' file 1<> file

ดูสด:

$ cat file
hello
i am here                           # see "here"
$ sed 's/here/away/' file 1<> file  # Run the `sed` command
$ cat file
hello
i am away                           # this line is changed now

จากคู่มืออ้างอิง Bash → 3.6.10 การเปิดตัวอธิบายไฟล์เพื่อการอ่านและการเขียน :

ผู้ประกอบการเปลี่ยนเส้นทาง

[n]<>word

ทำให้ไฟล์ที่มีชื่อคือส่วนขยายของคำที่จะเปิดสำหรับทั้งการอ่านและการเขียนบน file descriptor n หรือบน file descriptor 0 หากไม่ได้ระบุ n หากไฟล์ไม่มีอยู่ไฟล์จะถูกสร้างขึ้น


ความเสียหายจากไฟล์ ฉันไม่แน่ใจในเหตุผลที่อยู่เบื้องหลัง paste.fedoraproject.org/462017
Nehal J Wani

ดูรายการ [43-44], [88-89], [135-136]
Nehal J Wani

2
บล็อกนี้อธิบายว่าทำไมไม่ควรใช้คำตอบนี้ backreference.org/2011/01/29/in-place-editing-of-files
Nehal J Wani

@ NehalJWani ฉันจะได้อ่านบทความยาว ๆ ขอบคุณสำหรับหัวขึ้น! สิ่งที่ฉันไม่ได้กล่าวถึงในคำตอบก็คือมันใช้งานได้หากมันเปลี่ยนจำนวนตัวอักษรเท่ากัน
fedorqui 'ดังนั้นหยุดการทำร้าย'

@ NehalJWani ถูกกล่าวว่าฉันขอโทษถ้าคำตอบของฉันทำให้เกิดความเสียหายกับไฟล์ของคุณ ฉันจะอัปเดตด้วยการแก้ไข (หรือลบหากจำเป็น) หลังจากอ่านลิงก์ที่คุณให้ไว้
fedorqui 'ดังนั้นหยุดทำอันตราย'

2

เหมือนที่ Moneypenny พูดไว้ใน Skyfall: "บางครั้งวิธีการแบบเก่าก็ดีที่สุด" Kincade พูดอะไรที่คล้ายกันในภายหลัง

$ printf ',s/false/true/g\nw\n' | ed {YourFileHere}

มีความสุขในการแก้ไข เพิ่ม '\ nw \ n' เพื่อเขียนไฟล์ ขออภัยในความล่าช้าในการตอบคำขอ


คุณช่วยอธิบายสิ่งนี้ได้ไหม?
Matt Montag

1
มันจะเรียก ed (1), ตัวแก้ไขข้อความที่มีตัวอักษรบางตัวเขียนไปยัง stdin ในฐานะที่เป็นคนที่อายุต่ำกว่า 30 ปีฉันคิดว่ามันโอเคสำหรับฉันที่จะบอกว่าเอ็ด (1) คือไดโนเสาร์เนื่องจากไดโนเสาร์เป็นของเรา อย่างไรก็ตามแทนที่จะเปิด ed และการพิมพ์สิ่งที่ใน jlettvin |เป็นท่อตัวอักษรในการใช้ @MattMontag
Ari Sweedler

\nหากคุณกำลังถามว่าคำสั่งที่ทำมีสองของพวกเขายกเลิกโดย [range] s / <old> / <new> / <flag> เป็นรูปแบบของคำสั่งสำรอง - กระบวนทัศน์ที่ยังคงเห็นในโปรแกรมแก้ไขข้อความอื่น ๆ อีกมากมาย (โอเค, เป็นกลุ่ม, สืบทอดมาโดยตรงของเอ็ด) อย่างไรก็ตามgธงย่อมาจาก "global" และหมายถึง "ทุกอินสแตนซ์ในแต่ละบรรทัดที่คุณดู" ช่วง3,5จะดูที่เส้น 3-5 ,5ดูที่ StartOfFile-5 3,ดูที่บรรทัด 3-EndOfFile และ,ดูที่ StartOfFile-EndOfFile คำสั่งทดแทนทั่วโลกในทุกบรรทัดที่แทนที่ "false" ด้วย "true" จากนั้นคำสั่ง write wจะถูกป้อน
Ari Sweedler

1

ตัวอย่างที่ดีมาก ฉันมีความท้าทายในการแก้ไขไฟล์หลาย ๆ ไฟล์และตัวเลือก -i น่าจะเป็นทางออกเดียวที่สมเหตุสมผลโดยใช้ภายในคำสั่ง find ที่นี่สคริปต์เพื่อเพิ่ม "รุ่น:" ที่ด้านหน้าของบรรทัดแรกของแต่ละไฟล์:

find . -name pkg.json -print -exec sed -i '.bak' '1 s/^/version /' {} \;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.