บันทึกการแก้ไขในสถานที่ด้วย NON GNU awk


9

ฉันเจอคำถาม (ในตัวเองดังนั้น) ที่ OP จะต้องแก้ไขและบันทึกการดำเนินการใน Input_file (s) ตัวเอง

ฉันรู้สำหรับ Input_file เดียวที่เราสามารถทำได้:

awk '{print "test here..new line for saving.."}' Input_file > temp && mv temp Input_file

ตอนนี้สมมติว่าเราต้องทำการเปลี่ยนแปลงในรูปแบบไฟล์ประเภทเดียวกัน (สมมติว่า. txt ที่นี่)

สิ่งที่ฉันได้ลอง / คิดเกี่ยวกับปัญหานี้:วิธีการของมันคือการผ่านสำหรับไฟล์. txt และการเรียกไฟล์เดี่ยวawkนั้นเป็นกระบวนการที่ไม่เจ็บปวดและไม่แนะนำเนื่องจากมันจะทำให้วงจรซีพียูที่ไม่จำเป็นขาดหายไป ช้า.

ดังนั้นสิ่งที่สามารถทำได้ที่นี่เพื่อทำการแก้ไขแบบ inplace สำหรับหลายไฟล์ด้วย NON GNU awkซึ่งไม่รองรับตัวเลือก inplace ฉันได้ผ่านหัวข้อนี้แล้วบันทึกการแก้ไขด้วย awkแต่ไม่มีอะไรมากสำหรับ NON GNU awk Vice และการเปลี่ยนไฟล์หลาย ๆ ไฟล์ภายในawkตัวเองเนื่องจากไม่ใช่ GNU awk จะไม่มีinplaceตัวเลือก

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



แก้ไข:ตามความเห็นของเอ็ดเซอร์ที่เพิ่มตัวอย่างของตัวอย่างที่นี่แม้ว่าวัตถุประสงค์ของรหัสของหัวข้อนี้สามารถใช้โดยวัตถุประสงค์ทั่วไปแก้ไขในสถานที่เช่นกัน

ตัวอย่าง Input_file:

cat test1.txt
onetwo three
tets testtest

cat test2.txt
onetwo three
tets testtest

cat test3.txt
onetwo three
tets testtest

ตัวอย่างผลลัพธ์ที่คาดหวัง:

cat test1.txt
1
2

cat test2.txt
1
2

cat test3.txt
1
2

1
ปัญหาที่น่าสนใจและตรงประเด็น awk ++ ++
anubhava

1
@ RavinderSingh13 หากคุณมีไฟล์มากมายที่จะใช้กับสิ่งนี้ทำไมไม่ใช้การเรียกครั้งเดียวไปที่awk(อาจเป็น subshell) หรือ{...}กลุ่มที่ล้อมรอบแล้วเขียนผลลัพธ์ลงในไฟล์เอาต์พุตที่ต้องการ (ทั้งไฟล์อินพุตแต่ละไฟล์ หรือไฟล์รวมสำหรับไฟล์อินพุตทั้งหมด) จากนั้นคุณเพียงแค่เปลี่ยนเส้นทางเอาต์พุตของกลุ่มย่อยหรือกลุ่มที่อยู่ในวงเล็บปีกกาไปยังไฟล์ปัจจุบันที่กำลังเขียนไป? เพียงรวมสตริงของไฟล์อินพุตที่ทำตามawkคำสั่งตามลำดับจะประมวลผลไฟล์ทั้งหมด (หรือบางอย่างที่คล้ายกัน)?
David C. Rankin

@ DavidC.Rankin ขอขอบคุณที่ตอบกลับข้อความนี้ ใช่ฉันได้โพสต์สิ่งที่คล้ายกันที่คุณพูดว่า Sir คำตอบของฉันก็โพสต์ในคำถามนี้ lemme ทราบมุมมองของคุณใน Sir เดียวกันไชโย
RavinderSingh13

