วิธีแทรกเนื้อหาของไฟล์ลงในไฟล์อื่นก่อนรูปแบบ (เครื่องหมาย)?


32

File1 เนื้อหา:

line1-file1      "1" 
line2-file1      "2"
line3-file1      "3" 
line4-file1      "4" 

File2 เนื้อหา:

line1-file2     "25"  
line2-file2     "24"  
Pointer-file2   "23"  
line4-file2     "22" 
line5-file2     "21"

หลังจากการดำเนินการของ perl / shell script File2เนื้อหาควรเป็น:

line1-file2     "25"  
line2-file2     "24" 
line1-file1      "1" 
line2-file1      "2"
line3-file1      "3" 
line4-file1      "4" 
Pointer-file2   "23" 
line4-file2     "22" 
line5-file2     "21"

เช่นวางเนื้อหาFile1ในFile2ก่อนบรรทัดที่มี "ตัวชี้"


1
ถามด้วยที่StackOverflow
เกล็นแจ็คแมน


ฉันโหวตให้กับคำถาม SO แล้วสำหรับการปิดด้วยเหตุผลนี้ไม่มีเหตุผลที่จะปิดคำถามนี้อีก แต่คำถามก็คือคุณภาพที่แย่กว่านั้นดังนั้นตรรกะจึงสั่งให้ปิดและไม่ใช่สิ่งนี้
peterh กล่าวว่าคืนสถานะโมนิก้า

คำตอบ:


33

sed มีฟังก์ชั่นสำหรับสิ่งนั้นและสามารถทำการแก้ไขแบบอินไลน์:

sed -i -e '/Pointer/r file1' file2

แต่สิ่งนี้จะทำให้บรรทัดตัวชี้ของคุณอยู่เหนือ file1 หากต้องการวางไว้ด้านล่างให้เลื่อนการแสดงผลบรรทัด:

sed -n -i -e '/Pointer/r file1' -e 1x -e '2,${x;p}' -e '${x;p}' file2 

8
คุณกรุณาอธิบายสิ่งที่-e 1x -e '2,${x;p}' -e '${x;p}'ทำ ฉันเข้าใจว่าคุณแลกเปลี่ยนสิ่งของในบัฟเฟอร์รูปแบบแล้วพิมพ์ แต่ฉันไม่รู้ว่าทำไมคุณเพิ่มตัวเลือก-nที่เงียบในตอนแรก
hdl

@ jfg956 เป็นไปได้หรือไม่ที่จะแทนที่และลบส่วน 'ตัวชี้' ออกจากไฟล์ต้นฉบับ นี่ฉันสามารถเข้าใจได้ด้วยการกวาดครั้งที่สอง แต่มันเป็นไปได้ไหมในการวิ่งครั้งเดียว?
Alexander Cska

17

โดยไม่ต้องใช้sedหรือawk...

ก่อนอื่นให้ค้นหาบรรทัดที่เป็นรูปแบบของคุณ:

line=$(grep -n 'Pointer' file2 | cut -d ":" -f 1)

จากนั้นใช้ 3 คำสั่งเพื่อแสดงผลลัพธ์ที่ต้องการ:

{ head -n $(($line-1)) file2; cat file1; tail -n +$line file2; } > new_file

นี้มีอุปสรรคในการเข้าถึง 3 ครั้งไฟล์file2แต่อาจจะชัดเจนกว่าsedของawkการแก้ปัญหา


10

awkทำให้เรื่องนี้ค่อนข้างง่าย
แทรกบรรทัดก่อนไฟล์:

awk '/Pointer/{while(getline line<"innerfile"){print line}} //' outerfile >tmp
mv tmp outerfile

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

awk '//; /Pointer/{while(getline<"innerfile"){print}}' outerfile >tmp
mv tmp outerfile

และเพียงเพราะไม่มีใครได้ใช้perlเลย

# insert file before line
perl -e 'while(<>){if($_=~/Pointer/){system("cat innerfile")};print}' outerfile

# after line
perl -e 'while(<>){print;if($_=~/Pointer/){system("cat innerfile")}}' outerfile

มันใช้งานได้ แต่มันถูกลบบรรทัดที่มีตัวชี้
user1228191

เช่นเดียวกันวิธีการวางเนื้อหาของไฟล์ 1 ในไฟล์ 2 หลังจากนั้น "Pointer" ที่มีบรรทัดโดยใช้ awk
1228191

@ user1228191 แก้ไขที่หนึ่งเพิ่มที่สอง
เควิน

ดูเหมือนว่ารุ่น 'perl' จะไม่ทำงาน system("cat innerfile")ส่งออกinnerfileไปยังคอนโซล ฉันพลาดอะไรไปรึเปล่า?
kaartic

คำสั่ง awk [gawk '/ <body> / {ในขณะที่ (getline line <"$ HOME / bin / SrunScenario.style") {print line}} //' index.html> new_index.html] เพียงแค่ลูปและพิมพ์ล้านบรรทัด . gawk V4.2.0 ฉันหายไปไหน
JESii

7

งานง่าย ๆ สำหรับed:

