ฉันจะประสบความสำเร็จในการพกพาได้ด้วย sed -i (การแก้ไขในสถานที่) ได้อย่างไร


40

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

ส่วนหนึ่งของเว็บไซต์ของฉันใช้ไฟล์ HTML แบบคงที่ที่สร้างขึ้นและบรรทัด sed นี้แทรก DOCTYPE ที่ถูกต้องหลังจากการสร้างใหม่แต่ละครั้ง:

sed -i '1s/^/<!DOCTYPE html> \n/' ${file_name.html}

มันทำงานร่วมกับ GNU sedบน Linux แต่ FreeBSD sedคาดว่าอาร์กิวเมนต์แรกหลังจาก-iตัวเลือกที่จะเป็นส่วนขยายสำหรับสำเนาสำรอง นี่คือลักษณะ:

sed -i '' '1s/^/<!DOCTYPE html> \n/' ${file_name.html}

อย่างไรก็ตาม GNU ในทางกลับกันคาดว่าการแสดงออกที่จะปฏิบัติตามทันทีหลังจากที่sed -i(นอกจากนี้ยังต้องการการแก้ไขด้วยการจัดการบรรทัดใหม่ แต่ได้รับคำตอบแล้วในที่นี้ )

แน่นอนว่าฉันสามารถรวมการเปลี่ยนแปลงนี้ไว้ในสำเนาเซิร์ฟเวอร์ของสคริปต์ แต่นั่นอาจทำให้เกิดความยุ่งเหยิงเช่นการใช้ VCS สำหรับการกำหนดเวอร์ชัน มีวิธีในการบรรลุสิ่งนี้ด้วย sed แบบพกพาอย่างเต็มที่?


ตัวอย่างสั้น ๆ ที่คุณให้มาเหมือนกันคุณแน่ใจหรือว่าไม่มีการพิมพ์ผิด นอกจากนี้ฉันสามารถดำเนินการ GNU sed จัดหาส่วนขยายการสำรองข้อมูลได้ทันทีหลังจาก-i
iruvar

ขอบคุณมากที่จำสิ่งนี้ได้ ฉันแก้ไขคำถามแล้ว บรรทัดที่สองส่งผลให้เกิดข้อผิดพลาดใน sed ของฉันมันคาดว่า '1s / ^ / <! DOCTYPE html> \ n /' เป็นไฟล์และบ่นว่าหาไฟล์ไม่พบ
Red

คำตอบ:


39

sed GNU ยอมรับส่วนขยายเพิ่มเติมหลังจาก-iนั้น ส่วนขยายจะต้องอยู่ในอาร์กิวเมนต์เดียวกันโดยไม่มีการเว้นวรรค ไวยากรณ์นี้ยังทำงานบน BSD sed

sed -i.bak -e '…' SOMEFILE

โปรดทราบว่าใน BSD -iจะเปลี่ยนพฤติกรรมเมื่อมีไฟล์อินพุตหลายไฟล์: จะถูกประมวลผลอย่างอิสระ (เช่น$ตรงกับบรรทัดสุดท้ายของแต่ละไฟล์) นอกจากนี้สิ่งนี้จะไม่ทำงานบน BusyBox

หากคุณไม่ต้องการใช้ไฟล์สำรองคุณสามารถตรวจสอบรุ่นของรุ่นที่พร้อมใช้งาน

case $(sed --help 2>&1) in
  *GNU*) set sed -i;;
  *) set sed -i '';;
esac
"$@" -e '…' "$file"

หรือมิฉะนั้นเพื่อหลีกเลี่ยงการอุดตันพารามิเตอร์ตำแหน่งให้กำหนดฟังก์ชัน

case $(sed --help 2>&1) in
  *GNU*) sed_i () { sed -i "$@"; };;
  *) sed_i () { sed -i '' "$@"; };;
esac
sed_i -e '…' "$file"

หากคุณไม่ต้องการรบกวนให้ใช้ Perl

perl -i -pe '…' "$file"

หากคุณต้องการเขียนสคริปต์แบบพกพาอย่าใช้-i- มันไม่ได้อยู่ใน POSIX ทำสิ่งที่ sed ทำภายใต้ประทุนด้วยตนเอง - เป็นเพียงโค้ดอีกหนึ่งบรรทัด

sed -e '…' "$file" >"$file.new"
mv -- "$file.new" "$file"

2
GNU sed -iก็มีความหมาย-sเช่นกัน และวิธีที่ง่ายที่สุดในการตรวจสอบ GNU sedคือการใช้sed vคำสั่งซึ่งเป็น noop ที่ถูกต้องสำหรับ GNU แต่ล้มเหลวทุกที่
mikeserv

