使用 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
我不知道你脚本的其余部分在做什么,但下面会告诉你如何大幅提高它的性能。
而不是在循环的每次迭代中调用 base64
、tr
、head
和 awk
,所有开销都意味着:
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
...
- 从输入文件中删除编号为
n
和 n+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
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
我不知道你脚本的其余部分在做什么,但下面会告诉你如何大幅提高它的性能。
而不是在循环的每次迭代中调用 base64
、tr
、head
和 awk
,所有开销都意味着:
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
... - 从输入文件中删除编号为
n
和n+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