首页 > 解决方案 > 使用gawk慢速多次编辑同一个文件

问题描述

我运行一个测试环境,在那里我用 lorem alg 创建了 40 000 个测试文件。文件大小在 200k 到 5 MB 之间。我想修改很多随机文件。我将通过删除 2 行并使用 base64 字符串插入 1 行来更改 5% 的行。

问题是这个过程每个文件需要很多时间。我尝试通过将 testfile 复制到 ram 并在那里进行更改来修复,但我看到一个线程只使用一个完整的核心,而 gawk 显示最多的 cpu 工作。我正在寻找一些解决方案,但我找不到正确的建议。我认为 gawk 可以一步完成,但是对于大文件,当我使用“getconf ARG_MAX”进行计算时,我会得到一个很长的字符串。

我怎样才能加快速度?

zeilen=$(wc -l < testfile$filecount.txt);
    
    durchlauf=$(($zeilen/20))
    zeilen=$((zeilen-2))
    for (( c=1; c<=durchlauf; c++ ))
    do
        zeile=$(shuf -i 1-$zeilen -n 1);
        
        zeile2=$((zeile+1))
        zeile3=$((zeile2+1))
        
        string=$(base64 /dev/urandom | tr -dc '[[:print:]]' | head -c 230)
        
        if [[ $c -eq 1 ]] 
        then
        gawk -v n1="$zeile" -v n2="$zeile2" -v n3="$zeile3" -v s="$string" 'NR==n1{next;print} \
        NR==n2{next; print} NR==n3{print s}1' testfile$filecount.txt > /mnt/RAM/tempfile.tmp
        else
        gawk -i inplace -v n1="$zeile" -v n2="$zeile2" -v n3="$zeile3" -v s="$string" 'NR==n1{next; print} \
        NR==n2{next; print} NR==n3{print s}1' /mnt/RAM/tempfile.tmp
        fi
       
    done

标签: bashperformancefileawk

解决方案


假设:

  • 生成$durchlauf(一个数字)随机行号;我们将单个数字称为n...
  • 从输入文件中删除编号的行n并替换它们的位置...n+1
  • 插入$string(随机生成的base64字符串)
  • 这个随机行号列表不能有任何连续的行号

正如其他人指出的那样,您希望将自己限制为gawk每个输入文件一次调用。

新的方法:

  • 生成$durchlauf(计数)随机数(见gen_numbers()函数)
  • 生成$durchlauf(计数)base64字符串(我们将重用 Ed Morton 的代码)
  • paste将这 2 组数据放入单个输入流/文件中
  • 将 2 个文件提供给gawk...paste结果和要修改的实际文件
  • 我们将无法使用gawk's -i inplace,因此我们将使用中间 tmp 文件
  • 当我们在输入文件中找到匹配行时,我们将1)插入base64字符串,然后2)跳过/删除当前/下一个输入行;这应该解决我们有两个不同的随机数的问题+1

确保我们不会生成连续行号的一个想法:

  • 将我们的行号集合分解为范围,例如,将 100 行分成 5 个范围 => 1-20/ 21-40/ 41-60/ 61-80/81-100
  • 将每个范围的末尾减 1 ,例如,1-19////21-3941-5961-7981-99
  • 用于在每个范围之间生成数字(这往往比可比较的调用$RANDOM至少快一个数量级)shuf

我们将使用一个函数来生成我们的非连续行号列表:

gen_numbers () {

max=$1                             # $zeilen     eg, 100
count=$2                           # $durchlauf  eg, 5

interval=$(( max / count ))        # eg, 100 / 5 = 20

for (( start=1; start<max; start=start+interval ))
do
        end=$(( start + interval - 2 ))

        out=$(( ( RANDOM % interval ) + start ))
        [[ $out -gt $end ]] && out=${end}

        echo ${out}
done
}

样品运行:

$ zeilen=100
$ durchlauf=5
$ gen_numbers ${zeilen} ${durchlauf}
17
31
54
64
86

paste/gen_numbers/base64/tr/gawk想法的展示:

$ zeilen=300
$ durchlauf=3
$ paste <( gen_numbers ${zeilen} ${durchlauf} ) <( base64 /dev/urandom | tr -dc '[[:print:]]' | gawk -v max="${durchlauf}" -v RS='.{230}' '{print RT} FNR==max{exit}' ) 

这会产生:

74      7VFhnDN4J...snip...rwnofLv
142     ZYv07oKMB...snip...xhVynvw
261     gifbwFCXY...snip...hWYio3e

主要代码:

tmpfile=$(mktemp)

while/for loop ... # whatever OP is using to loop over list of input files
do
    zeilen=$(wc -l < "testfile${filecount}".txt)
    durchlauf=$(( $zeilen/20 ))

    awk '

    # process 1st file (ie, paste/gen_numbers/base64/tr/gawk)

    FNR==NR        { ins[$1]=$2                 # store base64 in ins[] array
                     del[$1]=del[($1)+1]        # make note of zeilen and zeilen+1 line numbers for deletion
                     next
                   }

    # process 2nd file

    FNR in ins     { print ins[FNR] }           # insert base64 string?

    ! (FNR in del)                              # if current line number not in del[] array then print the line

    ' <( paste <( gen_numbers ${zeilen} ${durchlauf} ) <( base64 /dev/urandom | tr -dc '[[:print:]]' | gawk -v max="${durchlauf}" -v RS='.{230}' '{print RT} FNR==max{exit}' )) "testfile${filecount}".txt > "${tmpfile}"

    # the last line with line continuations for readability:
    #' <( paste \
    #         <( gen_numbers ${zeilen} ${durchlauf} ) \
    #         <( base64 /dev/urandom | tr -dc '[[:print:]]' | gawk -v max="${durchlauf}" -v RS='.{230}' '{print RT} FNR==max{exit}' ) \
    #   ) \
    #"testfile${filecount}".txt > "${tmpfile}"

    mv "${tmpfile}" "testfile${filecount}".txt

done

运行代码的简单示例awk

$ cat orig.txt
line1
line2
line3
line4
line5
line6
line7
line8
line9

$ cat paste.out           # simulated output from paste/gen_numbers/base64/tr/gawk
1 newline1
5 newline5

$ awk '...' paste.out orig.txt
newline1
line3
line4
newline5
line7
line8
line9

推荐阅读