1
หลังจากหลับและคิดเกี่ยวกับมันฉันเห็น 2 ตัวเลือก (1) พร้อมawk {..} file1 .. fileXเขียนไฟล์ที่แก้ไขเช่นtemp01และในการทำซ้ำครั้งต่อไปของคุณในขณะที่ประมวลผลไฟล์ถัดไปใช้ a mv -f tmp01 input01เพื่อเขียนทับไฟล์อินพุตด้วยข้อมูลที่แก้ไข หรือ (2) เพียงเขียนไดเรกทอรีใหม่ของ./tmp/tmp01 ... ./tmp/tmp0Xในระหว่างการดำเนินการของawkสคริปต์และติดตามด้วยการวนรอบไฟล์ใน./tmpไดเรกทอรีและเช่นmv -f "$i" "input_${i##*[^0-9]}"(หรือการขยายตัวใด ๆ ที่คุณต้องการแทนที่ไฟล์อินพุตเก่า
David C. Rankin

@ DavidC.Rankin ขอขอบคุณที่ให้มุมมองของคุณรู้ว่าที่นี่ตัวเลือกที่ 1 ของ IMHO อาจมีความเสี่ยงเล็กน้อยเนื่องจากเรากำลังทำอะไรบางอย่างโดยไม่ต้องawkกรอกโค้ดให้สมบูรณ์ตัวเลือกที่ 2 เกือบจะเหมือนกับที่ฉันใช้ในคำแนะนำของฉัน รู้สึกขอบคุณถ้าคุณสามารถบอกความคิดของคุณเกี่ยวกับโซลูชันนั้น
RavinderSingh13

คำตอบ:


6

เนื่องจากจุดประสงค์หลักของเธรดนี้คือวิธีการทำ inplace SAVE ใน NON GNU awkดังนั้นฉันจึงโพสต์เทมเพลตแรกซึ่งจะช่วยให้ทุกคนในความต้องการใด ๆ พวกเขาต้องเพิ่ม / ผนวกBEGINและENDส่วนในรหัสของพวกเขาเก็บหลักของพวกเขาต่อ ความต้องการและควรแก้ไข inplace แล้ว:

หมายเหตุ:ต่อไปนี้จะเขียนเอาต์พุตทั้งหมดไปยัง output_file ดังนั้นในกรณีที่คุณต้องการพิมพ์อะไรไปยังเอาต์พุตมาตรฐานกรุณาเพิ่มprint...คำสั่งโดยไม่ต้อง> (out)ติดตาม

แม่แบบทั่วไป:

awk -v out_file="out" '
FNR==1{
close(out)
out=out_file count++
rename=(rename?rename ORS:"") "mv \047" out "\047 \047" FILENAME "\047"
}
{
    .....your main block code.....
}
END{
 if(rename){
   system(rename)
 }
}
' *.txt


วิธีการแก้ปัญหาตัวอย่างให้เฉพาะของ:

ฉันได้มาด้วยวิธีการดังต่อไปนี้ภายในawkตัวเอง (สำหรับตัวอย่างเพิ่มเติมต่อไปนี้เป็นแนวทางของฉันในการแก้ปัญหานี้และบันทึกผลลัพธ์ลงใน Input_file ตัวเอง)

awk -v out_file="out" '
FNR==1{
  close(out)
  out=out_file count++
  rename=(rename?rename ORS:"") "mv \047" out "\047 \047" FILENAME "\047"
}
{
  print FNR > (out)
}
END{
  if(rename){
    system(rename)
  }
}
' *.txt

หมายเหตุ: นี่เป็นเพียงการทดสอบสำหรับการบันทึกผลลัพธ์ที่แก้ไขแล้วลงใน Input_file (s) เองหนึ่งสามารถใช้ส่วน BEGIN ของมันพร้อมกับส่วน END ในโปรแกรมของพวกเขาส่วนหลักควรเป็นไปตามความต้องการของคำถามเฉพาะเอง

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



ต่อไปนี้คือการทดสอบรหัสข้างต้น

การทำงานของโปรแกรมด้วยตัวอย่าง:สมมติว่าต่อไปนี้คือ.txtInput_file (s):

cat << EOF > test1.txt
onetwo three
tets testtest
EOF

cat << EOF > test2.txt
onetwo three
tets testtest
EOF

cat << EOF > test3.txt
onetwo three
tets testtest
EOF

ตอนนี้เมื่อเราเรียกใช้รหัสต่อไปนี้:

awk -v out_file="out" '
FNR==1{
  close(out)
  out=out_file count++
  rename=(rename?rename ORS:"") "mv \047" out "\047 \047" FILENAME "\047"
}
{
  print "new_lines_here...." > (out)
}
END{
  if(rename){
    system("ls -lhtr;" rename)
  }
}
' *.txt

หมายเหตุ:ฉันมีที่ls -lhtrอยู่ในsystemส่วนโดยเจตนาเพื่อดูว่าไฟล์เอาต์พุตกำลังสร้าง (พื้นฐานชั่วคราว) เพราะในภายหลังจะเปลี่ยนชื่อไฟล์เป็นชื่อจริง

-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test2.txt
-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test1.txt
-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test3.txt
-rw-r--r-- 1 runner runner  38 Dec  9 05:33 out2
-rw-r--r-- 1 runner runner  38 Dec  9 05:33 out1
-rw-r--r-- 1 runner runner  38 Dec  9 05:33 out0

เมื่อเราทำ สคริปต์ls -lhtrหลังจากawkทำงานเสร็จแล้วเราจะเห็นเฉพาะ.txtไฟล์ในนั้น

-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test2.txt
-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test1.txt
-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test3.txt


คำอธิบาย: การเพิ่มคำอธิบายโดยละเอียดของคำสั่งด้านบนที่นี่:

awk -v out_file="out" '                                    ##Starting awk program from here, creating a variable named out_file whose value SHOULD BE a name of files which are NOT present in our current directory. Basically by this name temporary files will be created which will be later renamed to actual files.
FNR==1{                                                    ##Checking condition if this is very first line of current Input_file then do following.
  close(out)                                               ##Using close function of awk here, because we are putting output to temp files and then renaming them so making sure that we shouldn't get too many files opened error by CLOSING it.
  out=out_file count++                                     ##Creating out variable here, whose value is value of variable out_file(defined in awk -v section) then variable count whose value will be keep increment with 1 whenever cursor comes here.
  rename=(rename?rename ORS:"") "mv \047" out "\047 \047" FILENAME "\047"     ##Creating a variable named rename, whose work is to execute commands(rename ones) once we are done with processing all the Input_file(s), this will be executed in END section.
}                                                          ##Closing BLOCK for FNR==1  condition here.
{                                                          ##Starting main BLOCK from here.
  print "new_lines_here...." > (out)                       ##Doing printing in this example to out file.
}                                                          ##Closing main BLOCK here.
END{                                                       ##Starting END block for this specific program here.
  if(rename){                                              ##Checking condition if rename variable is NOT NULL then do following.
    system(rename)                                         ##Using system command and placing renme variable inside which will actually execute mv commands to rename files from out01 etc to Input_file etc.
  }
}                                                          ##Closing END block of this program here.
' *.txt                                                    ##Mentioning Input_file(s) with their extensions here.

1
ข้อเท็จจริงที่น่าสนุก: หากคุณลบไฟล์อินพุตในFNR==1บล็อกคุณยังสามารถบันทึกการเปลี่ยนแปลงในที่ได้ กดawk 'FNR==1{system("rm " FILENAME)} {print "new lines" > FILENAME}' files...ไลค์ สิ่งนี้ไม่น่าเชื่อถือเลย (การสูญเสียข้อมูลทั้งหมดมีแนวโน้มที่จะเกิดขึ้น) แต่ก็ยังใช้งานได้ดี: D
oguz ismail

1
อธิบายการทำงานได้ดีมาก
anubhava

3

ฉันอาจจะไปกับสิ่งนี้ถ้าฉันพยายามทำเช่นนี้:

$ cat ../tst.awk
FNR==1 { saveChanges() }
{ print FNR > new }
END { saveChanges() }

function saveChanges(   bak, result, mkBackup, overwriteOrig, rmBackup) {
    if ( new != "" ) {
        bak = old ".bak"
        mkBackup = "cp \047" old "\047 \047" bak "\047; echo \"$?\""
        if ( (mkBackup | getline result) > 0 ) {
            if (result == 0) {
                overwriteOrig = "mv \047" new "\047 \047" old "\047; echo \"$?\""
                if ( (overwriteOrig | getline result) > 0 ) {
                    if (result == 0) {
                        rmBackup = "rm -f \047" bak "\047"
                        system(rmBackup)
                    }
                }
            }
        }
        close(rmBackup)
        close(overwriteOrig)
        close(mkBackup)
    }
    old = FILENAME
    new = FILENAME ".new"
}

$ awk -f ../tst.awk test1.txt test2.txt test3.txt

ฉันต้องการคัดลอกไฟล์ต้นฉบับไปยังข้อมูลสำรองก่อนแล้วจึงดำเนินการกับการเปลี่ยนแปลงการบันทึกต้นฉบับ แต่การทำเช่นนั้นจะเปลี่ยนค่าของตัวแปร FILENAME สำหรับไฟล์อินพุตทุกไฟล์ที่ไม่พึงประสงค์

โปรดทราบว่าหากคุณมีไฟล์ต้นฉบับชื่อwhatever.bakหรือwhatever.newในไดเรกทอรีของคุณคุณจะเขียนทับไฟล์ temp ดังนั้นคุณต้องเพิ่มการทดสอบ การเรียกเพื่อmktempรับชื่อไฟล์ temp จะมีประสิทธิภาพมากกว่า

สิ่งที่มีประโยชน์มากกว่าที่จะมีในสถานการณ์นี้จะเป็นเครื่องมือที่ดำเนินการคำสั่งอื่น ๆ และทำการแก้ไขส่วน "inplace" เนื่องจากสามารถใช้เพื่อให้การแก้ไข "inplace" สำหรับ POSIX sed, awk, grep, tr, อะไรและ ไม่ต้องการให้คุณเปลี่ยนไวยากรณ์ของสคริปต์เป็นprint > outฯลฯ ทุกครั้งที่คุณต้องการพิมพ์ค่า ตัวอย่างที่เรียบง่ายบอบบาง:

$ cat inedit
#!/bin/env bash

for (( pos=$#; pos>1; pos-- )); do
    if [[ -f "${!pos}" ]]; then
        filesStartPos="$pos"
    else
        break
    fi
done

files=()
cmd=()
for (( pos=1; pos<=$#; pos++)); do
    arg="${!pos}"
    if (( pos < filesStartPos )); then
        cmd+=( "$arg" )
    else
        files+=( "$arg" )
    fi
done

tmp=$(mktemp)
trap 'rm -f "$tmp"; exit' 0

for file in "${files[@]}"; do
    "${cmd[@]}" "$file" > "$tmp" && mv -- "$tmp" "$file"
done

ซึ่งคุณจะใช้ดังนี้

$ awk '{print FNR}' test1.txt test2.txt test3.txt
1
2
1
2
1
2

$ ./inedit awk '{print FNR}' test1.txt test2.txt test3.txt

$ tail test1.txt test2.txt test3.txt
==> test1.txt <==
1
2

==> test2.txt <==
1
2

==> test3.txt <==
1
2

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

awk 'NR==FNR{a[$1];next} $1 in a' file1 file2

หรือสคริปต์ที่ตั้งค่าตัวแปรระหว่างไฟล์ในรายการหาเรื่องเช่น:

awk '{print $7}' FS=',' file1 FS=':' file2

ทำให้เหลือที่แข็งแกร่งขึ้นเป็นแบบฝึกหัดสำหรับผู้อ่าน แต่มองไปที่xargsบทสรุปเป็นจุดเริ่มต้นสำหรับวิธีที่แข็งแกร่งineditจะต้องทำงาน :-)


0

วิธีแก้ปัญหาเชลล์นั้นง่ายและรวดเร็วพอ:

for f in *.txt
do  awk '...' $f > $f.tmp
    mv $f.tmp $f
done

ค้นหาวิธีแก้ไขปัญหาอื่นเฉพาะเมื่อคุณได้แสดงให้เห็นอย่างชัดเจนว่านี่ช้าเกินไป ข้อควรจำ: การปรับให้เหมาะสมก่อนวัยอันควรเป็นรากของความชั่วร้ายทั้งหมด


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