การอ่านและการเขียนไฟล์: คำสั่ง tee


10

เป็นที่ทราบกันดีว่าคำสั่งเช่นนี้:

cat filename | some_sed_command >filename

ลบไฟล์ชื่อไฟล์เนื่องจากการเปลี่ยนเส้นทางเอาต์พุตที่ถูกเรียกใช้งานก่อนคำสั่งทำให้ชื่อไฟล์ถูกตัดทอน

หนึ่งสามารถแก้ปัญหาด้วยวิธีต่อไปนี้:

cat file | some_sed_command | tee file >/dev/null

แต่ฉันไม่แน่ใจว่าสิ่งนี้จะใช้ได้ในทุกกรณี: จะเกิดอะไรขึ้นถ้าไฟล์ (และผลลัพธ์ของคำสั่ง sed) มีขนาดใหญ่มาก ระบบปฏิบัติการจะหลีกเลี่ยงการเขียนทับเนื้อหาบางส่วนที่ยังไม่ได้อ่านได้อย่างไร? ฉันเห็นว่ายังมีคำสั่งฟองน้ำซึ่งควรทำงานในกรณีใด ๆ : มันเป็น "ปลอดภัย" กว่าที


เป้าหมายหลักของคุณคืออะไร (ในแง่ง่าย)
Sergiy Kolodyazhnyy

@Serg เพียงแค่เข้าใจว่าสิ่งต่าง ๆ ทำงานอย่างไรคำตอบที่เขียนโดย kos ชี้แจงเรื่องนี้
VeryHardCoder

คำตอบ:


10

หนึ่งสามารถแก้ปัญหาด้วยวิธีต่อไปนี้:

cat file | some_sed_command | tee file >/dev/null

ไม่

โอกาสที่fileจะถูกตัดลดลง แต่ไม่มีการรับประกันจะไม่ตัดcat file | some_sed_command | tee file >/dev/nullfile

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

เนื่องจากโอกาสในการกระทำผิดคำสั่งที่จะหยิบครั้งแรกในระหว่างสามคำสั่งจะต่ำกว่าโอกาสสำหรับคำสั่งที่กระทำผิดจะหยิบครั้งแรกในระหว่างสองคำสั่งก็มีโอกาสน้อยที่fileจะถูกตัด แต่ก็ยังคงที่จะเกิดขึ้น

script.sh:

#!/bin/bash
for ((i=0; i<100; i++)); do
    cat >file <<-EOF
    foo
    bar
    EOF
    cat file |
        sed 's/bar/baz/' |
        tee file >/dev/null
    [ -s file ] &&
        echo 'Not truncated' ||
        echo 'Truncated'
done |
    sort |
    uniq -c
rm file
% bash script.sh
 93 Not truncated
  7 Truncated
% bash script.sh
 98 Not truncated
  2 Truncated
% bash script.sh
100 Not truncated

ดังนั้นไม่เคยcat file | some_sed_command | tee file >/dev/nullใช้สิ่งที่ต้องการ ใช้spongeตามที่แนะนำ Oli

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

$ cat file
foo
bar
$ for ((i=0; i<100; i++)); do <<<"$(<file)" sed 's/bar/baz/' >file; done
$ cat file
foo
baz

9

สำหรับsedโดยเฉพาะคุณสามารถใช้-iอาร์กิวเมนต์แบบแทน มันเพิ่งบันทึกกลับไปยังไฟล์ที่เปิดเช่น:

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

หากคุณต้องการทำอะไรที่หนักกว่านี้สมมติว่าคุณทำมากกว่าsedนั้นใช่คุณสามารถบัฟเฟอร์สิ่งทั้งหมดด้วยsponge(จากmoreutilsแพ็คเกจ) ซึ่งจะ "ดูดซับ" stdin ทั้งหมดก่อนที่จะเขียนลงในไฟล์ มันเหมือนteeแต่มีฟังก์ชั่นน้อยลง สำหรับการใช้งานขั้นพื้นฐานแม้ว่ามันจะเป็นการแทนที่แบบดรอปอิน:

cat file | some_sed_command | sponge file >/dev/null

ปลอดภัยกว่าไหม? อย่างแน่นอน. มันอาจมีข้อ จำกัด ดังนั้นถ้าคุณทำสิ่งที่ใหญ่โต (และไม่สามารถแก้ไขด้วย sed) ได้คุณอาจต้องการแก้ไขไฟล์ที่สองจากนั้นmvไฟล์นั้นกลับไปที่ชื่อไฟล์เดิม นั่นควรเป็น atomic (ดังนั้นทุกอย่างขึ้นอยู่กับไฟล์เหล่านี้จะไม่แตกหากต้องการการเข้าถึงที่คงที่)



0

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

  1. จะยอมรับชื่อของไฟล์ที่ส่งออกเป็นพารามิเตอร์
  2. มันจะสร้างไฟล์เอาต์พุตเมื่ออินพุตทั้งหมดถูกประมวลผลแล้ว

คุณจะเห็นว่าปัญหาที่รู้จักกันดีว่า OP หมายถึงว่าเชลล์จะสร้างไฟล์ทั้งหมดที่จำเป็นสำหรับไพพ์ในการทำงานก่อนที่จะเริ่มดำเนินการคำสั่งในไพพ์ไลน์ดังนั้นมันจึงเป็นเชลล์ที่ตัดทอนจริง ๆ ไฟล์เอาต์พุต (ซึ่งน่าเสียดายที่เป็นไฟล์อินพุต) ก่อนที่คำสั่งใด ๆ จะมีโอกาสแม้แต่จะเริ่มดำเนินการ

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

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

    shuf --output=file --random-source=/dev/zero 

--random-source=/dev/zeroส่วนเทคนิคshufในการทำสิ่งที่ตนไม่ต้องทำสับใด ๆ เลยดังนั้นมันจะ buffer ป้อนข้อมูลของคุณโดยไม่ต้องเปลี่ยนมัน

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