การเปลี่ยนเส้นทาง IO และคำสั่ง head


9

ฉันพยายามแก้ไข.hgignoreไฟล์จาก Cygwin bash shell วันนี้อย่างรวดเร็วและฉันเพิ่มบรรทัดที่ผิด ฉันไม่แน่ใจว่านี่เป็นวิธีที่ดีที่สุดหรือไม่ แต่ฉันคิดว่าhead -1 .hgignoreจะลบบรรทัดที่ละเมิดออกไปอย่างรวดเร็ว(ก่อนหน้านี้ฉันเคยมีเพียงหนึ่งบรรทัดในไฟล์) นั่นเองเมื่อดำเนินการแล้วจะให้บรรทัดแรกเป็นเอาต์พุตเท่านั้น

แต่เมื่อฉันพยายามเปลี่ยนเส้นทางเอาต์พุตและเขียนไฟล์ใหม่โดยใช้head -1 .hgignore > .hgignoreไฟล์นั้นว่างเปล่า ทำไมสิ่งนี้ถึงเกิดขึ้น หากฉันลองต่อท้ายแทนhead -1 .hgignore >> .hgignoreจะเป็นการต่อท้ายอย่างถูกต้อง แต่นี่ไม่ใช่ผลลัพธ์ที่ต้องการอย่างชัดเจน เหตุใดการเปลี่ยนเส้นทางที่ถูกตัดทอนจึงไม่ทำงานในกรณีนี้


คำตอบ:


10

เมื่อเชลล์ได้รับบรรทัดคำสั่งเช่น: command > file.outเชลล์เองจะเปิดขึ้น (และอาจจะสร้าง) ไฟล์ที่ชื่อfile.outไฟล์ที่ชื่อว่า เชลล์ตั้งค่า file descriptor 0 เป็น file file descriptor ที่ได้จากการเปิด นั่นคือวิธีการเปลี่ยนเส้นทางของ I / O: ทุกกระบวนการรู้เกี่ยวกับ file descriptor 0, 1 และ 2

ส่วนที่ยากเกี่ยวกับเรื่องนี้เป็นวิธีการfile.outที่จะเปิด เวลาส่วนใหญ่คุณต้องการfile.outเปิดสำหรับการเขียนที่ offset 0 (เช่นถูกตัดทอน) และนี่คือสิ่งที่เชลล์ทำเพื่อคุณ มันถูกตัดทอน. hgignore เปิดเพื่อการเขียนและต่อท้ายไฟล์ที่เขียนถึง 0 จากนั้นเรียกheadใช้งาน การอุดตันไฟล์ทันที

ใน bash shell คุณต้องset noclobberเปลี่ยนพฤติกรรมนี้


ฉันเห็นแล้ว ฉันคิดว่าเชลล์ตัดทอนไฟล์ก่อนที่จะรันคำสั่ง แต่ฉันไม่รู้ว่าทำไม ขอบคุณสำหรับคำอธิบาย!
voithos

10

ฉันคิดว่าบรูซตอบว่าเกิดอะไรขึ้นกับท่อส่งกระสุน

หนึ่งของสาธารณูปโภคเล็ก ๆ น้อย ๆ ที่ชื่นชอบเป็นspongeคำสั่งจากmoreutils จะแก้ปัญหานี้ได้อย่างแน่นอนโดย "แช่" อินพุตที่มีอยู่ทั้งหมดก่อนที่จะเปิดไฟล์เอาต์พุตเป้าหมายและเขียนข้อมูล ช่วยให้คุณสามารถเขียนท่อส่งตรงตามที่คุณต้องการ:

$ head -1 .hgignore | sponge .hgignore

ทางออกของคนยากจนคือไพพ์เอาท์พุทไปยังไฟล์ชั่วคราวจากนั้นหลังจากไพพ์ไลน์เสร็จแล้ว (เช่นคำสั่งถัดไปที่คุณรัน) คือการย้ายไฟล์ temp กลับไปยังตำแหน่งไฟล์ดั้งเดิม

$ head -1 .hgingore > .hgignore.tmp
$ mv .hgignore{.tmp,}

เมื่อมองดูสิ่งนี้ในอีกไม่กี่ปีต่อมามีความคิดเกิดขึ้นกับฉัน: เราทำhead -1 .hgignore | tee .hgignoreไม่ได้เหรอ teeอยู่ใน coreutils และเป็น perk / side-
effects

@voithos ความรู้ของฉันteeเปิดขึ้นและตัดทอนไฟล์ที่มันเขียนไว้เมื่อมันถูกอินสแตนซ์เหมือนทุกสิ่งทุกอย่างดังนั้นมันจึงไม่ได้แก้ปัญหาหลักของสภาวะการแข่งขันในการอ่านเนื้อหาไฟล์ก่อนที่คุณจะตัดทอนด้วยการเขียน
Caleb

คุณพูดถึงจุดที่ฉันไม่ทราบจริง ๆ แล้ว - นั่นคือคำสั่ง piped จะเริ่มต้นทันทีแทนที่จะเรียงตามลำดับ ถูกต้องหรือไม่ อย่างไรก็ตามฉันทดสอบและtee ดูเหมือนว่าจะทำสิ่งที่ต้องการ ฉันมีรุ่น8.13ในเครื่องของฉัน
voithos

