簡體   English   中英

使用非 GNU awk 保存修改

[英]Save modifications in place with NON GNU awk

我遇到了一個問題(關於 SO 本身),其中 OP 必須對 Input_file(s) 本身進行編輯和保存操作。

我知道對於單個 Input_file 我們可以執行以下操作:

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

現在假設我們需要以相同類型的文件格式進行更改(假設這里是 .txt)。

我對這個問題的嘗試/想法:它的方法是通過 .txt 文件的 for 循環並調用單個awk是一個痛苦且不推薦的過程,因為它會浪費不必要的 cpu 周期並且對於更多數量的文件,它將是更慢。

那么在這里可以做什么來使用不支持就地選項的非 GNU awk對多個文件執行就地編輯。 我也經歷過這個線程使用 awk 保存修改,但是對於非 GNU awk 的惡習和在awk本身中就地更改多個文件沒有什么,因為非 GNU awk 將沒有inplace選項。

注意:為什么我要添加bash標簽,因為在我的回答部分中,我使用 bash 命令將臨時文件重命名為它們的實際 Input_file 名稱,因此添加它。



編輯:根據 Ed sir 的評論,在此處添加示例示例,盡管此線程代碼的用途也可用於通用就地編輯。

示例 Input_file(s):

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

由於該線程的主要目的是如何在非 GNU awk進行就地保存,因此我首先發布其模板,這將有助於滿足任何需求的任何人,因此他們需要在代碼中添加/附加BEGINEND部分,以保持其主要塊根據他們的要求,它應該進行就地編輯,然后:

注意:以下會將其所有輸出寫入 output_file,因此,如果您想將任何內容打印到標准輸出,請僅在后面添加不帶> (out) print...語句。

通用模板:

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(s) 但在操作期間它需要系統/目錄上的空間



以下是對上述代碼的測試。

使用示例執行程序:假設以下是.txt Input_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

當我們在awk腳本運行完成后執行ls -lhtr ,我們只能在那里看到.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.

如果我嘗試這樣做,我可能會采用這樣的方法:

$ 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.bakwhatever.new的原始文件,那么您將用臨時文件覆蓋它們,因此您也需要為此添加測試。 調用mktemp以獲取臨時文件名會更可靠。

在這種情況下更有用的東西是一個執行任何其他命令並執行“就地”編輯部分的工具,因為它可用於為 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

或在 arg 列表中的文件之間設置變量的腳本,例如:

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

讓它更健壯留給讀者練習,但將xargs概要作為一個起點,了解健壯的inedit需要如何工作:-)。

shell 解決方案很簡單,而且可能足夠快:

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

僅當您最終證明這太慢時才搜索不同的解決方案。 請記住:過早的優化是萬惡之源。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM