簡體   English   中英

Shell腳本中的性能問題

[英]performance issues in shell script

我有200 MB的制表符分隔的文本文件,其中包含數百萬行。 在此文件中,我有一列包含多個位置,例如美國,英國,澳大利亞等。

現在,我想在此專欄的基礎上打破這個文件。 盡管此代碼對我來說很好用,但由於要根據位置將文件拆分為多個文件需要花費超過1個小時的時間,因此面臨性能問題。 這是代碼:

#!/bin/bash

read -p "Please enter the file to split " file
read -p "Enter the Col No. to split " col_no

#set -x

header=`head -1 $file`

cnt=1
while IFS= read -r line
do
        if [ $((cnt++)) -eq 1 ]
        then
                echo "$line" >> /dev/null
        else
                loc=`echo "$line" | cut -f "$col_no"`
                f_name=`echo "file_"$loc".txt"`
                if [ -f "$f_name" ]
                then
                        echo "$line" >> "$f_name";
                else
                        touch "$f_name";
                        echo "file $f_name created.."
                        echo "$line" >> "$f_name";
                        sed -i '1i '"$header"'' "$f_name"
                fi
        fi

done < $file

這里應用的邏輯是,我們只讀取一次整個文件,然后根據位置創建數據並將其附加到該文件。

請建議對代碼進行必要的改進以增強其性能。

以下是示例數據,並用冒號代替制表符分隔。 國家/地區代碼在第四欄中:

ID1:ID2:ID3:ID4:ID5
100:abcd:TEST1:ZA:CCD
200:abcd:TEST2:US:CCD
300:abcd:TEST3:AR:CCD
400:abcd:TEST4:BE:CCD
500:abcd:TEST5:CA:CCD
600:abcd:TEST6:DK:CCD
312:abcd:TEST65:ZA:CCD
1300:abcd:TEST4153:CA:CCD

有幾件事要牢記:

  1. 使用while read讀取文件的速度很慢
  2. 創建子shell和執行外部進程很慢

這是文本處理工具(如awk)的工作。

我建議您使用如下所示的內容:

# save first line
NR == 1 {
    header = $0
    next
}

{
    filename = "file_" $col  ".txt"

    # if country code has changed
    if (filename != prev) {
        # close the previous file
        close(prev)
        # if we haven't seen this file yet
        if (!(filename in seen)) {
            print header > filename
        }
        seen[filename]
    }

    # print whole line to file
    print >> filename
    prev = filename
}

使用以下幾行來運行腳本:

awk -v col="$col_no" -f script.awk file

其中$col_no是一個shell變量,其中包含帶有國家/地區代碼的列號。

如果您沒有太多不同的國家/地區代碼,則可以不用打開所有文件,在這種情況下,可以刪除對close(filename)的調用。

您可以像這樣在問題中提供的樣本上測試腳本:

awk -F: -v col=4 -f script.awk file

請注意,我添加了-F:將輸入字段分隔符更改為:

我認為湯姆走在正確的道路上,但我會稍微簡化一下。

Awk在某些方面具有魔力。 其中一種方法是,除非您明確關閉它們,否則它將保持所有輸入和輸出文件句柄處於打開狀態。 因此,如果您創建一個包含輸出文件名的變量,則可以簡單地重定向到您的變量,並相信awk會將數據發送到您指定的位置,並最終在輸出文件用盡時關閉輸出文件。

(注意,此魔術的擴展是,除了重定向之外,您還可以維護多個PIPES。想象一下,如果要cmd="gzip -9 > file_"$4".txt.gz"; print | cmd

以下內容將拆分文件,而不會在每個輸出文件中添加標題。

awk -F: 'NR>1 {out="file_"$4".txt"; print > out}' inp.txt

如果添加標題很重要,則需要更多代碼。 但並不多。

awk -F: 'NR==1{h=$0;next} {out="file_"$4".txt"} !(out in files){print h > out; files[out]} {print > out}' inp.txt

或者,因為此單線現在有點長,我們可以將其分開進行解釋:

awk -F: '
  NR==1 {h=$0;next}        # Capture the header
  {out="file_"$4".txt"}    # Capture the output file
  !(out in files){         # If we haven't seen this output file before,
    print h > out;         # print the header to it,
    files[out]             # and record the fact that we've seen it.
  }
  {print > out}            # Finally, print our line of input.
' inp.txt

我在問題中提供的輸入數據上成功測試了這兩個腳本。 使用這種解決方案,無需對輸入數據進行排序-每個文件中的輸出將按照該子集的記錄在輸入數據中出現的順序。

注意: awk不同版本將允許您打開不同數量的打開文件。 GNU awk( gawk )有數千個限制-大大超過您可能要處理的國家/地區的數量。 BSD awk版本20121220(在FreeBSD中)似乎在21117個文件后用完。 BSD awk版本20070501(在OS X El Capitan中)限制為17個文件。

如果您不確定打開文件的數量,可以嘗試使用以下版本的awk usig:

mkdir -p /tmp/i
awk '{o="/tmp/i/file_"NR".txt"; print "hello" > o; printf "\r%d ",NR > "/dev/stderr"}' /dev/random

您還可以測試打開的管道數:

awk '{o="cat >/dev/null; #"NR; print "hello" | o; printf "\r%d ",NR > "/dev/stderr"}' /dev/random

(如果您有一個/dev/yes或只是吐出一行文本廣告惡作劇的東西,那將比使用/ dev / random輸入更好。)

我以前在自己的awk編程中沒有遇到這個限制,因為當我需要創建許多輸出文件時,我總是使用gawk。 :-P

暫無
暫無

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

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