[英]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
有幾件事要牢記:
while read
讀取文件的速度很慢 這是文本處理工具(如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.