ed -s file1 <<IN
/Pointer/-r file2
,p
q
IN

-r file1Pointerอ่านในแฟ้มที่ระบุหลังจากที่ส่งสายซึ่งในกรณีนี้คือเส้นก่อนที่จะจับคู่บรรทัดแรก ดังนั้นสิ่งนี้จะแทรกเนื้อหาfile2เพียงครั้งเดียวแม้ว่าจะPointerเกิดขึ้นในหลายบรรทัด หากคุณต้องการแทรกก่อนแต่ละบรรทัดที่ตรงกันให้เพิ่มgแฟล็ก lobal:

ed -s file1 <<IN
g/Pointer/-r file2
,p
q
IN

แทนที่,pด้วยwหากคุณต้องการแก้ไขไฟล์แบบแทนที่


sedคำตอบที่ยอมรับจะใช้งานได้ในกรณีส่วนใหญ่ แต่ถ้าเครื่องหมายอยู่ในบรรทัดสุดท้ายคำสั่งจะไม่ทำงานตามที่คาดไว้: จะแทรกเนื้อหาของFile1เครื่องหมายหลัง
ฉันได้ลองกับ:

sed '/Pointer/{r file1
N}' file2

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

sed '/Pointer/{              # like the first one, but this time even if the
r file1                      # marker is on the last line in File2 it
N                            # will be on the second to last line in
}                            # the combined input so N will always work;
${                           # on the last line of input: if the line is
/^$/!{                       # not empty, it means the marker was on the last
s/\n$//                      # line in File2 so the final empty line in the
}                            # input was pulled i\n: remove the latter;
//d                          # if the line is empty, delete it
}' file2 <(printf %s\\n)

สิ่งนี้จะแทรกfile2เนื้อหาก่อนแต่ละบรรทัดที่ตรงกัน หากต้องการแทรกเฉพาะก่อนบรรทัดจับคู่แรกคุณสามารถใช้loop และดึงnบรรทัดต่อจนกว่าคุณจะจบไฟล์:

sed '/Pointer/{
r file2
N
:l
$!n
$!bl
}
${
/^$/!{
s/\n$//
}
//d
}' file1 <(printf %s\\n)

ด้วยsedโซลูชันเหล่านี้คุณจะไม่สามารถแก้ไขแบบแทน (แต่คุณสามารถเปลี่ยนเส้นทางไปยังไฟล์อื่น)


6

ใช้การวนซ้ำเพื่ออ่านบรรทัดใน file2 หากคุณพบบรรทัดที่ขึ้นต้นด้วยPointerให้พิมพ์ file1 ดังแสดงด้านล่าง:

#!/bin/bash
while IFS= read -r line
do
    if [[ "$line" =~ ^Pointer.*$ ]]
    then
        cat file1
    fi
    echo "$line"
done < file2

4

มีไม่กี่วิธีที่จะไปเกี่ยวกับเรื่องนี้เป็น W sed/ วิธีหนึ่งคือการอ่านล่าช้าตามที่แนะนำในคำตอบที่ยอมรับ มันสามารถเขียนได้เช่น:

sed -e '$!N;P;/\nPointer/r file1' -e D file2

... ด้วยการมองไปข้างหน้าเล็กน้อยอย่างชัดเจนแทนที่จะใช้ look-behind ที่อื่นถูกใช้กับบัฟเฟอร์การพัก นั่นจะมีปัญหาเดียวกันกับบรรทัดสุดท้ายที่ @don_crissti บันทึกอย่างหลีกเลี่ยงไม่ได้เพราะN จะเพิ่มรอบของบรรทัดและrคำสั่ง ead จะถูกใช้โดยหมายเลขบรรทัด

คุณสามารถไปรอบ ๆ มัน:

echo | sed -e '$d;N;P;/\nPointer/r file1' -e D file2 -

ไม่ใช่ว่าทุกคนsedจะตีความ-หมายถึงอินพุตมาตรฐาน แต่มีหลายคนที่ทำ ( POSIX บอกว่า sedควรสนับสนุน-ให้มีความหมายเป็นมาตรฐานหากผู้ดำเนินการต้องการ-หมายถึงมาตรฐานใน)

อีกวิธีคือจัดการเนื้อหาที่ต่อท้ายตามลำดับ มีคำสั่งอื่นที่กำหนดเวลาการส่งออกในแบบเดียวกับที่อีดrทำและsedจะนำไปใช้และได้rรับตามลำดับที่พวกเขาเขียนสคริปต์ มันมีส่วนเกี่ยวข้องมากกว่านิดหน่อย - มันเกี่ยวข้องกับการใช้หนึ่งsedเพื่อaเชื่อมการPointerแข่งขันกับผลลัพธ์ของอีกอันsedในสคริปต์

