คำสั่ง Unix เพื่อนำหน้าข้อความไปยังไฟล์


126

มีคำสั่ง Unix เพื่อนำหน้าข้อมูลสตริงบางส่วนไปยังไฟล์ข้อความหรือไม่?

สิ่งที่ต้องการ:

prepend "to be prepended" text.txt

คุณกำลังมองหาคำสั่งเดียวหรือเป็นคำสั่งสคริปต์ / นามแฝงขนาดเล็กตกลง?
Levon

2
หากคุณรวมคำตอบทั้งหมดเข้าด้วยกันนี่อาจเป็นวิธีที่กะทัดรัดที่สุด:<<(echo "to be prepended") < text.txt | sponge text.txt
Sridhar Sarnobat

คำถามนี้ทำให้เกิดความคิดสร้างสรรค์ในหลาย ๆ คน!
Richard Jessop

คำตอบ:


111
sed -i.old '1s;^;to be prepended;' inFile
  • -iเขียนการเปลี่ยนแปลงและทำการสำรองข้อมูลหากมีการให้ส่วนขยายใด ๆ (ในกรณีนี้.old)
  • 1s;^;to be prepended;แทนที่จุดเริ่มต้นของบรรทัดแรกด้วยสตริงการแทนที่ที่กำหนดโดยใช้;เป็นตัวคั่นคำสั่ง

19
หากคุณสามารถเพิ่มคำอธิบายด้วยคำสั่งได้มันจะเป็นประโยชน์สำหรับคนอื่น ๆ ที่ไม่ค่อยคุ้นเคยกับการทำงานของ sed OP อาจต้องการแทรกที่\nหลังคำนำหน้า; ขึ้นอยู่กับความต้องการของพวกเขา โซลูชันที่เรียบร้อย
Levon

ใช่คำอธิบายจะดีมาก สามารถinFileรวมไดเรกทอรีในเส้นทางของมัน?
zakdances

3
@ Levon ฉันต้องการแทรก\nไฟล์. ควรอ่านความคิดเห็นก่อน ประณาม.
chishaku

ในกรณีของฉันฉันต้องการให้มีเซมิโคลอนในตอนท้ายของบรรทัดที่ต่อท้ายดังนั้นฉันจึงไปด้วย'1s;^;my-prepended-line\;\n;'
SamGamgee

5
โปรดทราบว่า '\ n' ใช้ได้กับ GNU sed เท่านั้นไม่ใช่ BSD (เช่น OSX, FreeBSD เป็นต้น) เพื่อให้พกพาสะดวกยิ่งขึ้นโปรดดู: stackoverflow.com/questions/1421478/…
jwd

164
printf '%s\n%s\n' "to be prepended" "$(cat text.txt)" >text.txt

2
ในบรรทัดเดียวและในไฟล์ข้อความต้นฉบับ! นี่คือคำตอบง่ายๆที่ถูกต้อง ฉันขอแนะนำให้ขึ้นบรรทัดใหม่ \ n เสียงสะท้อน -e "ที่จะนำหน้า \ n $ (cat text.txt)"> text.txt
VincentPerrin.com

32
โซลูชันนี้มีปัญหา ... จะแปลงรายการ "\ n" เป็นบรรทัดใหม่จริง ... มีปัญหาหากคุณนำไฟล์โค้ดที่มีสตริง "\ n" มาไว้ล่วงหน้า
Paul Go

1
ฉันมีสิ่งนี้ในไฟล์ของฉันgit config --get-regex $arg | sed -r 's/\t{3}/\\n/g';และสิ่งนี้ยุ่งเหยิงเมื่อมันแปลง\tและ\n.
hIpPy

8
สิ่งนี้ (1) โหลดไฟล์ทั้งหมดลงในหน่วยความจำและ (2) รวมไฟล์ทั้งหมดไว้ในอาร์กิวเมนต์บรรทัดคำสั่งเดียว เหมาะสำหรับของง่ายๆ แต่ระวังข้อ จำกัด
jwd

