使用 gawk 对同一文件进行多次编辑的速度较慢

Slow speed with gawk for multiple edits the same file

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

问题是这个过程每个文件需要很多时间。我尝试通过将测试文件复制到 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

我不知道你脚本的其余部分在做什么,但下面会告诉你如何大幅提高它的性能。

而不是在循环的每次迭代中调用 base64trheadawk,所有开销都意味着:

for (( c=1; c<=3; c++ ))
do
    string=$(base64 /dev/urandom | tr -dc '[[:print:]]' | head -c 230)
    echo "$string" | awk '{print "<" [=10=] ">"}'
done
<nSxzxmRQc11+fFnG7ET4EBIBUwoflPo9Mop0j50C1MtRoLNjb43aNTMNRSMePTnGub5gqDWeV4yEyCVYC2s519JL5OLpBFxSS/xOjbL4pkmoFqOceX3DTmsZrl/RG+YLXxiLBjL//I220MQAzpQE5bpfQiQB6BvRw64HbhtVzHYMODbQU1UYLeM6IMXdzPgsQyghv1MCFvs0Nl4Mez2Zh98f9+472c6K+44nmi>
<9xfgBc1Y7P/QJkB6PCIfNg0b7V+KmSUS49uU7XdT+yiBqjTLcNaETpMhpMSt3MLs9GFDCQs9TWKx7yXgbNch1p849IQrjhtZCa0H5rtCXJbbngc3oF9LYY8WT72RPiV/gk4wJrAKYq8/lKYzu0Hms0lHaOmd4qcz1hpzubP7NuiBjvv16A8T3slVG1p4vwxa5JyfgYIYo4rno219ba/vRMB1QF9HaAppdRMP32>
<K5kNgv9EN1a/c/7eatrivNeUzKYolCrz5tHE2yZ6XNm1aT4ZZq3OaY5UgnwF8ePIpMKVw5LZNstVwFdVaNvtL6JreCkcO+QtebsCYg5sAwIdozwXFs4F4hZ/ygoz3DEeMWYgFTcgFnfoCV2Rct2bg/mAcJBZ9+4x9IS+JNTA64T1Zl+FJiCuHS05sFIsZYBCqRADp2iL3xcTr913dNplqUvBEEsW1qCk/TDwQh>

你应该写这个只调用每个工具一次,这样 运行 个数量级的速度会更快:

$ base64 /dev/urandom | tr -dc '[[:print:]]' |
    gawk -v RS='.{230}' '{print "<" RT ">"} NR==3{exit}'
<X0If1qkQItVLDOmh2BFYyswBgKFZvEwyA+WglyU0BhqWHLzURt/AIRgL3olCWZebktfwBU6sK7N3nwK6QV2g5VheXIY7qPzkzKUYJXWvgGcrIoyd9tLUjkM3eusuTTp4TwNY6E/z7lT0/2oQrLH/yZr2hgAm8IXDVgWNkICw81BRPUqITNt3VqmYt/HKnL4d/i88F4QDE0XgivHzWAk6OLowtmWAiT8k1a0Me6>
<TqCyRXj31xsFcZS87vbA50rYKq4cvIIn1oCtN6PJcIsSUSjG8hIhfP8zwhzi6iC33HfL96JfLIBcLrojOIkd7WGGXcHsn0F0XVauOR+t8SRqv+/t9ggDuVsn6MsY2R4J+mppTMB3fcC5787u0dO5vO1UTFWZG0ZCzxvX/3oxbExXb8M54WL6PZQsNrVnKtkvllAT/s4mKsQ/ojXNB0CTw7L6AvB9HU7W2x+U3j>
<ESsGZlHjX/nslhJD5kJGsFvdMp+PC5KA+xOYlcTbc/t9aXoHhAJuy/KdjoGq6VkP+v4eQ5lNURdyxs+jMHqLVVtGwFYSlc61MgCt0IefpgpU2e2werIQAsrDKKT1DWTfbH1qaesTy2IhTKcEFlW/mc+1en8912Dig7Nn2MD8VQrGn6BzvgjzeGRqGLAtWJWkzQjfx+74ffJQUXW4uuEXA8lBvbuJ8+yQA2WHK5>

假设:

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

正如其他人所指出的那样,您想限制自己对每个输入文件进行一次 gawk 调用。

新方法:

  • 生成$durchlauf(计数)个随机数(参见gen_numbers()函数)
  • 生成 $durchlauf(计数)base64 个字符串(我们将重用 Ed Morton 的代码)
  • paste将这2组数据合并为一个输入stream/file
  • 将 2 个文件提供给 gawk ... paste 结果和要修改的实际文件
  • 我们将无法使用 gawk-i inplace 所以我们将使用一个中间 tmp 文件
  • 当我们在输入文件中找到匹配行时,我们将 1) 插入 base64 字符串,然后 2) skip/delete current/next 输入行;这应该可以解决我们有两个随机数相差 +1
  • 的问题

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

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

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

gen_numbers () {

max=                             # $zeilen     eg, 100
count=                           # $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[]=                 # store base64 in ins[] array
                     del[]=del[()+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

@mark-fuso,哇,太快了!但是脚本中有一个错误。该文件的大小增加了一点,这是我必须避免的。我认为如果两个随机行号($durchlauf)相互跟随,那么不会删除一行。老实说,我不完全明白你的命令在做什么,但它工作得很好。我想这样的任务,我必须要有更多的bash经验。

示例输出:

64
65
66
gOf0Vvb9OyXY1Tjb1r4jkDWC4VIBpQAYnSY7KkT1gl5MfnkCMzUmN798pkgEVAlRgV9GXpknme46yZURCaAjeg6G5f1Fc7nc7AquIGnEER>
AFwB9cnHWu6SRnsupYCPViTC9XK+fwGkiHvEXrtw2aosTGAAFyu0GI8Ri2+NoJAvMw4mv/FE72t/xapmG5wjKpQYsBXYyZ9YVV0SE6c6rL>
70
71