Shell-Script

使用 awk 數組時,對大數求和並列印帶有所有小數點的結果

  • May 18, 2020

我有一個下面的輸入文件,我需要根據第 3 列中的日期將其拆分為多個文件。基本上所有相同日期的交易都應該被分成特定的日期文件。拆分後我需要創建一個標題和拖車。預告片應包含記錄的計數和第 4 列中的金額總和(該日期的金額總和)。在這種情況下,正如我上面所說,我的數量非常大,我如何將 bc 集成到下面的程式碼中。

輸入文件

H|~^20200425|~^abcd|~^sum
R|~^abc|~^2019-03-06T12:33:52.27|~^123562388.23456|~^2018-04-12T12:33:52.27|~^hhh
R|~^abc|~^2019-03-05T12:33:52.27|~^105603.042|~^2018-10-23T12:33:52.27|~^aus
R|~^abc|~^2019-03-05T12:33:52.27|~^2054.026|~^2018-10-24T12:33:52.27|~^usa
R|~^abc|~^2019-03-06T12:33:52.27|~^10.00|~^2018-09-11T12:33:52.27|~^virginia
R|~^abc|~^2019-03-05T12:33:52.27|~^30.00|~^2018-08-05T12:33:52.27|~^ddd
R|~^abc|~^2019-03-06T12:33:52.27|~^15.03|~^2018-10-23T12:33:52.27|~^jjj
R|~^abc|~^2019-03-06T12:33:52.27|~^10.04|~^2018-04-08T12:33:52.27|~^jj
R|~^abc|~^2019-03-05T12:33:52.27|~^20.00|~^2018-07-23T12:33:52.27|~^audg
T|~^20200425|~^8|~^xxx|~^123670130.37256

輸出文件 20190305.txt

H|~^20200425|~^abcd|~^sum
R|~^abc|~^2019-03-05T12:33:52.27|~^105603.042|~^2018-10-23T12:33:52.27|~^aus
R|~^abc|~^2019-03-05T12:33:52.27|~^2054.026|~^2018-10-24T12:33:52.27|~^usa
R|~^abc|~^2019-03-05T12:33:52.27|~^30.00|~^2018-08-05T12:33:52.27|~^ddd
R|~^abc|~^2019-03-05T12:33:52.27|~^20.00|~^2018-07-23T12:33:52.27|~^audg
T|~^20200425|~^4|~^xxx|~^107707.068

輸出文件 20190306.txt

H|~^20200425|~^abcd|~^sum
R|~^abc|~^2019-03-06T12:33:52.27|~^123562388.23456|~^2018-04-12T12:33:52.27|~^hhh
R|~^abc|~^2019-03-06T12:33:52.27|~^10.00|~^2018-09-11T12:33:52.27|~^virginia
R|~^abc|~^2019-03-06T12:33:52.27|~^15.03|~^2018-10-23T12:33:52.27|~^jjj
R|~^abc|~^2019-03-06T12:33:52.27|~^10.04|~^2018-04-08T12:33:52.27|~^jj
T|~^20200425|~^4|~^xxx|~^123562423.30456

我正在使用的程式碼(PS:由我們的一位社區成員建議)這是一個awk解決方案:

awk -F'\\|~\\^' '{ 
           if($1=="H"){ 
               head=$0
           }
           else if($1=="T"){
               foot=$1"|~^"$2
               foot4=$4
           }
           else{
               date=$3;
               sub("T.*","", date);
               data[date][NR]=$0;
               sum[date]+=$4; 
               num[date]++
           }
          }
          END{
           for(date in data){
               file=date".txt";
               gsub("-","",file); 
               print head > file; 
               for(line in data[date]){
                   print data[date][line] > file
               } 
               printf "%s|~^%s|~^%s|~^%s\n", foot, num[date], 
                                             foot4, sum[date] > file
           }
          }' file 

程式碼執行良好。但在步驟

sum[date]+=$4;

它無法對大數求和。因為我%s在最後一步使用,所以 Trailer sum 以指數值列印。

printf "%s|~^%s|~^%s|~^%s\n", foot, num[date], 
                                                 foot4, sum[date] > file

在這裡,我只想對大數應用 sum 並列印確切的總和。(我在這裡嘗試了 bc(bash 計算器)但被卡住了,因為這個總和是基於數組之外的,而且它也是根據特定日期添加的)。請幫我解決這個問題