2
ล่าสุดที่ฉันลองฉันเชื่อว่าสิ่งนี้จะทำลายไฟล์ขนาดใหญ่จริงๆโดยที่ cat ไม่สามารถอ่านเนื้อหาทั้งหมดของ text.txt ก่อนที่จะเขียนทับ หากตัวเลือก -e บน echo มีปัญหาคุณสามารถขึ้นบรรทัดใหม่ใน bash หรือ doecho "to be prepended"$'\n'"$(cat text.txt)"
jbo5112

44

การทดแทนกระบวนการ

ฉันแปลกใจที่ไม่มีใครพูดถึงเรื่องนี้

cat <(echo "before") text.txt > newfile.txt

ซึ่งเป็นเนื้อหาที่เป็นธรรมชาติมากกว่าคำตอบที่ยอมรับ (การพิมพ์บางสิ่งบางอย่างและวางลงในคำสั่งการแทนที่เป็นคำศัพท์ที่ตอบโต้ได้ง่าย)

... และหักหลังสิ่งที่ไรอันกล่าวไว้ข้างต้นโดยที่spongeคุณไม่จำเป็นต้องใช้ไฟล์ชั่วคราว:

sudo apt-get install moreutils
<<(echo "to be prepended") < text.txt | sponge text.txt

แก้ไข: ดูเหมือนว่าจะใช้ไม่ได้ใน Bourne Shell /bin/sh


สตริงที่นี่ (zsh เท่านั้น)

ใช้สตริงที่นี่ - <<<คุณสามารถทำได้:

<<< "to be prepended" < text.txt | sponge text.txt

3
คำตอบนี้คือผู้ชนะสำหรับฉัน ง่ายและทำได้ด้วยบิวท์อินเท่านั้น การทำงานที่ดี!
PiersyP

1
@PiersyP ฉันเห็นด้วย! สิ่งนี้ช่วยได้มากขอบคุณ Sridhar-Sarnobat

ปัญหาคือมี 1 ไฟล์ไม่ใช่ 2 ไฟล์ ดังนั้นคำตอบบรรทัดแรกจึงใช้ไม่ได้จริงๆ บรรทัดสุดท้ายอาจใช้ได้ (แต่ต้องใช้ฟองน้ำ)
VasiliNovikov

1
ฟองน้ำทำอะไร?
Hemanta

1
ของคุณ<<(echo "to be prepended") < text.txtและสิ่ง<<< "to be prepended" < text.txtก่อสร้างไม่ได้ผลในการทุบตี พวกเขาต้องการ zsh
Anders Kaseorg

32

นี่เป็นความเป็นไปได้อย่างหนึ่ง:

(echo "to be prepended"; cat text.txt) > newfile.txt

คุณอาจจะไม่สามารถเข้าถึงไฟล์ระดับกลางได้อย่างง่ายดาย

ทางเลือกอื่น (อาจยุ่งยากด้วยการหลบหนี):

sed -i '0,/^/s//to be prepended/' text.txt

กลยุทธ์ที่ 1) เป็นคำตอบที่เข้าใจง่ายที่สุดที่ฉันรู้สึก และการเปลี่ยนแปลงเล็กน้อย: cat <(echo "to be prepended") text.txt > newfile.txt . ลองคิดดูฉันไม่แน่ใจว่าของฉันเกี่ยวข้องกันดังนั้นฉันจึงโพสต์คำตอบแยกต่างหาก
Sridhar Sarnobat

1
วิธีแรกจะไม่รักษาสิทธิ์ของไฟล์
Xlsx

วิธีเดียวที่จะรักษาสิทธิ์คือการใช้ฟองน้ำ
Sridhar Sarnobat

20

สิ่งนี้จะทำงานเพื่อสร้างผลลัพธ์ - หมายถึงอินพุตมาตรฐานซึ่งให้มาทางท่อจากเสียงสะท้อน

echo -e "to be prepended \n another line" | cat - text.txt

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

echo "to be prepended" | cat - text.txt > text.txt.tmp
mv text.txt.tmp text.txt

2
สิ่งนี้ไม่ได้นำหน้าข้อความไปยังไฟล์text.txtแม้ว่าจะเป็นไปตามที่ร้องขอ แต่จะแสดงบน stdout ผลลัพธ์อาจถูกแบ่งเป็นไฟล์อื่นหากใช้งานได้กับ OP
Levon

1
นี่จะเป็นการพิมพ์ "to be prepended" ตามด้วยเนื้อหา "text.txt" แต่ฉันต้องการให้ข้อความอยู่ข้างหน้าไฟล์
One Two Three

1
แล้วถ้ามีข้อความหลายบรรทัดล่ะ? ฉันจะใส่อักขระขึ้นบรรทัดใหม่ได้อย่างไร
One Two Three

สิ่งนี้จะดี แต่ bash ไม่ชอบ: $ echo RewriteBase / | cat - web / .htaccess> web / .htaccess cat: web / .htaccess: input file is output file
geek-merlin

14

ชอบคำตอบของอดัม

เราสามารถทำให้มันง่ายต่อการใช้ฟองน้ำ ตอนนี้เราไม่จำเป็นต้องสร้างไฟล์ชั่วคราวและเปลี่ยนชื่อโดย

echo -e "to be prepended \n another line" | cat - text.txt | sponge text.txt

นี่เป็นทางออกเดียวที่ใช้ได้ผลสำหรับฉัน มันตลกพอที่ชื่อเซิร์ฟเวอร์ของฉันคือ Spongebob
Ö

11

หากสามารถเปลี่ยนไฟล์อินพุตได้:

บันทึก:

  • การทำเช่นนั้นอาจมีผลข้างเคียงที่ไม่คาดคิดโดยเฉพาะอย่างยิ่งอาจแทนที่ symlink ด้วยไฟล์ปกติลงเอยด้วยสิทธิ์ที่แตกต่างกันในไฟล์และเปลี่ยนวันที่สร้าง (วันเกิด) ของไฟล์

  • sed -iเช่นเดียวกับในคำตอบของเจ้าชายจอห์นเวสลีย์อย่างน้อยก็พยายามกู้คืนสิทธิ์เดิม แต่ก็มีข้อ จำกัด อื่น ๆ เช่นกัน

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

{ printf 'to be prepended'; cat text.txt; } > tmp.txt && mv tmp.txt text.txt

การใช้กลุ่มคำสั่ง ( { ...; ...; }) เล็กน้อยมีประสิทธิภาพมากขึ้นกว่าการใช้subshell ( (...; ...)) ในขณะที่การแก้ปัญหาของ 0xC0000022L

ข้อดีคือ:

  • มันง่ายที่จะควบคุมว่าข้อความใหม่ควรจะใช้ได้โดยตรงกับบรรทัดแรกหรือว่ามันควรจะแทรกเป็นบรรทัดใหม่ (s) (เพียงผนวก\nกับprintfการโต้เถียง)

  • ไม่เหมือนกับsedวิธีการแก้ปัญหาจะทำงานได้หากไฟล์อินพุตว่างเปล่า ( 0ไบต์)


sedวิธีการแก้ปัญหาได้ง่ายถ้าเจตนาคือการย่อหน้าหนึ่งหรือทั้งเส้นกับเนื้อหาที่มีอยู่ (สมมติว่าแฟ้มใส่เป็นไม่ว่างเปล่า):

sed's iแทรกฟังก์ชั่นสายทั้งหมด:

ด้วยGNU sed :

# Prepends 'to be prepended' *followed by a newline*, i.e. inserts a new line.
# To prepend multiple lines, use '\n' as part of the text.
# -i.old creates a backup of the input file with extension '.old'
sed -i.old '1 i\to be prepended' inFile

ตัวแปรแบบพกพาที่ใช้งานได้กับ macOS / BSD sed:

# Prepends 'to be prepended' *followed by a newline*
# To prepend multiple lines, escape the ends of intermediate
# lines with '\'
sed -i.old -e '1 i\
to be prepended' inFile

โปรดทราบว่าต้องขึ้นบรรทัดใหม่ตามตัวอักษรหลังจาก\นั้น


หากต้องแก้ไขไฟล์อินพุตให้อยู่ในตำแหน่ง (รักษาไอโหนดไว้ด้วยแอตทริบิวต์ทั้งหมด):

การใช้edยูทิลิตี้ POSIX ที่น่าเคารพ:

