Bash中大型固定长度文本文件的高效子字符串解析

时间:2017-11-05 13:02:50

标签: bash awk sed

我有一个固定长度数据的大文本文件(数百万条记录),需要提取唯一子串并使用这些值创建许多数组。我有一个工作版本,但我想知道性能是否可以提高,因为我需要迭代运行脚本。

$ _ 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赢了这个。无论如何,我原始代码的性能提升是巨大的。谢谢你们的贡献。

2 个答案:

答案 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)

如果贿赂较少,awkO(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,以查看代码在functioneval,var =值分配的“图层”中的执行情况。

希望您了解awk脚本如何将代码转录为awk

它确实依赖于数组只能包含一个键/值对的副本。

如果您发布所有提交的解决方案的时间,我真的很感激。 (您可以将文件大小减小1/2并仍然有一个很好的测试)。确保多次运行每个版本,并丢弃第一次运行。

IHTH