แรงบันดาลใจจากเคล็ดลับข้างต้นนี่เป็นรุ่นพกพา (ถ้าน่าเกลียด) สำหรับผู้ที่ต้องการมันแม้ว่ามันจะวางไข่ subshell: sed -i$(sed v < /dev/null 2> /dev/null || echo -n " ''") -e '...' "$file"ถ้ามันไม่ใช่ GNU มันก็แทรกช่องว่างตามด้วยสองคำพูดหลังจาก-iนั้น ทำงานกับ BSD GNU sed -iได้รับเพียง
Ivan X

2
@IvanX ฉันระวังการใช้vคำสั่งเพื่อทดสอบ GNU sed จะเป็นอย่างไรถ้า FreeBSD ตัดสินใจใช้งาน
Gilles 'หยุดชั่วร้าย'

@Gilles Fair point แต่หน้า man สำหรับ GNU sed อธิบาย v ว่าเป็นไปตามวัตถุประสงค์นั้นอย่างแน่นอน (ตรวจพบว่าเป็น GNU sed และไม่ใช่อย่างอื่น) ดังนั้นเราจะหวังว่า * BSD จะให้เกียรตินั้น ฉันไม่คิดว่าจะมีการทดสอบอีกแบบหนึ่งโดยไม่ทำอะไรกับ GNU sed ในขณะที่ทำให้เกิดข้อผิดพลาดใน BSD sed (หรือกลับกัน) นอกเหนือจากการใช้ -i เอง แต่นั่นจะต้องสร้างไฟล์จำลองก่อน การทดสอบด้านบนของคุณนั้นโอเค แต่ไม่สะดวกสำหรับแบบอินไลน์ การหลีกเลี่ยง -i ทั้งหมดตามที่คุณแนะนำดูเหมือนจะเป็นทางออกที่ปลอดภัยที่สุด แต่ฉันก็โอเคกับการใช้sed vเพราะนั่นคือจุดประสงค์ของมันที่มีอยู่
Ivan X

1
@lapo อีกทางเลือกหนึ่งคือการกำหนดฟังก์ชั่นให้ดูที่การแก้ไขของฉัน
Gilles 'SO- หยุดความชั่วร้าย'

10

หากคุณไม่พบเคล็ดลับในการsedเล่นที่ดีคุณสามารถลอง:

  1. อย่าใช้-i:

    sed '1s/^/<!DOCTYPE html> \n/' "${file_name.html}" > "${file_name.html}.tmp" &&
      mv "${file_name.html}.tmp" "${file_name.html}"
  2. ใช้ Perl

    perl -i -pe 'print "<!DOCTYPE html> \n" if $.==1;' "${file_name.html}"

8

เอ็ด

คุณสามารถใช้edเพื่อเติมบรรทัดลงในไฟล์ที่มีอยู่เสมอ

$ printf '0a\n<!DOCTYPE html>\n.\nw\n' | ed my.html

รายละเอียด

บิตรอบ<!DOCTYPE html>มีคำสั่งไปยังสอนเพื่อเพิ่มบรรทัดนั้นไปยังแฟ้มedmy.html

sed

ฉันเชื่อว่าคำสั่งsedนี้ยังสามารถใช้ได้:

$ sed -i '1i<!DOCTYPE html>\n` testfile.csv

ในที่สุดฉันก็หันไปใช้ Perl แต่การใช้ ed เป็นทางเลือกที่ดีซึ่งไม่เป็นที่นิยมในหมู่ผู้ใช้ Unix อย่างที่ควรจะเป็น
Red

@Red - ดีใจที่ได้ยินว่าคุณแก้ไขปัญหาของคุณแล้ว ใช่ฉันไม่เคยเห็นแบบนั้นมาก่อน googling เปิดใช้งานและดูเหมือนจริง ๆ แล้วเป็นวิธีที่พกพาได้ดีที่สุดในการทำสิ่งนี้
slm

7

คุณสามารถทำสิ่งต่างๆperl -iภายใต้ประทุนได้ด้วยตนเอง:

{ rm -f file && { echo '<!DOCTYPE html>'; cat; } > file;} < file

เช่นperl -iไม่มีการสำรองข้อมูลและเช่นเดียวกับวิธีแก้ปัญหาส่วนใหญ่ที่ให้ที่นี่ระวังว่าอาจส่งผลกระทบต่อสิทธิ์การเป็นเจ้าของไฟล์และอาจเปลี่ยน symlink ให้เป็นไฟล์ปกติ

ด้วย:

sed '1i\
<!DOCTYPE html>' file 1<> file

sedจะเขียนทับไฟล์ทับตัวเองดังนั้นจะไม่ส่งผลกระทบต่อความเป็นเจ้าของและสิทธิ์หรือ symlink มันทำงานร่วมกับ GNU sedเพราะsedโดยทั่วไปแล้วจะได้อ่านบัฟเฟอร์ที่เต็มไปด้วยข้อมูลจากfile(4k ในกรณีของฉัน) ก่อนเขียนทับมันด้วยiคำสั่ง ที่จะไม่ทำงานหากไฟล์มากกว่า 4k ยกเว้นความจริงที่ว่าsedบัฟเฟอร์ที่ส่งออก

โดยทั่วไปsedทำงานบนบล็อก 4k สำหรับการอ่านและการเขียน หากบรรทัดที่แทรกมีขนาดเล็กกว่า 4k sedจะไม่เขียนทับบล็อกที่ยังไม่ได้อ่าน

ฉันไม่เชื่อหรอกว่า


ควรecho '<!DOCTYPE html>'หรือหนีโดยไม่มีเครื่องหมาย ""
โฆษณา

@ จุดที่ดี ฉันมักจะลืมข้อผิดพลาดนั้น ^ Wfeature ของ bash / zsh แบบโต้ตอบเนื่องจากฉันปิดการใช้งานด้วยตนเอง
Stéphane Chazelas

นี้เป็นหนึ่งในมากตอบไม่กี่ที่นี่ไม่ได้มีการรักษาความปลอดภัยหลุมที่เปิดกว้างของการเปลี่ยนเส้นทางไปยัง "ไฟล์ temp" กับคงชื่อที่คาดเดาได้โดยไม่ตรวจสอบถ้ามีอยู่แล้ว ฉันเชื่อว่านี่ควรเป็นคำตอบที่ยอมรับได้ สาธิตการใช้คำสั่งกลุ่มได้เป็นอย่างดี
Wildcard

3

FreeBSD sedซึ่งใช้บน Mac OS X เช่นกันต้องการ-eตัวเลือกหลังจาก-iสวิตช์เพื่อกำหนดและรับรู้คำสั่ง (regex) ต่อไปนี้อย่างถูกต้อง & ชัดเจน

ในคำอื่น ๆsed -i -e ...ควรจะทำงานกับทั้ง FreeBSD และ sedGNU

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

(อย่างไรก็ตามโปรดทราบว่าsedการแก้ไขไฟล์ในสถานที่นำไปสู่การเปลี่ยนแปลง inode ของไฟล์ดูที่"การแทนที่" การแก้ไขไฟล์ )

(ตามคำแนะนำทั่วไป FreeBSD รุ่นล่าสุดsedมี-rสวิตช์เพื่อเพิ่มความเข้ากันได้กับ GNU sed)

echo a > testfile.txt
ls -li testfile.txt
#gsed -i -e 's/a/A/' testfile.txt
#bsdsed -i 's/a/A/' testfile.txt  # does not work
bsdsed -i -e 's/a/A/' testfile.txt
ls -li testfile.txt
cat testfile.txt

5
ไม่bsdsed -i -e 's/a/A/'เป็นไม่ได้ในสถานที่การแก้ไขก็แก้ไขด้วยการประหยัดเดิมด้วย "อี" ต่อท้าย ( testfile.txt-e)
Stéphane Chazelas

โปรดทราบว่า GNU sed ยังรองรับ -E (นอกเหนือจาก -r) เพื่อความเข้ากันได้กับ FreeBSD -E น่าจะถูกระบุใน POSIX เวอร์ชันถัดไปดังนั้นเราทุกคนควรลืมเกี่ยวกับ -r ที่ไม่รู้สึกและแสร้งทำเป็นไม่เคยมีอยู่
Stéphane Chazelas

2

หากต้องการจำลองsed -iไฟล์เดี่ยวแบบพกพาในขณะที่หลีกเลี่ยงสภาพการแข่งขันให้มากที่สุด:

sed 'script' <<FILE >file
$(cat file)
FILE

โดยวิธีการนี้ยังจัดการกับปัญหาที่เป็นไปได้ที่sed -iแนะนำในนั้นขึ้นอยู่กับสิทธิ์ของไดเรกทอรีและไฟล์sed -iอาจทำให้ผู้ใช้สามารถเขียนทับไฟล์ที่ผู้ใช้นั้นไม่มีสิทธิ์ในการแก้ไข

คุณอาจสำรองข้อมูลเช่น:

sed 'script' <<FILE >file
$(tee file.old <file)
FILE

2

คุณสามารถใช้ Vim ในโหมด Ex:

ex -sc '1i|<!DOCTYPE html>' -cx file
  1. 1 เลือกบรรทัดแรก

  2. i แทรกข้อความและขึ้นบรรทัดใหม่

  3. x บันทึกและปิด

หรือแม้กระทั่งในความคิดเห็นมาตรฐาน exเก่าธรรมดา:

printf '%s\n' 1i '<!DOCTYPE html>' . x | ex file 

ไม่มีอะไรเฉพาะเจาะจงเป็นกลุ่มที่นี่ สิ่งนี้สอดคล้องกับข้อกำหนด POSIXexอย่างสมบูรณ์ยกเว้นว่าการนำไปใช้งานนั้นไม่จำเป็นต้องมีเพื่อสนับสนุนหลาย-cแฟล็ก สำหรับการพกพาที่แน่นอนฉันจะใช้printf '%s\n' 1i '<!DOCTYPE html>' . x | ex file
ไวลด์การ์ด
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.