บันทึก:

  • edอ่านไฟล์อินพุตโดยรวมในหน่วยความจำอย่างสม่ำเสมอก่อน

หากต้องการนำหน้าไปยังบรรทัดแรกโดยตรง (เช่นเดียวกับsedจะใช้ไม่ได้หากไฟล์อินพุตว่างเปล่า ( 0ไบต์)):

ed -s text.txt <<EOF
1 s/^/to be prepended/
w
EOF
  • -sedข้อความสถานะที่ถูกระงับ
  • สังเกตวิธีจัดเตรียมคำสั่งให้edเป็นเอกสารที่นี่หลายบรรทัด( <<EOF\n...\nEOF) กล่าวคือผ่านทางstdin ; โดยการขยายสตริงเริ่มต้นจะดำเนินการในเอกสารดังกล่าว (ตัวแปรเชลล์ถูกสอดแทรก) อ้างตัวคั่นเปิดเพื่อระงับสิ่งนั้น (เช่น<<'EOF')
  • 1 ทำให้บรรทัดที่ 1 เป็นบรรทัดปัจจุบัน
  • ฟังก์ชันsดำเนินการแทนที่สตริงตาม regex บนบรรทัดปัจจุบันเช่นเดียวกับในsed; คุณอาจใส่บรรทัดใหม่ตามตัวอักษรในข้อความการแทนที่ได้ แต่ต้องเป็น\-escaped
  • wเขียนผลลัพธ์กลับไปที่ไฟล์อินพุต (สำหรับการทดสอบแทนที่wด้วย,pเพื่อพิมพ์ผลลัพธ์เท่านั้นโดยไม่ต้องแก้ไขไฟล์อินพุต)

การย่อหน้าหนึ่งหรือทั้งเส้น :

เช่นเดียวกับsedที่iฟังก์ชั่นอย่างสม่ำเสมอเพิ่มขึ้นบรรทัดใหม่ต่อท้ายข้อความที่จะแทรก

ed -s text.txt <<EOF
0 i
line 1
line 2
.
w
EOF
  • 0 iทำให้0(จุดเริ่มต้นของไฟล์) บรรทัดปัจจุบันและเริ่มโหมดแทรก ( i); โปรดทราบว่าหมายเลขบรรทัดจะขึ้นอยู่กับ1-
  • บรรทัดต่อไปนี้เป็นข้อความที่จะแทรกก่อนบรรทัดปัจจุบันซึ่งสิ้นสุดด้วย.บรรทัดของมันเอง

7

อาจไม่มีอะไรในตัว แต่คุณสามารถเขียนของคุณเองได้อย่างง่ายดายเช่นนี้:

#!/bin/bash
echo -n "$1" > /tmp/tmpfile.$$
cat "$2" >> /tmp/tmpfile.$$
mv /tmp/tmpfile.$$ "$2"

อะไรทำนองนั้นอย่างน้อย ...


6
+1 สำหรับการใช้ $$ (PID ปัจจุบัน) เป็นส่วนหนึ่งของชื่อไฟล์ temp หากสร้างสคริปต์สำหรับการใช้งานทั่วไปสิ่งนี้จะป้องกันไม่ให้ผู้ใช้รายอื่นเขียนทับไฟล์ชั่วคราวโดยเรียกใช้สคริปต์เดียวกันในเวลาเดียวกัน
E Brown

3
การใช้$$ไม่เพียงพอสำหรับรหัสการผลิตแม้ว่า (google symlink attack); แต่มันดีกว่าชื่อไฟล์แบบคงที่
tripleee

1
เพิ่งใช้mktemp
mewa

7

ในบางสถานการณ์ข้อความที่อยู่ข้างหน้าอาจใช้ได้เฉพาะจาก stdin จากนั้นชุดค่าผสมนี้จะทำงาน

echo "to be prepended" | cat - text.txt | tee text.txt

หากคุณต้องการที่จะละเว้นการส่งออกแล้วผนวกtee> /dev/null


