Bash shell脚本中的性能问题
我有一个200 MB的标签分隔文本文件,有数百万行。在这个文件中,我有一个包含多个位置的专栏,如美国、英国、澳大利亚等 现在我想在这个专栏的基础上打破这个文件。虽然这段代码对我来说工作得很好,但面临性能问题,因为根据位置将文件拆分为多个文件需要1个多小时。代码如下:Bash shell脚本中的性能问题,bash,shell,unix,Bash,Shell,Unix,我有一个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 h
#!/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
有几件事需要记住:
读取文件速度较慢
# 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 -F: -v col=4 -f script.awk file
mkdir -p /tmp/i
awk '{o="/tmp/i/file_"NR".txt"; print "hello" > o; printf "\r%d ",NR > "/dev/stderr"}' /dev/random
使用以下内容运行脚本:
awk -v col="$col_no" -f script.awk file
其中,$col_no
是一个shell变量,包含带有国家/地区代码的列号
如果您没有太多不同的国家/地区代码,您可以不必打开所有文件,在这种情况下,您可以删除对close(filename)
的调用
您可以在问题中提供的示例上测试脚本,如下所示:
# 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 -F: -v col=4 -f script.awk file
mkdir -p /tmp/i
awk '{o="/tmp/i/file_"NR".txt"; print "hello" > o; printf "\r%d ",NR > "/dev/stderr"}' /dev/random
请注意,我添加了
-F:
以将输入字段分隔符更改为:
我认为Tom的做法是正确的,但我会稍微简化一下
Awk在某些方面很神奇。其中一种方法是,它将保持其所有输入和输出文件句柄处于打开状态,除非您显式关闭它们。因此,如果您创建了一个包含输出文件名的变量,您可以简单地重定向到您的变量,并相信awk会将数据发送到您指定的位置,并在输出文件的处理输入不足时最终关闭输出文件
(注意:这个魔法的一个扩展是,除了重定向之外,您还可以维护多个管道。想象一下,如果您要cmd=“gzip-9>文件“$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版本:
# 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 -F: -v col=4 -f script.awk file
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在读取时使用
读取文件和执行子进程都很慢。这是文本处理工具(如awk)的作业。您可以在单个awk脚本中完成整个任务,这将更加高效。您需要向我们展示一个简单的示例,其中包含一些输入/输出示例。您为什么不在任何地方打印某些内容(/dev/null
)?f_name=`echo“file_u$loc.txt”`
太难了,也没用:使用f_name=“file_$loc.txt”
相同的句子(echo“$line”>>“$f_uname”;
)在IF-THEN-ELSE的两个部分中都可以找到。您可以在IF之后移动它,然后在我出错时更正我,但您希望根据位置将数据拆分为不同的文件,这样您将拥有data_UK.txt
,data_US.txt
等。?在对位置名称运行uniq
后,您是否考虑过awk
与for
循环?我想会快得多。我添加了一个冒号生成的示例数据。所以国家代码的列号是4Nice idea,带有文件数组,+1