awk关联数组快速增长

时间:2015-04-05 02:15:36

标签: awk out-of-memory associative-array

我有一个文件可以将数字分配给md5sums,如下所示:

0   0000001732816557DE23435780915F75
1   00000035552C6F8B9E7D70F1E4E8D500
2   00000051D63FACEF571C09D98659DC55
3   0000006D7695939200D57D3FBC30D46C
4   0000006E501F5CBD4DB56CA48634A935
5   00000090B9750D99297911A0496B5134
6   000000B5AEA2C9EA7CC155F6EBCEF97F
7   00000100AD8A7F039E8F48425D9CB389
8   0000011ADE49679AEC057E07A53208C1

另一个文件在每行中包含三个md5sums,如下所示:

00000035552C6F8B9E7D70F1E4E8D500    276EC96E149571F8A27F4417D7C6BC20    9CFEFED8FB9497BAA5CD519D7D2BB5D7
00000035552C6F8B9E7D70F1E4E8D500    44E48C092AADA3B171CE899FFC6943A8    1B757742E1BF2AA5DB6890E5E338F857

我想要的是用第一个文件的整数替换第二个文件中的第一个和第三个md5sums。目前我正在尝试以下awk脚本:

awk '{OFS="\t"}FNR==NR{map[$2]=$1;next}
{print map[$1],$2,map[$3]}' mapping.txt relation.txt

问题在于,尽管第一个文件在硬盘驱动器上仅为5.7g,但脚本需要更多16g内存。

3 个答案:

答案 0 :(得分:2)

如果您没有足够的内存来存储第一个文件,那么您需要编写类似这样的内容来查找第二个文件中每个值的第一个文件:

awk 'BEGIN{OFS="\t"}
{
    val1 = val3 = ""
    while ( (getline line < "mapping.txt") > 0 ) {
        split(line,flds)
        if (flds[2] == $1) {
            val1 = flds[1]
        }
        if (flds[2] == $3) {
            val3 = flds[1]
        }
        if ( (val1 != "") && (val3 != "") ) {
            break
        }
    }
    close("mapping.txt")

    print val1,$2,val3

}' relation.txt

会很慢。你可以添加一个N getline-d行的缓存来加速它,如果你愿意的话。

答案 1 :(得分:1)

这个问题可以解决,如下所示(file1.txt是包含整数和md5sums的文件,而file2.txt是包含三列md5sums的文件)​​:

#!/bin/sh
# First sort each of file 1 and the first and third columns of file 2 by MD5
awk '{ print $2 "\t" $1}' file1.txt | sort >file1_n.txt
# Before we sort the file 2 columns, we number the rows so we can put them
# back into the original order later
cut -f1 file2.txt | cat -n - | awk '{ print $2 "\t" $1}' | sort >file2_1n.txt
cut -f3 file2.txt | cat -n - | awk '{ print $2 "\t" $1}' | sort >file2_3n.txt
# Now do a join between them, extract the two columns we want, and put them back in order
join -t'    ' file2_1n.txt file1_n.txt | awk '{ print $2 "\t" $3}' | sort -n | cut -f2 >file2_1.txt
join -t'    ' file2_3n.txt file1_n.txt | awk '{ print $2 "\t" $3}' | sort -n | cut -f2 >file2_3.txt
cut -f2 file2.txt | paste file2_1.txt - file2_3.txt >file2_new1.txt

对于file1.txtfile2.txt每行长达100万行的情况,此解决方案和Ed Morton的awk - 解决方案在我的系统上花费的时间大致相同。我的系统将花费很长时间来解决1.4亿行的问题,无论使用何种方法,但我为1000万行的文件运行了一个测试用例。

我曾假设依赖sort(在需要时自动使用临时文件)的解决方案对于大量行应该更快,因为它将是O(N log N)运行时,而解决方案是如果两个文件具有相似的大小,则重新读取输入的每一行的映射文件将是O(N ^ 2)。

时间安排结果

我对两个候选解决方案的性能关系的假设对于我尝试过的测试用例来说是错误的。在我的系统上,基于sort的解决方案和awk - 仅解决方案对于100万和1000万行输入文件中的每一个都采用了相似(在30%以内)的时间量。 awk - 在每种情况下,只有解决方案更快。当然,当输入文件大小超过10倍时,我不知道这种关系是否会成立。

奇怪的是,1000万行问题花了大约10倍的时间来使用这两种解决方案作为100万行问题,这让我感到困惑,因为我预计两种解决方案都会出现文件长度的非线性关系。

答案 2 :(得分:1)

