จะผนวก Line เข้ากับ Line ก่อนหน้าได้อย่างไร?


9

ฉันมีไฟล์บันทึกซึ่งต้องวิเคราะห์และวิเคราะห์ ไฟล์มีบางสิ่งที่คล้ายกันดังนี้:

ไฟล์:

20141101 server contain dump
20141101 server contain nothing
    {uekdmsam ikdas 

jwdjamc ksadkek} ssfjddkc * kdlsdl
sddsfd jfkdfk 
20141101 server contain dump

จากสถานการณ์ข้างต้นฉันต้องตรวจสอบว่าบรรทัดเริ่มต้นไม่มีวันที่หรือหมายเลขที่ฉันต้องต่อท้ายบรรทัดก่อนหน้า

ไฟล์ที่ส่งออก:

20141101 server contain dump
20141101 server contain nothing {uekdmsam ikdas jwdjamc ksadkek} ssfjddkc * kdlsdl sddsfd jfkdfk 
20141101 server contain dump

คำตอบ:


11

รุ่นในperlโดยใช้ lookaheads เชิงลบ:

$ perl -0pe 's/\n(?!([0-9]{8}|$))//g' test.txt
20141101 server contain dump
20141101 server contain nothing    {uekdmsam ikdas jwdjamc ksadkek} ssfjddkc * kdlsdlsddsfd jfkdfk
20141101 server contain dump

-0อนุญาตให้ regex จับคู่กับไฟล์ทั้งหมดและ\n(?!([0-9]{8}|$))เป็น lookahead เชิงลบซึ่งหมายถึงการขึ้นบรรทัดใหม่ที่ไม่ตามด้วยตัวเลข 8 หลักหรือท้ายบรรทัด (ซึ่งด้วย-0จะเป็นจุดสิ้นสุดของไฟล์)


@terdon อัปเดตเพื่อบันทึกบรรทัดใหม่ล่าสุด
muru

ทำได้ดีนี่! ฉันขอโหวตคุณ แต่ฉันเกรงว่าฉันมีอยู่แล้ว :)
terdon

ไม่-0ถ้าเป็นระเบียนที่คั่นด้วย NUL ใช้-0777เพื่อปัดไฟล์ทั้งหมดในหน่วยความจำ (ซึ่งคุณไม่จำเป็นต้องใช้ที่นี่)
Stéphane Chazelas

@ StéphaneChazelasดังนั้นวิธีที่ดีที่สุดในการทำให้ Perl ตรงกับบรรทัดใหม่คืออะไรนอกจากการอ่านไฟล์ทั้งหมดแล้ว
muru

ดูคำตอบอื่น ๆ ที่ประมวลผลไฟล์ตามบรรทัด
Stéphane Chazelas

5

อาจจะง่ายนิดเดียวด้วย sed

sed -e ':1 ; N ; $!b1' -e 's/\n\+\( *[^0-9]\)/\1/g'
  • ส่วนแรก:1;N;$!b1รวบรวมทุกบรรทัดในไฟล์หารด้วย\nยาว 1 บรรทัด

  • ส่วนที่สองตัดสัญลักษณ์ขึ้นบรรทัดใหม่หากมันตามด้วยสัญลักษณ์ที่ไม่ใช่ตัวเลขพร้อมช่องว่างที่เป็นไปได้ระหว่างนั้น

ในการหลีกเลี่ยงการ จำกัด หน่วยความจำ (โดยเฉพาะไฟล์ขนาดใหญ่) คุณสามารถใช้:

sed -e '1{h;d}' -e '1!{/^[0-9]/!{H;d};/^[0-9]/x;$G}' -e 's/\n\+\( *[^0-9]\)/\1/g'

หรือลืมsedสคริปต์ที่ยากและจำได้ว่าปีนั้นเริ่มต้นจาก2

tr '\n2' ' \n' | sed -e '1!s/^/2/' -e 1{/^$/d} -e $a

ดี +1 คุณช่วยเพิ่มคำอธิบายวิธีการทำงานของมันได้ไหม
terdon

1
Aw ดี ฉันมักจะทำtr '\n' $'\a' | sed $'s/\a\a*\( *[^0-9]\)/\1/g' | tr $'\a' '\n'เอง
mirabilos