sed '   /Pointer/!d                  #only operate on first match
        s/[]^$&\./*[]/\\&/g;H        #escape all metachars, Hold
        s|.*|/&/!p;//!d|p;g          #print commands, exchange
        s|.|r file1&a\\&|;q' file2|  #more commands, quit
        sed -nf - file2              #same input file

ดังนั้นโดยทั่วไปคนแรกsedเขียนsedสคริปต์ตัวที่สองซึ่งตัวที่สองsedอ่านในอินพุตมาตรฐาน(อาจ ... )และนำไปใช้ในทางกลับกัน ครั้งแรกsedเท่านั้นที่ทำงานในการแข่งขันครั้งแรกสำหรับการPointerค้นพบและหลังจากนั้นquits อินพุต หน้าที่ของมันคือ ...

  1. s/[]^$&\./*[]/\\&/g;H
    • ตรวจสอบให้แน่ใจว่าตัวอักษรรูปแบบทั้งหมดมีเครื่องหมายแบ็กสแลชอย่างปลอดภัยเนื่องจากตัวที่สองsedจะต้องตีความทุก ๆ บิตที่อ่านอย่างแท้จริงเพื่อทำให้ถูกต้อง เมื่อเสร็จแล้วใส่สำเนาในHพื้นที่เก่า
  2. s|.*|/&/!p;//!d|p; x
    • บอกวินาทีsedเพื่อprint ทุก ๆ บรรทัดอินพุต!แต่/&/อันที่เราเพิ่งเก็บรักษารูปแบบ จากนั้นให้dลบล้างสิ่งเดียวกันทั้งหมด pเรียกใช้คำสั่งในครั้งที่สองsedจากนั้นxเปลี่ยนe hและบัฟเฟอร์แบบเก่าเพื่อทำงานกับสำเนาที่เราบันทึกไว้
  3. s|.|r file1&a\\&|p;q
    • ถ่านตัวเดียวที่เราทำงานด้วยที่นี่คือ\newline เพราะsedจะได้Hรับการเติมก่อนเมื่อเราเข้าแถวมาก่อน ดังนั้นเราจึงแทรกคำสั่งr file1และปฏิบัติตามด้วย\newline ของเราจากนั้นคำสั่งa\\สำหรับappend ตามด้วย\newline ส่วนที่เหลือทั้งหมดของHบรรทัดแรกของเราเป็นไปตาม\newline สุดท้าย

สคริปต์ที่เขียนครั้งแรกมีลักษณะดังนี้:

/Pointer-file2   "23"/!p;//!d
r file1
a\
Pointer-file2   "23"

โดยทั่วไปที่สองsedจะพิมพ์ทุกบรรทัด แต่บรรทัดแรกsedจะตั้งค่าเป็นappend สำหรับว่าสายโดยเฉพาะอย่างยิ่งสองเขียนล่าช้ามาตรฐานออกจะกำหนดเวลา - ครั้งแรกเป็นrอี๊ดของfile1และสองคือสำเนาของเส้นที่เราต้องการหลังจากที่มัน การsedแพทย์คนแรกไม่จำเป็นแม้แต่ในกรณีนี้(ดู? ไม่มีแบ็กสแลช)แต่มันเป็นเรื่องสำคัญที่จะต้องหลบหนีอย่างปลอดภัยในวิธีที่ฉันทำที่นี่เมื่อใดก็ตามที่รูปแบบการจับคู่ถูกเปลี่ยนเป็นอินพุต

อย่างไรก็ตาม ... มีสองสามวิธี


2

นี่ค่อนข้างง่ายกับ AWK:

File1 ลงใน File2 before pattern = "Pointer"

ขั้นแรกให้โหลดเนื้อหาของ File1 ลงในตัวแปร

f1="$(<File1)"

จากนั้นทำการแทรก

awk -vf1="$f1" '/Pointer/{print f1;print;next}1' file2

(หรือหากคุณต้องการแทรก File1 หลังจาก "Pointer")

awk -vf1="$f1" '/Pointer/{print;print f1;next}1' file2

2

วิธีที่ต้องการของฉัน: templating

sed 's/CHANGEME/$x/g' origfile | x="$(<file2insert)" envsubst '$x' > newfile

นี้จะแทนที่ทุกCHANGEMEเกิดขึ้นในorigfileที่มีเนื้อหาของfile2insert ลบgล่าสุดจาก sed เพื่อแทนที่เฉพาะการเกิดขึ้นครั้งแรกของ CHANGEMEเท่านั้น


คุณจะใช้$xคำสั่งแรกของคุณได้อย่างไรเมื่อมันถูกกำหนดในคำสั่งที่สองเท่านั้น?
Totor

"$ x" ในคำสั่งแรกเป็นเพียงตัวยึดตำแหน่งที่จะประเมินโดย envsubst ในคำสั่งที่สอง ด้วยเครื่องหมายคำพูดเดียวของ sed script $ x จะไม่ถูกประเมินโดยเชลล์ของคุณ
nrc

2

[แทรกเนื้อหาไฟล์ลงในไฟล์อื่นก่อนรูปแบบ]

sed -i '/PATTERN/r file1' -e //N file2

[หลังจากรูปแบบ]

sed -i '/PATTERN/r file1' file2

Nทำงานได้ดี แต่ไม่ใช่ถ้ารูปแบบตรงกับบรรทัดสุดท้ายของอินพุต
Sundeep

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