นี้น่าจะเป็นวิธีการแก้ปัญหาที่ซับซ้อน
ฉันกำลังมองหาตัวดำเนินการง่ายๆเช่น ">>" แต่สำหรับการสั่งซื้อล่วงหน้า
ฉันกลัวว่ามันจะไม่มีอยู่จริง ฉันจะต้องทำสิ่งที่ชอบ
mv myfile tmp แมว myheader tmp> myfile
อะไรที่ฉลาดกว่านี้?
คำตอบ:
สับข้างล่างนี้เป็นคำตอบที่ปิดข้อมืออย่างรวดเร็วซึ่งจะทำงานและได้รับจำนวนมาก upvotes จากนั้นเมื่อคำถามเป็นที่นิยมมากขึ้นและเวลาผ่านไปมากขึ้นผู้คนที่โกรธเคืองเริ่มรายงานว่ามันใช้งานได้จริง แต่อาจมีสิ่งแปลก ๆ เกิดขึ้นได้หรือไม่ได้ผลเลยดังนั้นจึงถูกโหวตลงอย่างรุนแรงในช่วงเวลาหนึ่ง สนุกดังกล่าว.
โซลูชันนี้ใช้ประโยชน์จากการใช้งานตัวอธิบายไฟล์ในระบบของคุณและเนื่องจากการนำไปใช้งานแตกต่างกันอย่างมีนัยสำคัญระหว่าง nixes ความสำเร็จจึงขึ้นอยู่กับระบบทั้งหมดไม่สามารถพกพาได้อย่างแน่นอนและไม่ควรพึ่งพาสิ่งใดก็ตามที่มีความสำคัญอย่างคลุมเครือ
ตอนนี้คำตอบคือ:
การสร้าง file descriptor อื่นสำหรับไฟล์ ( exec 3<> yourfile
) ดังนั้นการเขียนไปที่ ( >&3
) ดูเหมือนว่าจะเอาชนะปัญหาการอ่าน / เขียนในไฟล์เดียวกัน ใช้ได้กับฉันบนไฟล์ 600K ด้วย awk อย่างไรก็ตามการลองใช้เคล็ดลับเดียวกันโดยใช้ 'cat' ล้มเหลว
การส่งคำนำหน้าเป็นตัวแปรไปยัง awk ( -v TEXT="$text"
) จะเอาชนะปัญหาเครื่องหมายคำพูดตามตัวอักษรซึ่งป้องกันไม่ให้ทำเคล็ดลับนี้ด้วย 'sed'
#!/bin/bash
text="Hello world
What's up?"
exec 3<> yourfile && awk -v TEXT="$text" 'BEGIN {print TEXT}{print}' yourfile >&3
ยังคงใช้ไฟล์ชั่วคราว แต่อย่างน้อยก็อยู่ในบรรทัดเดียว:
echo "text" | cat - yourfile > /tmp/out && mv /tmp/out yourfile
-
หลังจากทำcat
อะไร?
echo -n "text"
yourfile
เป็น symlink สิ่งนี้จะไม่ทำตามที่คุณต้องการ
echo '0a
your text here
.
w' | ed some_file
ed เป็นตัวแก้ไขมาตรฐาน! http://www.gnu.org/fun/jokes/ed.msg.html
0r header.file
echo -e '0a\nyour text here\n.\nw' | ed some_file
John Mee: วิธีการของคุณไม่ได้รับการรับรองว่าจะใช้งานได้และอาจล้มเหลวหากคุณนำหน้าข้อมูลมากกว่า 4096 ไบต์ (อย่างน้อยนั่นคือสิ่งที่เกิดขึ้นกับ gnu awk แต่ฉันคิดว่าการใช้งานอื่น ๆ จะมีข้อ จำกัด ที่คล้ายกัน) ไม่เพียง แต่จะล้มเหลวในกรณีนั้น แต่จะเข้าสู่ลูปที่ไม่มีที่สิ้นสุดซึ่งจะอ่านเอาต์พุตของตัวเองซึ่งจะทำให้ไฟล์เติบโตขึ้นจนกว่าจะเต็มพื้นที่ว่างทั้งหมด
ลองด้วยตัวคุณเอง:
exec 3<>myfile && awk 'BEGIN{for(i=1;i<=1100;i++)print i}{print}' myfile >&3
(คำเตือน: ฆ่ามันหลังจากนั้นสักครู่มิฉะนั้นมันจะเต็มระบบไฟล์)
ยิ่งไปกว่านั้นการแก้ไขไฟล์ด้วยวิธีนั้นอันตรายมากและเป็นคำแนะนำที่ไม่ดีมากเช่นหากมีบางอย่างเกิดขึ้นในขณะที่ไฟล์กำลังแก้ไข (เกิดความผิดพลาดดิสก์เต็ม) คุณเกือบจะรับประกันได้ว่าไฟล์จะอยู่ในสถานะไม่สอดคล้อง
เป็นไปไม่ได้หากไม่มีไฟล์ชั่วคราว แต่ที่นี่ต้องใช้โปรแกรม oneliner
{ echo foo; cat oldfile; } > newfile && mv newfile oldfile
คุณสามารถใช้เครื่องมืออื่น ๆ เช่น ed หรือ perl เพื่อทำได้โดยไม่ต้องใช้ไฟล์ temp
อาจเป็นที่น่าสังเกตว่าบ่อยครั้งเป็นความคิดที่ดีที่จะสร้างไฟล์ชั่วคราวอย่างปลอดภัยโดยใช้ยูทิลิตี้เช่นmktempอย่างน้อยถ้าสคริปต์จะถูกเรียกใช้ด้วยสิทธิ์ระดับรูท ตัวอย่างเช่นคุณสามารถทำสิ่งต่อไปนี้ (อีกครั้งใน bash):
(tmpfile=`mktemp` && { echo "prepended text" | cat - yourfile > $tmpfile && mv $tmpfile yourfile; } )
หากคุณต้องการสิ่งนี้ในคอมพิวเตอร์ที่คุณควบคุมให้ติดตั้งแพ็คเกจ "moreutils" และใช้ "ฟองน้ำ" จากนั้นคุณสามารถทำได้:
cat header myfile | sponge myfile
{ echo "prepended text"; cat myfile } | sponge myfile
การใช้ bash heredoc คุณสามารถหลีกเลี่ยงความจำเป็นในการใช้ไฟล์ tmp:
cat <<-EOF > myfile
$(echo this is prepended)
$(cat myfile)
EOF
ใช้งานได้เนื่องจาก $ (cat myfile) ได้รับการประเมินเมื่อสคริปต์ bash ได้รับการประเมินก่อนที่ cat ที่มีการเปลี่ยนเส้นทางจะถูกดำเนินการ
สมมติว่าไฟล์ที่คุณต้องการแก้ไขคือ my.txt
$cat my.txt
this is the regular file
และไฟล์ที่คุณต้องการนำหน้าคือส่วนหัว
$ cat header
this is the header
ตรวจสอบให้แน่ใจว่ามีบรรทัดว่างสุดท้ายในไฟล์ส่วนหัว
ตอนนี้คุณสามารถนำหน้าด้วย
$cat header <(cat my.txt) > my.txt
คุณจบลงด้วย
$ cat my.txt
this is the header
this is the regular file
เท่าที่ฉันรู้สิ่งนี้ใช้ได้เฉพาะใน 'bash'
this is the header
ใน my.txt แม้ว่าฉันจะอัปเดต Bash ไป4.3.42(1)-release
แล้วฉันก็ได้ผลลัพธ์เหมือนกัน
<(...)
)เขียนดังนั้นจึงไม่มีการรับประกันว่าmy.txt
จะอ่านแบบเต็มหน้าโดยที่เทคนิคนี้จะไม่ได้ผล
เมื่อคุณเริ่มพยายามทำสิ่งที่ยากในเชลล์สคริปต์ฉันขอแนะนำอย่างยิ่งให้พิจารณาการเขียนสคริปต์ใหม่ในภาษาสคริปต์ที่ "เหมาะสม" (Python / Perl / Ruby / etc)
สำหรับการใส่บรรทัดไว้ล่วงหน้าในไฟล์นั้นจะไม่สามารถทำได้ผ่านการไพพ์เนื่องจากเมื่อคุณทำอะไรก็ตามที่ต้องการcat blah.txt | grep something > blah.txt
จะทำให้ไฟล์ว่างโดยไม่ได้ตั้งใจ มีคำสั่งยูทิลิตี้ขนาดเล็กที่เรียกว่าsponge
คุณสามารถติดตั้งได้ (คุณทำcat blah.txt | grep something | sponge blah.txt
และบัฟเฟอร์เนื้อหาของไฟล์จากนั้นเขียนลงในไฟล์) มันคล้ายกับไฟล์ชั่วคราว แต่คุณไม่จำเป็นต้องทำอย่างชัดเจน แต่ฉันจะบอกว่านั่นเป็นข้อกำหนดที่ "แย่กว่า" มากกว่าพูดว่า Perl
อาจมีวิธีทำผ่าน awk หรือคล้ายกัน แต่ถ้าคุณต้องใช้เชลล์สคริปต์ฉันคิดว่าไฟล์ temp เป็นวิธีที่ง่ายที่สุด (/ เท่านั้น?) ..
เช่นเดียวกับ Daniel Velkov แนะนำให้ใช้ที
สำหรับฉันแล้วนั่นเป็นวิธีง่ายๆที่ชาญฉลาด:
{ echo foo; cat bar; } | tee bar > /dev/null
แก้ไข: สิ่งนี้เสีย ดูพฤติกรรมแปลก ๆ เมื่อเติมไฟล์ด้วย cat และ tee
วิธีแก้ปัญหาการเขียนทับคือการใช้tee
:
cat header main | tee main > /dev/null
อันที่ฉันใช้ อันนี้ช่วยให้คุณระบุคำสั่งตัวอักษรพิเศษและอื่น ๆ ในแบบที่คุณชอบ:
echo -e "TEXTFIRSt\n$(< header)\n$(< my.txt)" > my.txt
PS: จะไม่ทำงานหากไฟล์มีข้อความที่มีแบ็กสแลชเท่านั้นทำให้ถูกตีความว่าเป็นอักขระหลีก
ส่วนใหญ่สำหรับกอล์ฟสนุก / เปลือก แต่
ex -c '0r myheader|x' myfile
จะทำเคล็ดลับและไม่มีท่อหรือการเปลี่ยนเส้นทาง แน่นอนว่า vi / ex ไม่ได้มีไว้สำหรับการใช้งานแบบไม่โต้ตอบดังนั้น vi จะกระพริบสั้น ๆ
ทำไมไม่ใช้คำสั่ง ed (ตามที่แนะนำโดย fluffle ที่นี่)?
ed อ่านไฟล์ทั้งหมดลงในหน่วยความจำและทำการแก้ไขไฟล์ในสถานที่โดยอัตโนมัติ!
ดังนั้นถ้าไฟล์ของคุณไม่ใหญ่มาก ...
# cf. "Editing files with the ed text editor from scripts.",
# http://wiki.bash-hackers.org/doku.php?id=howto:edit-ed
prepend() {
printf '%s\n' H 1i "${1}" . wq | ed -s "${2}"
}
echo 'Hello, world!' > myfile
prepend 'line to prepend' myfile
วิธีแก้ปัญหาอื่นคือใช้การจัดการไฟล์แบบเปิดตามที่แนะนำโดยJürgenHötzelในการเปลี่ยนเส้นทางเอาต์พุตจาก sed 's / c / d /' myFile ไปยัง myFile
echo cat > manipulate.txt
exec 3<manipulate.txt
# Prevent open file from being truncated:
rm manipulate.txt
sed 's/cat/dog/' <&3 > manipulate.txt
ทั้งหมดนี้สามารถใส่ไว้ในบรรทัดเดียวได้แน่นอน
ตัวแปรในโซลูชันของ cb0 สำหรับ "no temp file" เพื่อนำหน้าข้อความคงที่:
echo "text to prepend" | cat - file_to_be_modified | ( cat > file_to_be_modified )
สิ่งนี้อาศัยการดำเนินการย่อยของเชลล์อีกครั้ง - the (.. ) - เพื่อหลีกเลี่ยงไม่ให้แมวปฏิเสธที่จะมีไฟล์เดียวกันสำหรับอินพุตและเอาต์พุต
หมายเหตุ: ชอบโซลูชันนี้ อย่างไรก็ตามใน Mac ของฉันไฟล์ต้นฉบับหายไป (คิดว่าไม่ควรทำ แต่เป็นเช่นนั้น) ซึ่งสามารถแก้ไขได้โดยการเขียนโซลูชันของคุณเป็น: echo "text to prepend" | แมว - file_to_be_modified | แมว> tmp_file; mv tmp_file file_to_be_modified
นี่คือสิ่งที่ฉันค้นพบ:
echo -e "header \n$(cat file)" >file
sed -i -e '1rmyheader' -e '1{h;d}' -e '2{x;G}' myfile
คำเตือน: ต้องทำงานเพิ่มอีกเล็กน้อยเพื่อตอบสนองความต้องการของ OP
ควรมีวิธีที่จะทำให้แนวทางใจเย็นโดย @shixilun ทำงานได้แม้จะมีความไม่พอใจก็ตาม ต้องมีคำสั่ง bash เพื่อหลีกเลี่ยงช่องว่างเมื่ออ่านไฟล์ในสตริงทดแทน sed (เช่นแทนที่อักขระขึ้นบรรทัดใหม่ด้วย '\ n' คำสั่งเชลล์vis
และcat
สามารถจัดการกับอักขระที่พิมพ์ไม่ได้ แต่ไม่ใช่ช่องว่างดังนั้นสิ่งนี้จะไม่แก้ปัญหาของ OP ปัญหา:
sed -i -e "1s/^/$(cat file_with_header.txt)/" file_to_be_prepended.txt
ล้มเหลวเนื่องจากการขึ้นบรรทัดใหม่ดิบในสคริปต์การแทนที่ซึ่งจำเป็นต้องนำหน้าด้วยอักขระต่อเนื่องบรรทัด () และอาจตามด้วย & เพื่อให้เชลล์และมีความสุขเช่นคำตอบ SO นี้
sed
มีขีด จำกัด ขนาด 40K สำหรับคำสั่งแทนที่การค้นหาที่ไม่ใช่โกลบอล (ไม่มีการต่อท้าย / g หลังรูปแบบ) ดังนั้นจึงน่าจะหลีกเลี่ยงปัญหาการบุกรุกบัฟเฟอร์ที่น่ากลัวของ awk ที่ anonymous เตือน
sed -i -e "1s/^/new first line\n/" old_file.txt
ด้วย$ (command)คุณสามารถเขียนผลลัพธ์ของคำสั่งลงในตัวแปรได้ ดังนั้นฉันจึงทำสามคำสั่งในหนึ่งบรรทัดและไม่มีไฟล์ชั่วคราว
originalContent=$(cat targetfile) && echo "text to prepend" > targetfile && echo "$originalContent" >> targetfile
หากคุณกำลังเขียนสคริปต์ใน BASH จริงๆแล้วคุณสามารถออก:
cat - yourfile / tmp / out && mv / tmp / out yourfile
นั่นเป็นจริงในตัวอย่างที่ซับซ้อนคุณโพสต์ในคำถามของคุณเอง
cat - yourfile <<<"text" > /tmp/out && mv /tmp/out yourfile
อย่างไรก็ตามมันแตกต่างจากคำตอบของฉันพอสมควรที่ควรเป็นคำตอบของตัวเอง
หากคุณมีไฟล์ขนาดใหญ่ (ไม่กี่ร้อยกิโลไบต์ในกรณีของฉัน) และเข้าถึง python สิ่งนี้เร็วกว่าcat
โซลูชันท่อมาก:
python -c 'f = "filename"; t = open(f).read(); open(f, "w").write("text to prepend " + t)'
วิธีแก้ปัญหาด้วยprintf
:
new_line='the line you want to add'
target_file='/file you/want to/write to'
printf "%s\n$(cat ${target_file})" "${new_line}" > "${target_file}"
คุณยังสามารถทำได้:
printf "${new_line}\n$(cat ${target_file})" > "${target_file}"
แต่ในกรณีนี้คุณต้องแน่ใจว่าไม่มีที่ใดเลย%
รวมถึงเนื้อหาของไฟล์เป้าหมายเนื่องจากสามารถตีความและทำให้ผลลัพธ์ของคุณแย่ลงได้
echo
ดูเหมือนจะเป็นตัวเลือกที่ปลอดภัยกว่า echo "my new line\n$(cat my/file.txt)" > my/file.txt
${new_line}
ไม่ใช่ไฟล์เป้าหมาย
คุณสามารถใช้บรรทัดคำสั่ง perl:
perl -i -0777 -pe 's/^/my_header/' tmp
โดยที่ -i จะสร้างการแทนที่ไฟล์แบบอินไลน์และ -0777 จะทำให้ไฟล์ทั้งหมดคลาดเคลื่อนและทำให้ ^ ตรงกับจุดเริ่มต้นเท่านั้น -pe จะพิมพ์ทุกบรรทัด
หรือถ้า my_header เป็นไฟล์:
perl -i -0777 -pe 's/^/`cat my_header`/e' tmp
โดยที่ / e จะอนุญาตให้มีการประเมินโค้ดในการแทนที่
current=`cat my_file` && echo 'my_string' > my_file && echo $current >> my_file
โดย "my_file" คือไฟล์ที่นำหน้า "my_string" ไป
ฉันชอบวิธีedของ @ fluffleที่ดีที่สุด ท้ายที่สุดแล้วบรรทัดคำสั่งของเครื่องมือใด ๆ จะสลับกับคำสั่งเอดิเตอร์แบบสคริปต์ก็เป็นสิ่งเดียวกัน ไม่เห็นว่าโซลูชันตัวแก้ไขสคริปต์ "ความสะอาด" มีน้อยกว่าหรือไม่
นี่คือซับเดียวของฉันต่อท้ายไฟล์.git/hooks/prepare-commit-msg
ใน repo .gitmessage
เพื่อส่งข้อความ:
echo -e "1r $PWD/.gitmessage\n.\nw" | ed -s "$1"
ตัวอย่าง.gitmessage
:
# Commit message formatting samples:
# runlevels: boot +consolekit -zfs-fuse
#
ฉันทำ1r
แทน0r
เพราะนั่นจะทำให้บรรทัดว่างพร้อมเขียนอยู่ด้านบนของไฟล์จากเทมเพลตดั้งเดิม อย่าวางบรรทัดว่างไว้ด้านบนของคุณ.gitmessage
คุณจะจบลงด้วยสองบรรทัดที่ว่างเปล่า -s
ระงับเอาต์พุตข้อมูลการวินิจฉัยของ ed
ในการทำสิ่งนี้ฉันได้ค้นพบว่าสำหรับกลุ่มคนที่ชื่นชอบมันก็ดีที่จะมี:
[core]
editor = vim -c ':normal gg'
ตัวแปร ftw?
NEWFILE=$(echo deb http://mirror.csesoc.unsw.edu.au/ubuntu/ $(lsb_release -cs) main universe restricted multiverse && cat /etc/apt/sources.list)
echo "$NEWFILE" | sudo tee /etc/apt/sources.list
ฉันคิดว่านี่เป็นรูปแบบที่สะอาดที่สุดของ ed:
cat myheader | { echo '0a'; cat ; echo -e ".\nw";} | ed myfile
เป็นฟังก์ชัน:
function prepend() { { echo '0a'; cat ; echo -e ".\nw";} | ed $1; }
cat myheader | prepend myfile
IMHO ไม่มีโซลูชันเชลล์ (และจะไม่เป็นหนึ่งเดียว) ที่จะทำงานได้อย่างสม่ำเสมอและเชื่อถือได้ไม่ว่าไฟล์ทั้งสองจะมีขนาดเท่าใดmyheader
และmyfile
. เหตุผลก็คือถ้าคุณต้องการทำเช่นนั้นโดยไม่เกิดซ้ำกับไฟล์ชั่วคราว (และโดยไม่ปล่อยให้เชลล์เกิดซ้ำแบบเงียบ ๆ ไปยังไฟล์ชั่วคราวเช่นผ่านโครงสร้างเช่นการexec 3<>myfile
ไปยังtee
ฯลฯ
โซลูชัน "ของจริง" ที่คุณกำลังมองหาจำเป็นต้องใช้กับระบบไฟล์ดังนั้นจึงไม่มีให้ใช้งานในพื้นที่ผู้ใช้และจะขึ้นอยู่กับแพลตฟอร์ม: คุณกำลังขอให้แก้ไขตัวชี้ระบบไฟล์ที่ใช้โดยmyfile
เป็นค่าปัจจุบันของตัวชี้ระบบไฟล์ สำหรับmyheader
และแทนที่ในระบบแฟ้มEOF
ของมีการเชื่อมโยงเครือเดียวกับที่อยู่ในปัจจุบันระบบแฟ้มชี้myheader
myfile
นี่ไม่ใช่เรื่องเล็กน้อยและเห็นได้ชัดว่าไม่สามารถทำได้โดยผู้ใช้ที่ไม่ใช่ superuser และอาจไม่ใช่โดย superuser เช่นกัน ... เล่นกับ inodes ฯลฯ
คุณสามารถปลอมได้มากหรือน้อยโดยใช้อุปกรณ์ลูป ดูเช่นเธรด SOนี้
mktemp
? คุณสามารถล้างไฟล์ temp ได้ตลอดเวลาหลังจากนั้น ...