ฉันชอบคำตอบนี้มาก เป็นวิธีที่สะอาดและชัดเจนมาก การใช้ทีเป็นวิธีแก้ปัญหาอย่างเป็นธรรมชาติในการพยายามแคทเอาต์พุตไปยังไฟล์อินพุต ยังไม่มีการแฮ็กด้วยการเปลี่ยนชื่อ ดีกว่าโซลูชันที่ได้รับการโหวตสูงสุดในขณะนี้เนื่องจากไม่ได้สร้างไฟล์ใหม่ โดยพื้นฐานแล้วนี่คือสิ่งที่ใกล้เคียงที่สุดที่คุณจะได้รับ >> สำหรับการเติมเงินล่วงหน้าแทนที่จะต่อท้าย
DC2

นี่เป็นคำตอบที่ดีที่สุดและสะอาดที่สุด
nerdoc

6
มีการรับประกันหรือteeไม่ว่าจะไม่ยุ่งเหยิงtext.txtก่อนที่catจะอ่าน? ฉันคิดว่าไม่ - ซึ่งจะทำให้การแก้ปัญหานี้เป็นอันตรายต่อสุขภาพของไฟล์
Jonathan Leffler

3
@JonathanLeffler คงไม่มี ฉันลองใช้รหัสนี้เพื่อทดสอบ: lenA=1000000; yes a | head -c $lenA > a.txt; lenB=10000; b=$(yes b | head -c $lenB); echo "$b" | cat - a.txt | tee a.txt > /dev/null. ถ้าlenAเป็น 1000000 (ไฟล์ 1Mb) และlenBเป็น 10,000 (ข้อความ 10Kb นำหน้า) ไฟล์ "a.txt" จะถูกเขียนทับด้วยตัวอักษร "b" 20Kb นี่พังโดยสิ้นเชิง ตอนนี้ถ้าคุณใช้ข้อความ 1Mb a.txt และ 1Mb เพื่อนำหน้าteeเข้าไปในลูปที่สร้างไฟล์ 7Gb + ฉันต้องหยุดคำสั่ง ดังนั้นจึงเห็นได้ชัดว่าผลลัพธ์นั้นไม่สามารถคาดเดาได้สำหรับขนาดใหญ่ ฉันไม่มีข้อมูลว่าควรใช้กับขนาดเล็กหรือไม่
VasiliNovikov

3
ที่จะนำมันในแง่ความคิด: คำสั่งนี้จะส่งผลให้เกิดการสูญเสียข้อมูลถ้าแฟ้มใส่ที่เกิดขึ้นจะมีขนาดใหญ่กว่าขนาดท่อบัฟเฟอร์ของระบบซึ่งโดยปกติ 64 กิโลไบต์ในปัจจุบัน
mklement0

7

สารละลาย:

printf '%s\n%s' 'text to prepend' "$(cat file.txt)" > file.txt

โปรดทราบว่าสิ่งนี้ปลอดภัยสำหรับอินพุตทุกประเภทเนื่องจากไม่มีการขยาย ตัวอย่างเช่นหากคุณต้องการนำ!@#$%^&*()ugly text\n\t\nหน้ามันจะทำงาน:

printf '%s\n%s' '!@#$%^&*()ugly text\n\t\n' "$(cat file.txt)" > file.txt

"$(cat file.txt)"ส่วนสุดท้ายที่เหลืออยู่ในการพิจารณาคือการกำจัดช่องว่างในตอนท้ายของแฟ้มในระหว่างแทนคำสั่ง การแก้ไขปัญหาทั้งหมดนี้ค่อนข้างซับซ้อน หากคุณต้องการคงบรรทัดใหม่ไว้ที่ส่วนท้ายของ file.txt โปรดดูสิ่งนี้: https://stackoverflow.com/a/22607352/1091436


รักมัน. พกพาสะดวกและไม่จำเป็นต้องสร้างไฟล์ใหม่ ขอบคุณ!
pyb

1
ปัญหาเดียวที่เกิดขึ้นหากเนื้อหาfile.txtใหญ่กว่าที่สามารถใส่ลงในรายการอาร์กิวเมนต์printfได้
Jonathan Leffler

6

อีกวิธีหนึ่งโดยใช้sed:

sed -i.old '1 {i to be prepended
}' inFile