ขออภัยต้อง downvote แต่สำหรับการใช้สิ่งที่ไม่ใช่POSIX พื้นฐานประจำการแสดงออก S ในsed (1)ซึ่งเป็น GNUism
mirabilos

1
@Costas นั่นคือหน้าแรกของ GNU grep POSIX BRE ข้อมูลจำเพาะอยู่ที่นั่น BRE เทียบเท่าของ ERE เป็น+ ไม่สามารถพกพาได้เช่นกัน จะเป็น POSIX \{1,\}[\n]\n\{1,\}
Stéphane Chazelas

1
นอกจากนี้คุณไม่สามารถมีคำสั่งอื่นหลังจากติดป้ายกำกับได้ : 1;xคือการกำหนด1;xป้ายกำกับใน POSIX seds ดังนั้นคุณต้องการ: sed -e :1 -e 'N;$!b1' -e 's/\n\{1,\}\( *[^0-9]\)/\1/g'. นอกจากนี้โปรดทราบว่าsedการใช้งานหลายอย่างมีข้อ จำกัด เล็กน้อยเกี่ยวกับขนาดของพื้นที่รูปแบบ (POSIX รับประกันเฉพาะ 10 x LINE_MAX IIRC)
Stéphane Chazelas

5

วิธีหนึ่งจะเป็น:

 $ perl -lne 's/^/\n/ if $.>1 && /^\d+/; printf "%s",$_' file
 20141101 server contain dump
 20141101 server contain nothing    {uekdmsam ikdas jwdjamc ksadkek} ssfjddkc * kdlsdlsddsfd jfkdfk 
 20141101 server contain dump

อย่างไรก็ตาม. thats ยังลบ newline สุดท้าย หากต้องการเพิ่มอีกครั้งให้ใช้:

$ { perl -lne 's/^/\n/ if $.>1 && /^\d+/; printf "%s",$_' file; echo; } > new

คำอธิบาย