另外,我嘗試"%.15g"了預告片步驟

printf "%s|~^%s|~^%s|~^%.15g\n", foot, num[date], 
                                                 foot4, sum[date] > file

在這種情況下,如果結果有 15 位(包括小數),我可以得到確切的總和。如果總和結果超過 15 位,這是行不通的。請幫助

如果不考慮您的大數字問題,我會編寫如下awk程序:

BEGIN {
       FS = "\\|~\\^"
       OFS= "|~^"
}

$1 == "H" {
       header = $0
}

$1 == "R" {
       name = $3
       sub("T.*", "", name)

       sum[name] += $4
       cnt[name] += 1

       if (cnt[name] == 1)
               print header >name ".txt"

       print >name ".txt"
}

$1 == "T" {
       for (name in sum)
               print $1, $2, cnt[name], $4, sum[name] >name ".txt"
}

為方便起見,我將輸出欄位分隔符 , 設置OFS|~^。這使我不必擔心在我輸出的欄位之間插入它。輸入的欄位分隔符 ,FS設置為與該字元串匹配的正則表達式。

然後我有三個主要的程式碼塊:

  1. 一種用於解析H行。假設只有其中一個,並且它發生在開始時。這只是將標題行儲存在變數中header
  2. 一種用於解析R行。每條記錄都包含應用作第三個欄位中的輸出文件名的日期。解析方式與您執行此操作的方式相同。該日期的總和被累積,並且計數器也增加。

如果計數器為 1,即這是我們第一次看到該特定日期,我們將標題寫入與該日期關聯的輸出文件。然後我們將目前記錄寫入文件。 3. 最後一個塊解析該T行。假設只有其中之一併且它發生在最後。T這只是將每個單獨日期的累積總和和計數以及來自原始行的一些數據輸出到與該日期關聯的文件中。

為了支持任意大數(您在其他地方說您有需要儲存超過 100 位的數字,因此會溢出 中的整數awk),我們使用任意精度計算器bc作為“協程序”(一種計算服務)。這句話sum[name] += $4被替換為

if (sum[name] == "") sum[name] = 0
printf "%s + %s\n", sum[name], $4 |& "bc"
"bc" |& getline sum[name]

這需要 GNU awk(以一種或另一種方式可用於大多數 Unix 系統)。

如果該日期還沒有總和,則首先將目前日期的總和初始化為零。我們這樣做是因為我們需要為初始總和提供一個0to bc

然後我們列印應該使用 GNU特定管道bc計算的表達式以寫入協同程序。該實用程序將與我們的腳本並行啟動和執行,執行計算,然後從另一個管道讀取輸出,直接進入.awk``|&``bc``awk``getline``bc``|&``sum[name]

據我了解,GNUawk不會bc為每個求和生成一個單獨的程序,而是會保持一個bc程序作為協程序執行。因此,這將比在本機內部進行計算要慢,但比為每個求和awk生成單獨的求和要快得多。bc

對於給定的數據,將創建以下兩個文件:

$ cat 2019-03-05.txt
H|~^20200425|~^abcd|~^sum
R|~^abc|~^2019-03-05T12:33:52.27|~^105603.042|~^2018-10-23T12:33:52.27|~^aus
R|~^abc|~^2019-03-05T12:33:52.27|~^2054.026|~^2018-10-24T12:33:52.27|~^usa
R|~^abc|~^2019-03-05T12:33:52.27|~^30.00|~^2018-08-05T12:33:52.27|~^ddd
R|~^abc|~^2019-03-05T12:33:52.27|~^20.00|~^2018-07-23T12:33:52.27|~^audg
T|~^20200425|~^4|~^xxx|~^107707.068
$ cat 2019-03-06.txt
H|~^20200425|~^abcd|~^sum
R|~^abc|~^2019-03-06T12:33:52.27|~^123562388.23456|~^2018-04-12T12:33:52.27|~^hhh
R|~^abc|~^2019-03-06T12:33:52.27|~^10.00|~^2018-09-11T12:33:52.27|~^virginia
R|~^abc|~^2019-03-06T12:33:52.27|~^15.03|~^2018-10-23T12:33:52.27|~^jjj
R|~^abc|~^2019-03-06T12:33:52.27|~^10.04|~^2018-04-08T12:33:52.27|~^jj
T|~^20200425|~^4|~^xxx|~^123562423.30456

引用自:https://unix.stackexchange.com/questions/587143