Linux: วิธีใช้ไฟล์เป็นอินพุตและเอาต์พุตในเวลาเดียวกัน?


55

ฉันเพิ่งเรียกใช้ต่อไปนี้ในทุบตี:

uniq .bash_history > .bash_history

และไฟล์ประวัติของฉันว่างเปล่าโดยสมบูรณ์

ฉันเดาว่าฉันต้องการวิธีการอ่านไฟล์ทั้งหมดก่อนที่จะเขียนลงไป นั่นเป็นวิธีที่ทำ?

PS: เห็นได้ชัดว่าฉันคิดว่าการใช้ไฟล์ชั่วคราว แต่ฉันกำลังมองหาทางออกที่ดีกว่า


เป็นเพราะไฟล์เปิดจากขวาไปซ้าย ดูstackoverflow.com/questions/146435/…
WheresAlice

คุณต้องเขียนผลลัพธ์ไปยังไฟล์ใหม่ในไดเรกทอรีเดียวกันและเปลี่ยนชื่อที่ด้านบนของไฟล์เก่า วิธีการอื่นใดที่มีความเสี่ยงที่จะสูญเสียข้อมูลของคุณหากถูกขัดจังหวะผ่านไปครึ่งทาง เครื่องมือบางอย่างอาจซ่อนขั้นตอนนี้จากคุณ
kasperd

หรือbashจะไม่ใส่ข้อมูลที่ต่อเนื่องกันในประวัติถ้าคุณตั้งค่า HISTCONTROL เพื่อรวมการละเว้น ดู manpage
dave_thompson_085

คำตอบ:


49

ผมขอแนะนำให้ใช้spongeจากmoreutils จาก manpage:

DESCRIPTION
  sponge  reads  standard  input  and writes it out to the specified file. Unlike
  a shell redirect, sponge soaks up all its input before opening the output file.
  This allows for constructing pipelines that read from and write to the same 
  file.

หากต้องการใช้สิ่งนี้กับปัญหาของคุณลอง:

uniq .bash_history | sponge .bash_history

6
มันเหมือนแมว แต่มีความสามารถในการดูด: D
MilliaLover

77

ฉันแค่อยากให้คำตอบอื่นที่เรียบง่ายและไม่ใช้ฟองน้ำ (เพราะมักจะไม่รวมอยู่ในสภาพแวดล้อมที่มีน้ำหนักเบา)

echo "$(uniq .bash_history)" > .bash_history

ควรมีผลลัพธ์ที่ต้องการ เชลล์ย่อยได้รับการดำเนินการก่อน. bash_history จะเปิดขึ้นเพื่อการเขียน ดังที่อธิบายไว้ในคำตอบของ Phil P ตามเวลา. bash_history ถูกอ่านในคำสั่งดั้งเดิมมันได้ถูกตัดทอนโดยโอเปอเรเตอร์ '>' แล้ว


15
ปกติแล้วฉันไม่ได้เป็นแฟนของคำตอบที่ขุดคำถามโบราณที่มีคำตอบที่ถูกต้องและเป็นที่ยอมรับ - แต่นี่มันสง่างามเขียนได้ดีและสร้างข้อโต้แย้งที่แข็งแกร่งสำหรับความจำเป็น สำหรับฉันมันเพิ่มบางสิ่งบางอย่างในชุดคำตอบที่มีอยู่เดิม ยินดีต้อนรับสู่ SF ฮาร์ต (คุณมาที่นี่หนึ่งเดือนแล้ว แต่ฉันคิดว่านี่เป็นครั้งแรกที่คุณโพสต์ข้อความสำคัญ) ฉันหวังว่าจะอ่านคำตอบเพิ่มเติมจากคุณเช่นนี้!
MadHatter

4
นี่คือทางออกที่ดีที่สุด ฉันต้องใช้ subshell $()แทน backticks เนื่องจากปัญหาการหลบหนีบางอย่าง
CMCDragonkai

3
ฉันสงสัยว่าโซลูชันนี้ปรับขนาดเป็นไฟล์ขนาดใหญ่หรือไม่ ... 20 หรือ 50 GB
Amit Naidu

1
นี่ควรเป็นคำตอบที่ยอมรับได้
maxywb

1
ฉันใช้คำตอบนี้ทำecho "$(fmt -p '# ' -w 50 readme.txt)" > readme.txtวันนี้ การค้นหารอบ ๆ เป็นเวลานานเพื่อหาทางออกที่สง่างาม ขอบคุณมาก @Hart Simha!
shredalert

12

ปัญหาคือเปลือกของคุณตั้งค่าไปป์ไลน์คำสั่งก่อนเรียกใช้คำสั่ง ไม่ใช่เรื่องของ "อินพุทและเอาท์พุท" เนื้อหาของไฟล์นั้นได้หายไปแล้วก่อนที่ uniq จะทำงาน มันไปบางอย่างเช่น:

  1. เชลล์เปิด>ไฟล์เอาต์พุตสำหรับการเขียนโดยตัดทอน
  2. เชลล์ตั้งค่าให้ใช้ file-descriptor 1 (สำหรับ stdout) สำหรับเอาต์พุตนั้น
  3. เชลล์เรียกใช้งาน uniq ซึ่งอาจคล้ายกับ execlp ("uniq", "uniq", ".bash_history", NULL)
  4. uniq ทำงานเปิด. bash_history และไม่พบสิ่งใดที่นั่น

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


9

เคล็ดลับในการทำสิ่งนี้โดยไม่ใช้spongeคือคำสั่งต่อไปนี้:

{ rm .bash_history && uniq > .bash_history; } < .bash_history

นี่เป็นหนึ่งในกลโกงที่อธิบายไว้ในบทความ"การแทนที่" ที่ยอดเยี่ยมของไฟล์บน backreference.org

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

ข้อเสียของการแก้ปัญหานี้: หากuniqล้มเหลวด้วยเหตุผลบางอย่างประวัติของคุณจะหายไป



3

sedสคริปต์นี้จะลบรายการที่อยู่ติดกัน ด้วย-iตัวเลือกมันทำการปรับเปลี่ยนในสถานที่ มันมาจากsed infoไฟล์:

sed -i 'h;:b;$b;N;/^\(.*\)\n\1$/ {g;bb};$b;P;D' .bash_history

sed ยังคงใช้ไฟล์ temp เพิ่มคำตอบพร้อมstraceภาพประกอบ (ไม่ใช่ว่ามันสำคัญจริงๆ) :-)
Kyle Brandt

3
@ Kyle: จริง แต่ "ออกจากสายตาออกจากใจ" โดยส่วนตัวแล้วฉันจะใช้ไฟล์ชั่วคราวที่ชัดเจนเนื่องจากสิ่งที่ชอบprocess input > tmp && mv tmp inputนั้นง่ายกว่าและอ่านง่ายกว่าการใช้sedเล่ห์เหลี่ยมเพื่อหลีกเลี่ยงไฟล์ชั่วคราวและมันจะไม่เขียนทับต้นฉบับของฉันหากมันล้มเหลว (ฉันไม่รู้ว่าsed -iล้มเหลวอย่างสง่างาม - ฉันจะ คิดว่ามันจะ) นอกจากนี้ยังมีหลายสิ่งที่คุณสามารถทำได้ด้วยวิธีการส่งออกไปยังไฟล์ชั่วคราวที่ไม่สามารถทำได้โดยไม่ต้องมีอะไรเกี่ยวข้องมากกว่าsedสคริปต์นี้ ฉันรู้ว่าคุณรู้ทั้งหมดนี้ แต่มันอาจเป็นประโยชน์ต่อผู้สังเกตการณ์บางคน
Dennis Williamson

3

ในฐานะที่เป็นชิ้นอาหารอันโอชะที่น่าสนใจ, Sed ใช้ไฟล์ temp เช่นกัน (นี่เป็นเพียงแค่ทำเพื่อคุณ):

$ strace sed -i 's/foo/bar/g' foo    
open("foo", O_RDONLY|O_LARGEFILE)       = 3
...
open("./sedPmPv9z", O_RDWR|O_CREAT|O_EXCL|O_LARGEFILE, 0600) = 4
...
read(3, "foo\n"..., 4096)               = 4
write(4, "bar\n"..., 4)                 = 4
read(3, ""..., 4096)                    = 0
close(3)                                = 0
close(4)                                = 0
rename("./sedPmPv9z", "foo")            = 0
close(1)                                = 0
close(2)                                = 0

คำอธิบาย:
tempfile ./sedPmPv9zกลายเป็น fd 4 และfooไฟล์จะกลายเป็น fd 3 การดำเนินการอ่านอยู่บน fd 3 และการเขียนบน fd 4 (ไฟล์ temp) จากนั้นไฟล์ foo จะถูกเขียนทับด้วยไฟล์ temp ในการเปลี่ยนชื่อการโทร



0

ไฟล์ชั่วคราวนั้นสวยมากยกเว้นว่าคำสั่งที่เป็นปัญหาจะเกิดขึ้นเพื่อรองรับการแก้ไขในสถานที่ ( uniqไม่ - บางคนsedทำ ( sed -i))



โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.