-lจะลบต่อท้ายบรรทัดใหม่ (และยังเพิ่มหนึ่งไปยังแต่ละprintโทรซึ่งเป็นเหตุผลที่ผมใช้printfแทน. แล้วถ้าเส้นเริ่มต้นในปัจจุบันมีตัวเลข ( /^\d+/) และจำนวนบรรทัดปัจจุบันมีมากกว่าหนึ่ง ( $.>1นี้เป็นสิ่งจำเป็นเพื่อหลีกเลี่ยงการเพิ่มเป็นพิเศษ บรรทัดว่างที่จุดเริ่มต้น) เพิ่ม a \nไปยังจุดเริ่มต้นของบรรทัดprintfพิมพ์แต่ละบรรทัด


หรือคุณสามารถเปลี่ยนทุก\nตัวอักษร\0แล้วเปลี่ยนผู้\0ที่มีสิทธิก่อนสตริงของตัวเลขไป\nอีกครั้ง:

$ tr '\n' '\0' < file | perl -pe 's/\0\d+ |$/\n$&/g' | tr -d '\0'
20141101 server contain dump
20141101 server contain nothing    {uekdmsam ikdas jwdjamc ksadkek} ssfjddkc * kdlsdlsddsfd jfkdfk 
20141101 server contain dump

ในการทำให้ตรงกับสตริงที่มีตัวเลข 8 ตัวให้ใช้แทน:

$ tr '\n' '\0' < file | perl -pe 's/\0\d{8} |$/\n$&/g' | tr -d '\0'

อาร์กิวเมนต์แรกจะprintfเป็นรูปแบบ ใช้printf "%s", $_
Stéphane Chazelas

@ StéphaneChazelasทำไม? ฉันหมายความว่าฉันรู้ว่ามันสะอาดและอาจจะเข้าใจได้ง่ายขึ้น แต่มีอันตรายที่จะป้องกันหรือไม่
terdon

ใช่มันผิดและอาจเป็นอันตรายหากอินพุตอาจมีอักขระ% ลองกับอินพุทด้วย%10000000000sเช่น
Stéphane Chazelas

ใน C นั้นเป็นที่รู้จักกันดีในเรื่องการปฏิบัติที่ไม่ดีและเป็นช่องโหว่ ด้วยperl, echo %.10000000000f | perl -ne printfนำเครื่องของฉันที่เข่า
Stéphane Chazelas

@ StéphaneChazelasว้าวใช่ ของฉันด้วย. ยุติธรรมเพียงพอแล้วตอบแก้ไขและขอบคุณ
terdon

3

ลองทำสิ่งนี้โดยใช้ :

#!/usr/bin/awk -f

{
    # if the current line begins with 8 digits followed by
    # 'nothing' OR the current line doesn't start with 8 digits
    if (/^[0-9]{8}.*nothing/ || !/^[0-9]{8}/) {
        # print current line without newline
        printf "%s", $0
        # feeding a 'state' variable
        weird=1
    }
    else {
        # if last line was treated in the 'if' statement
        if (weird==1) {
            printf "\n%s", $0
            weird=0
        }
        else {
            print # print the current line
        }
    }
}
END{
    print # add a newline when there's no more line to treat
}

วิธีใช้:

chmod +x script.awk
./script.awk file.txt

2

อีกวิธีที่ง่ายที่สุด (กว่าคำตอบอื่น ๆ ของฉัน) โดยใช้อัลกอริทึมและterdon :

awk 'NR>1 && /^[0-9]{8}/{printf "%s","\n"$0;next}{printf "%s",$0}END{print}' file

END{print ""}ITYM ทางเลือก:awk -v ORS= 'NR>1 && /^[0-9]{8}/{print "\n"};1;END{print "\n"}'
Stéphane Chazelas


0

เขียนโปรแกรมทุบตี:

while read LINE
do
    if [[ $LINE =~ ^[0-9]{8} ]]
    then
        echo -ne "\n${LINE} "
    else
        echo -n "${LINE} "
    fi
done < file.txt

ในรูปแบบบรรทัดเดียว:

while read L; do if [[ $L =~ ^[0-9]{8} ]]; then echo -ne "\n${L} "; else echo -n "${L} "; fi done < file.txt

โซลูชันที่มีเครื่องหมายแบ็กสแลชรักษา ( read -r) และเว้นวรรคนำหน้า ( IFS=หลังwhile):

while IFS= read -r LINE
do
    if [[ $LINE =~ ^[0-9]{8} ]]
    then
        echo
        echo -nE "\n${LINE} "
    else
        echo -nE "${LINE} "
    fi
done < file.txt

แบบฟอร์มหนึ่งบรรทัด:

while IFS= read -r L; do if [[ $L =~ ^[0-9]{8} ]]; then echo; echo -nE "${L} "; else echo -nE "${L} "; fi done < file.text

นี้จะแตกถ้าสายมีการพูด, nเครื่องหมายและ นอกจากนี้ยังตัดช่องว่าง แต่คุณสามารถใช้mkshการทำเช่นนี้:while IFS= read -r L; do [[ $L = [0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]* ]] && print; print -nr -- "$L"; done; print
mirabilos

แน่นอนว่าไม่ใช่สำหรับอัลกอริธึมทุกอย่าง แต่เป็นการแก้ปัญหาสำหรับข้อกำหนดที่มีให้โดยงาน แน่นอนทางออกสุดท้ายจะซับซ้อนมากขึ้นและสามารถอ่านได้น้อยได้อย่างรวดเร็วในขณะที่มันมักจะเกิดขึ้นในชีวิตจริง :)
โกง

ฉันเห็นด้วย แต่ฉันได้เรียนรู้วิธียากที่จะไม่คิดมากเกี่ยวกับ OP ☺โดยเฉพาะอย่างยิ่งหากพวกเขาแทนที่ข้อความจริงด้วยข้อความจำลอง
mirabilos

0
[shyam@localhost ~]$ perl -lne 's/^/\n/ if $.>1 && /^\d+/; printf "%s",$_' appendDateText.txt

ที่จะทำงาน

i/p:
##06/12/2016 20:30 Test Test Test
##TestTest
##06/12/2019 20:30 abbs  abcbcb abcbc
##06/11/2016 20:30 test test
##i123312331233123312331233123312331233123312331233Test
## 06/12/2016 20:30 abc

o/p:
##06/12/2016 20:30 Test Test TestTestTest
##06/12/2019 20:30 abbs  abcbcb abcbc
##06/11/2016 20:30 test ##testi123312331233123312331233123312331233123312331233Test
06/12/2016 20:30 abc vi appendDateText.txt 
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.