หากบรรทัดที่จะนำหน้าเป็นแบบหลายเส้น:

sed -i.old '1 {i\ 
to be prepended\
multiline
}' inFile

ทำไมไม่sed -i '1ito be prepended' inFile- หรืออนุญาตเฉพาะ GNU sed เท่านั้น?
ผู้ใช้ไม่ทราบ

@userunknown: ในมาตรฐานsedที่iคำสั่งตามด้วยเครื่องหมายและขึ้นบรรทัดใหม่และแต่ละบรรทัดของการป้อนข้อมูลยกเว้นปลายสุดท้ายด้วยเครื่องหมายเกินไป GNU sedอนุญาตให้ shorthands ตามบรรทัดที่คุณถาม แต่เป็น GNU เท่านั้นsedที่ทำเช่นนั้น '
Jonathan Leffler

3

ตามที่ทดสอบใน Bash (ใน Ubuntu) หากเริ่มต้นด้วยไฟล์ทดสอบผ่าน;

echo "Original Line" > test_file.txt

คุณสามารถดำเนินการ;

echo "$(echo "New Line"; cat test_file.txt)" > test_file.txt

หรือถ้าเวอร์ชันของ bash เก่าเกินไปสำหรับ $ () คุณสามารถใช้ backticks;

echo "`echo "New Line"; cat test_file.txt`" > test_file.txt

และรับเนื้อหาต่อไปนี้ของ "test_file.txt";

New Line
Original Line

ไม่มีไฟล์ตัวกลางเพียง bash / echo


2
คำตอบที่ดีที่สุดฉันเขียนซับเดียวนี้เพื่อหมุนไฟล์ของฉันโดยใช้คำตอบนี้: สำหรับฉันใน $ (<file2.txt); ทำ echo "$ (echo $ i; cat file.txt)"> file.txt; ทำ;
Aurangzeb

2

อีกวิธีหนึ่งที่ค่อนข้างตรงไปตรงมาคือ:

    $ echo -e "string\n" $(cat file)

สิ่งนี้ใช้ได้ผลสำหรับฉัน ... เพียงแค่ปรับเปลี่ยนเล็กน้อยเพื่อรักษา multilines จากไฟล์อินพุตคุณต้องห่อด้วยเครื่องหมายคำพูดดังนั้นสิ่งนี้: echo -e "string \ n" "$ (cat ~ / file.txt) "> ~ / file.txt;
Craig Wayne

2
ไม่ได้มีcatการขยายตัวพารามิเตอร์ในราคาคู่เป็นเหตุผลที่ดีงามสำหรับdownvote echoรหัสนี้จะแบ่งเนื้อหาของไฟล์เป็นคำพูดและการส่งผ่านแต่ละคำพูดเหล่านั้นเป็นอาร์กิวเมนต์ที่แยกต่างหากเพื่อ คุณสูญเสียขอบเขตอาร์กิวเมนต์เดิมคุณสูญเสียการขึ้นบรรทัดใหม่ / แท็บ / ฯลฯ และสตริงเช่น\tและ\nในข้อความต้นฉบับจะถูกแทนที่ด้วยแท็บและบรรทัดใหม่แทนที่จะเก็บไว้เหมือนเดิม
Charles Duffy


0
# create a file with content..
echo foo > /tmp/foo
# prepend a line containing "jim" to the file
sed -i "1s/^/jim\n/" /tmp/foo
# verify the content of the file has the new line prepened to it
cat /tmp/foo

0

ฉันขอแนะนำให้กำหนดฟังก์ชันจากนั้นนำเข้าและใช้ตามความจำเป็น

prepend_to_file() { 
    file=$1
    text=$2

    if ! [[ -f $file ]] then
        touch $file
    fi

    echo "$text" | cat - $file > $file.new

    mv -f $file.new $file
}

จากนั้นใช้มันดังนี้:

prepend_to_file test.txt "This is first"
prepend_to_file test.txt "This is second"

เนื้อหาไฟล์ของคุณจะเป็น:

This is second
This is first

ฉันกำลังจะใช้แนวทางนี้ในการติดตั้งตัวอัปเดตบันทึกการเปลี่ยนแปลง

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