我有一个固定长度数据的大文本文件(数百万条记录),需要提取唯一子串并使用这些值创建许多数组。我有一个工作版本,但我想知道性能是否可以提高,因为我需要迭代运行脚本。
$ _ file5看起来像:
138000010065011417865201710152017102122
138000010067710416865201710152017102133
138000010131490417865201710152017102124
138000010142349413865201710152017102154
138400010142356417865201710152017102165
130000101694334417865201710152017102176
这是我到目前为止所做的:
while IFS='' read -r line || [[ -n "$line" ]]; do
_in=0
_set=${line:15:6}
_startDate=${line:21:8}
_id="$_account-$_set-$_startDate"
for element in "${_subsets[@]}"; do
if [[ $element == "$_set" ]]; then
_in=1
break
fi
done
# If we find a new one and it's not 504721
if [ $_in -eq 0 ] && [ $_set != "504721" ] ; then
_subsets=("${_subsets[@]}" "$_set")
_ids=("${_ids[@]}" "$_id")
fi
done < $_file5
这会产生:
_subsets=("417865","416865","413865")
_ids=("9899-417865-20171015", "9899-416865-20171015", "9899-413865-20171015")
我不确定sed或awk在这里会不会更好,也无法找到实现的方法。感谢。
编辑:基准测试
所以我将原始解决方案与提供的两个解决方案进行了对比。超过10次,所有结果都与下面相似。
# Bash read
real 0m8.423s
user 0m8.115s
sys 0m0.307s
# Using sort -u (@randomir)
real 0m0.719s
user 0m0.693s
sys 0m0.041s
# Using awk (@shellter)
real 0m0.159s
user 0m0.152s
sys 0m0.007s
看起来awk赢了这个。无论如何,我原始代码的性能提升是巨大的。谢谢你们的贡献。
答案 0 :(得分:2)
我认为您不能使用sort -u
循环优于bash
的效果(极端情况除外,为this one turned out to be,请参阅脚注✻)。
根据子字符串将file
中的字符串列表缩减为唯一字符串(集)列表:
sort -k1.16,1.21 -u file >set
然后,要从位置504721
开始过滤掉不需要的ID 16
,您可以使用grep -v
:
grep -vE '.{15}504721' set
最后,重新格式化剩余的行并将其存储在包含cut
/ sed
/ awk
/ bash
的数组中。
因此,要填充 _subsets
数组,例如:
$ _subsets=($(sort -k1.16,1.21 -u file | grep -vE '.{15}504721' | cut -c16-21))
$ printf "%s\n" "${_subsets[@]}"
413865
416865
417865
或填充 _ids
数组:
$ _ids=($(sort -k1.16,1.21 -u file | grep -vE '.{15}504721' | sed -E 's/^.{15}(.{6})(.{8}).*/9899-\1-\2/'))
$ printf "%s\n" "${_ids[@]}"
9899-413865-20171015
9899-416865-20171015
9899-417865-20171015
✻如果输入文件很大,但它只包含小数字(~40
)的唯一元素(对于相关字段),那么让awk
solution变得更快是完全合理的。 sort
需要对一个巨大的文件(O(N*logN)
)进行排序,然后过滤欺骗(O(N)
),全部为大N.另一方面,awk
需要通过通过大输入只需一次,通过集合成员资格测试检查欺骗。由于唯一身份集很小,因此成员资格测试仅需O(1)
(平均来说,但对于如此小的集合,即使在最坏的情况下几乎保持不变),也会使总时间O(N)
。
如果贿赂较少,awk
将O(N*log(N))
摊销,O(N2)
最坏情况。更不用说更高的常量每指令开销。
简而言之:在为工作选择合适的工具之前,您必须先了解数据的样子。
答案 1 :(得分:1)
这是awk
脚本中嵌入的bash
解决方案:
#!/bin/bash
fn_parser() {
awk '
BEGIN{ _account="9899" }
{ _set=substr($0,16,6)
_startDate=substr($0,22,8)
#dbg print "#dbg:_set=" _set "\t_startDate=" _startDate
if (_set != "504721") {
_id= _account "-" _set"-" _startDate
ids[_id] = _id
sets[_set]=_set
}
}
END {
printf "_subsets=("
for (s in sets) { printf("%s\"%s\"" , (commaCtr++ ? "," : ""), sets[s]) }
print ");"
printf "_ids=("
for (i in ids) { printf("%s\"%s\"" , (commaCtr2++ ? "," : ""), ids[i]) }
print ")"
}
' "${@}"
}
#dbg set -vx
eval $( echo $(fn_parser *.txt) )
echo "_subsets="$_subsets
echo "_ids="$_ids
<强>输出强>
_subsets=413865,417865,416865
_ids=9899-416865-20171015,9899-413865-20171015,9899-417865-20171015
如果您对变量名称echo
执行了操作,我相信您的脚本会得到相同的输出。
我没有看到从您的文件中提取_account
,并假设它是从您批处理中的上一步传入的。但在我知道这是否是一个关键部分之前,我将不得不回过头来确定如何将var传递给调用awk
的函数。
人们不会喜欢使用eval,但希望没有人会将/bin/rm -rf /
嵌入到您的数据集中; - )
我使用eval
,以便通过shell变量提取所提取的数据。您可以在#dbg
行之前取消注释eval
,以查看代码在function
,eval
,var =值分配的“图层”中的执行情况。
希望您了解awk
脚本如何将代码转录为awk
。
它确实依赖于数组只能包含一个键/值对的副本。
如果您发布所有提交的解决方案的时间,我真的很感激。 (您可以将文件大小减小1/2并仍然有一个很好的测试)。确保多次运行每个版本,并丢弃第一次运行。
IHTH