1
@voithos ใช่คำสั่งในไปป์ไลน์และช่องสัญญาณอินพุต / เอาท์พุตที่เกี่ยวข้องทั้งหมดเริ่มต้นในลำดับที่กลับกันดังนั้นไปป์ไลน์พร้อมรับข้อมูลเมื่อคนแรกเริ่มให้มัน ฉันสงสัยว่าการทดสอบของคุณมีข้อบกพร่องเพราะคุณอาจใช้ข้อมูลจำนวนน้อยเกินไปและได้รับแคชทั้งหมดในบัฟเฟอร์การอ่านก่อนที่คุณจะต้องการ teeโปรแกรมจะตัดไฟล์ของคุณมันไม่ได้ติดตั้งไปคู่บัฟเฟอร์พวกเขา
คาเลบ

3

ใน

head -n 1 file > file

fileถูกตัดทอนก่อนที่headจะเริ่ม แต่ถ้าคุณเขียนมัน:

head -n 1 file 1<> file

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

อย่างไรก็ตามหลังจากที่headได้กลับมาและในขณะที่ยังคงเปิดอยู่คุณสามารถเรียกคำสั่งที่ไม่อีกfdtruncate

ตัวอย่างเช่น

{ head -n 1 file; perl -e 'truncate STDOUT, tell STDOUT'; } 1<> file

สิ่งที่สำคัญที่นี่คือtruncateข้างบนheadเพียงแค่เลื่อนเคอร์เซอร์สำหรับ fd 1 ภายในไฟล์หลังจากบรรทัดแรก มันจะเขียนบรรทัดแรกที่เราไม่ต้องการ แต่ก็ไม่เป็นอันตราย

ด้วยหัว POSIX เราสามารถหนีไปได้โดยไม่ต้องเขียนบรรทัดแรกนั้นใหม่:

{ head -n 1 > /dev/null
  perl -e 'truncate STDIN, tell STDIN'
} <> file

ที่นี่เราใช้ข้อเท็จจริงที่headย้ายตำแหน่งเคอร์เซอร์ใน stdin ในขณะที่headโดยทั่วไปแล้วจะอ่านอินพุตของมันโดยชิ้นใหญ่เพื่อปรับปรุงประสิทธิภาพ POSIX จะต้อง (ถ้าเป็นไปได้) เพื่อseekกลับหลังบรรทัดแรกถ้ามันเกินกว่าที่กำหนดไว้ โปรดทราบว่าการใช้งานไม่ได้ทั้งหมดทำ

หรือคุณสามารถใช้readคำสั่งของเชลล์แทนในกรณีนี้:

{ read -r dummy; perl -e 'truncate STDIN, tell STDIN'; } <> file

1
สเตฟาน, คุณรู้ว่ามีมาตรฐานหรือ coreutils คำสั่งที่สามารถตัดSTDINคล้ายกับสิ่งที่คุณทำได้โดยใช้perlข้างต้น
Iruvar

2
@ 1_CR ไม่ใช่ ddสามารถตัดทอนที่ออฟเซ็ตสัมบูรณ์โดยพลการใด ๆในไฟล์ได้ ดังนั้นคุณสามารถกำหนดออฟเซ็ตไบต์ของบรรทัดที่สองและตัดจากที่นั่นด้วยdd bs=1 seek="$offset" of=file
Stéphane Chazelas

1

ทางออกของ Real Man คือ

ed .hgignore
$d
wq

หรือเป็นหนึ่งซับ

printf '%s\n' '$d' 'wq' | ed .hgignore

หรือด้วย GNU sed:

sed -i '$d' .hgignore

(ไม่ฉันล้อเล่นฉันจะใช้เครื่องมือแก้ไขแบบโต้ตอบvi .hgignore GddZZ)


ฉันสงสัยว่ามีประโยชน์ในการใช้:wqมากกว่าZZหรือไม่
voithos

นอกจากนี้:xซึ่งเป็นสิ่งที่นิ้วมือของฉันทำโดยอัตโนมัติ
เกล็นแจ็คแมน

และZQเหมือนกับ:q!
glenn jackman

ZZ และ: x เขียนได้เฉพาะในกรณีที่มีบางอย่างที่จะเขียน ... : w fsyncs แฟ้มไปยังดิสก์เสมอโดยไม่คำนึงว่ามันต้องการ ฉันใช้: xa เพราะฉันใช้แท็บ
xenoterracide


0

สำหรับไฟล์ในสถานที่การแก้ไขนอกจากนี้คุณยังสามารถใช้เปิดไฟล์จับเคล็ดลับที่แสดงโดยJürgenHötzelในการส่งออกการเปลี่ยนเส้นทางจาก sed 'S / C / D /' myFile เพื่อ myFile

exec 3<.hgignore
rm .hgignore  # prevent open file from being truncated
head -1 <&3 > .hgignore

ls -l .hgignore  # note that permissions may have changed

2
และหลังจากที่rm .hgignoreพลังของคุณล้มเหลวทำให้ต้องทำงานหนักหลายชั่วโมง ตกลงมันไม่สำคัญ.hgignoreแต่ทำไมคุณถึงทำสิ่งที่ซับซ้อนอยู่ดี ดังนั้น downvote ของฉัน: ถูกต้องทางเทคนิค แต่เป็นความคิดที่ดีมาก
Gilles 'หยุดความชั่วร้าย'

@Gilles อาจไม่เป็นความคิดที่ดีนัก แต่นั่นเป็นสิ่งที่perl -i(สำหรับการแก้ไขในสถานที่) ทำเช่นนั้นและฉันจะไม่แปลกใจหากการใช้งานบางอย่างของsed -iมันทำเช่นกัน (แม้ว่า GNU รุ่นล่าสุดsedดูเหมือนจะไม่)
Stéphane Chazelas
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.