如果文件大小导致awk内存不足,则使用其他工具或完全采用其他方法。

sed命令可能会以更少的内存使用量成功。我们的想法是读取索引文件并创建一个执行重映射的sed脚本,然后在生成的sedscript上调用sed。

下面的bash脚本是这个想法的实现。它包含一些STDERR输出以帮助跟踪进度。我喜欢为大数据集或其他类型的耗时处理问题生成进度跟踪输出。

此脚本已在一小组数据上进行了测试; 可能处理您的数据。请试一试。

#!/bin/bash

# md5-indexes.txt
# 0   0000001732816557DE23435780915F75
# 1   00000035552C6F8B9E7D70F1E4E8D500
# 2   00000051D63FACEF571C09D98659DC55
# 3   0000006D7695939200D57D3FBC30D46C
# 4   0000006E501F5CBD4DB56CA48634A935
# 5   00000090B9750D99297911A0496B5134
# 6   000000B5AEA2C9EA7CC155F6EBCEF97F
# 7   00000100AD8A7F039E8F48425D9CB389
# 8   0000011ADE49679AEC057E07A53208C1

# md5-data.txt
# 00000035552C6F8B9E7D70F1E4E8D500    276EC96E149571F8A27F4417D7C6BC20    9CFEFED8FB9497BAA5CD519D7D2BB5D7
# 00000035552C6F8B9E7D70F1E4E8D500    44E48C092AADA3B171CE899FFC6943A8    1B757742E1BF2AA5DB6890E5E338F857

# Goal replace field 1 and field 3 with indexes to md5 checksums from md5-indexes

md5_indexes='md5-indexes.txt'
md5_data='md5-data.txt'

talk()  { echo 1>&2 "$*" ; }
talkf() { printf 1>&2 "$@" ; }
track() {
  local var="$1" interval="$2"
  local val
  eval "val=\$$var"
  if (( interval == 0 || val % interval == 0 )); then
    shift 2
    talkf "$@"
  fi
  eval "(( $var++ ))"   # increment the counter
}

# Build a sedscript to translate all occurances of the 1st & 3rd MD5 sums into their
# corresponding indexes

talk "Building the sedscript from the md5 indexes.."

sedscript=/tmp/$$.sed

linenum=0
lines=`wc -l <$md5_indexes`
interval=$(( lines / 100 ))

while read index md5sum ; do
  track linenum $interval "..$linenum"
  echo "s/^[[:space:]]*[[:<:]]$md5sum[[:>:]]/$index/" >>$sedscript
  echo "s/[[:<:]]$md5sum[[:>:]]\$/$index/"            >>$sedscript
done <$md5_indexes
talk ''

sedlength=`wc -l <$sedscript`

talkf "The sedscript is %d lines\n" $sedlength

cmd="sed -E -f $sedscript -i .bak $md5_data"
talk "Invoking: $cmd"

$cmd

changes=`diff -U 0 $md5_data.bak $md5_data | tail +3 | grep -c '^+'`

talkf "%d lines changed in $md5_data\n" $changes

exit

以下是两个文件:

cat md5-indexes.txt
0   0000001732816557DE23435780915F75
1   00000035552C6F8B9E7D70F1E4E8D500
2   00000051D63FACEF571C09D98659DC55
3   0000006D7695939200D57D3FBC30D46C
4   0000006E501F5CBD4DB56CA48634A935
5   00000090B9750D99297911A0496B5134
6   000000B5AEA2C9EA7CC155F6EBCEF97F
7   00000100AD8A7F039E8F48425D9CB389
8   0000011ADE49679AEC057E07A53208C1

cat md5-data.txt
00000035552C6F8B9E7D70F1E4E8D500    276EC96E149571F8A27F4417D7C6BC20    9CFEFED8FB9497BAA5CD519D7D2BB5D7
00000035552C6F8B9E7D70F1E4E8D500    44E48C092AADA3B171CE899FFC6943A8    1B757742E1BF2AA5DB6890E5E338F857

以下是示例运行:

$ ./md5-reindex.sh
Building the sedscript from the md5 indexes..
..0..1..2..3..4..5..6..7..8
The sedscript is 18 lines
Invoking: sed -E -f /tmp/83800.sed -i .bak md5-data.txt
2 lines changed in md5-data.txt

最后,生成的文件:

$ cat md5-data.txt
1    276EC96E149571F8A27F4417D7C6BC20    9CFEFED8FB9497BAA5CD519D7D2BB5D7
1    44E48C092AADA3B171CE899FFC6943A8    1B757742E1BF2AA5DB6890E5E338F857