如何使用 awk 交换行,只有一次通过和有限的内存使用?
how swap lines with awk with only a single pass and limited memory use?
在之前的post中,显示了这个答案:,虽然很漂亮,但问题是你应该读取输入文件两次。
我希望制作一个 GNU awk 脚本来只读取一次输入。
cat swap_line.awk
你得到
BEGIN {
if(init > end){
exit 1;
}
flag = 1;
memory_init = "";
memory = ""
}
{
if (NR != init && NR != end){
if(flag==1){
print [=11=];
}else{
memory = memory""[=11=]"\n";
}
}else if(end == init){
print [=11=];
}else if(NR == init){
flag = 0;
memory_init = [=11=];
}else{
#NR == end
print [=11=];
printf("%s",memory);
print memory_init;
flag = 1;
}
}
END {
#if end is greater than the number of lines of the file
if(flag == 0){
printf("%s",memory);
print memory_init;
}
}
脚本运行良好
cat input
1
2
3
4
5
awk -v init=2 -v end=4 -f swap_line.awk input
1
4
3
2
5
awk -v init=2 -v end=2 -f swap_line.awk input
1
2
3
4
5
awk -v init=2 -v end=8 -f swap_line.awk input
1
3
4
5
2
问题
我怎样才能更好地制作脚本?因为,我不喜欢使用 memory
变量,因为对于大文件可能会有问题,例如,如果输入文件是 1000 万行并且想要在第 1 行和第 1000 万行之间进行交换,我存储memory
变量
中的 9,999,998 行
@JoseRicardoBustosM。如果不保存从 init 到内存中结束行之前的行,就不可能在 awk 中一次完成。试想一下不可能在您已经阅读的内容之前获得一行 N 行以奇迹般地出现在当前行的位置。最好的解决方案绝对是一种简单的 2 遍方法,即在第 1 遍中保存线条并在第 2 遍中使用它们。我包括所有涉及提前 grep 或在“2”遍方法桶中使用 getline 循环的解决方案。
FWIW 这是我真正会做的方式(这是一个 2-pass 方法):
$ cat swap_line.awk
BEGIN { ARGV[ARGC]=ARGV[ARGC-1]; ARGC++ }
NR==FNR { if (NR==end) tl=[=10=]; next }
FNR==init { hd=[=10=]; [=10=]=tl; nr=NR-FNR; if (nr<end) next }
FNR==end { [=10=]=hd }
FNR==nr { if (nr<end) [=10=] = [=10=] ORS hd }
{ print }
.
$ awk -v init=2 -v end=4 -f swap_line.awk input
1
4
3
2
5
$ awk -v init=2 -v end=2 -f swap_line.awk input
1
2
3
4
5
$ awk -v init=2 -v end=8 -f swap_line.awk input
1
3
4
5
2
请注意,如果您对如何处理超过文件末尾的 "end" 没有非常具体的要求,那么解决方案就是:
$ cat swap_line.awk
BEGIN { ARGV[ARGC]=ARGV[ARGC-1]; ARGC++ }
NR==FNR { if (NR==end) tl=[=12=]; next }
FNR==init { hd=[=12=]; [=12=]=tl }
FNR==end { [=12=]=hd }
{ print }
如果你真的想考虑一些事情(同样,只是为了晴天的情况):
$ cat swap_line.awk
NR==init { hd=[=13=]; while ((getline<FILENAME)>0 && ++c<end); }
NR==end { [=13=]=hd }
{ print }
$ awk -v init=2 -v end=4 -f swap_line.awk input
1
4
3
2
5
我仍然认为最后一个是“2”遍方法,如果我没有完全理解 http://awk.info/?tip/getline 中列出的所有注意事项,我就不会这样做。
我觉得你太辛苦了。这不会尝试处理极端情况(例如,如果 end 大于行数,则不会打印初始行,但这可以很容易地在 END 块中处理),因为我认为处理边缘情况会模糊不清这个想法。即,打印直到到达要换出的行,然后将数据存储在文件中,然后打印要交换的行、存储的数据和初始行,然后打印文件的其余部分:
$ cat swap.sh
#!/bin/sh
trap 'rm -f $T1' 0
T1=$(mktemp)
awk '
NR<init { print; next; }
NR==init { f = [=10=]; next; }
NR<end { print > t1; next; }
NR==end { print; system("cat "t1); print f; next; }
1
' init=${1?} end=${2?} t1=$T1
$ yes | sed 10q | nl -ba | ./swap.sh 4 8
1 y
2 y
3 y
8 y
5 y
6 y
7 y
4 y
9 y
10 y
我同意需要 2 次通过。第一步可以使用专为该任务设计的工具完成:
# $init and $end have been defined
endline=$( tail -n "+$end" file | head -n 1 )
awk -v init="$init" -v end="$end" -v endline="$endline" '
NR == init {saved = [=10=]; [=10=] = endline}
NR == end {[=10=] = saved}
{print}
' file
在函数中隐藏细节:
swap_lines () {
awk -v init="" \
-v end="" \
-v endline="$(tail -n "+" "" | head -n 1)" \
'
NR == init {saved = [=11=]; [=11=] = endline}
NR == end {[=11=] = saved}
1
' ""
}
seq 5 > file
swap_lines 2 4 file
1
4
3
2
5
在之前的post中,显示了这个答案:
我希望制作一个 GNU awk 脚本来只读取一次输入。
cat swap_line.awk
你得到
BEGIN {
if(init > end){
exit 1;
}
flag = 1;
memory_init = "";
memory = ""
}
{
if (NR != init && NR != end){
if(flag==1){
print [=11=];
}else{
memory = memory""[=11=]"\n";
}
}else if(end == init){
print [=11=];
}else if(NR == init){
flag = 0;
memory_init = [=11=];
}else{
#NR == end
print [=11=];
printf("%s",memory);
print memory_init;
flag = 1;
}
}
END {
#if end is greater than the number of lines of the file
if(flag == 0){
printf("%s",memory);
print memory_init;
}
}
脚本运行良好
cat input
1
2
3
4
5
awk -v init=2 -v end=4 -f swap_line.awk input
1
4
3
2
5
awk -v init=2 -v end=2 -f swap_line.awk input
1
2
3
4
5
awk -v init=2 -v end=8 -f swap_line.awk input
1
3
4
5
2
问题
我怎样才能更好地制作脚本?因为,我不喜欢使用 memory
变量,因为对于大文件可能会有问题,例如,如果输入文件是 1000 万行并且想要在第 1 行和第 1000 万行之间进行交换,我存储memory
变量
@JoseRicardoBustosM。如果不保存从 init 到内存中结束行之前的行,就不可能在 awk 中一次完成。试想一下不可能在您已经阅读的内容之前获得一行 N 行以奇迹般地出现在当前行的位置。最好的解决方案绝对是一种简单的 2 遍方法,即在第 1 遍中保存线条并在第 2 遍中使用它们。我包括所有涉及提前 grep 或在“2”遍方法桶中使用 getline 循环的解决方案。
FWIW 这是我真正会做的方式(这是一个 2-pass 方法):
$ cat swap_line.awk
BEGIN { ARGV[ARGC]=ARGV[ARGC-1]; ARGC++ }
NR==FNR { if (NR==end) tl=[=10=]; next }
FNR==init { hd=[=10=]; [=10=]=tl; nr=NR-FNR; if (nr<end) next }
FNR==end { [=10=]=hd }
FNR==nr { if (nr<end) [=10=] = [=10=] ORS hd }
{ print }
.
$ awk -v init=2 -v end=4 -f swap_line.awk input
1
4
3
2
5
$ awk -v init=2 -v end=2 -f swap_line.awk input
1
2
3
4
5
$ awk -v init=2 -v end=8 -f swap_line.awk input
1
3
4
5
2
请注意,如果您对如何处理超过文件末尾的 "end" 没有非常具体的要求,那么解决方案就是:
$ cat swap_line.awk
BEGIN { ARGV[ARGC]=ARGV[ARGC-1]; ARGC++ }
NR==FNR { if (NR==end) tl=[=12=]; next }
FNR==init { hd=[=12=]; [=12=]=tl }
FNR==end { [=12=]=hd }
{ print }
如果你真的想考虑一些事情(同样,只是为了晴天的情况):
$ cat swap_line.awk
NR==init { hd=[=13=]; while ((getline<FILENAME)>0 && ++c<end); }
NR==end { [=13=]=hd }
{ print }
$ awk -v init=2 -v end=4 -f swap_line.awk input
1
4
3
2
5
我仍然认为最后一个是“2”遍方法,如果我没有完全理解 http://awk.info/?tip/getline 中列出的所有注意事项,我就不会这样做。
我觉得你太辛苦了。这不会尝试处理极端情况(例如,如果 end 大于行数,则不会打印初始行,但这可以很容易地在 END 块中处理),因为我认为处理边缘情况会模糊不清这个想法。即,打印直到到达要换出的行,然后将数据存储在文件中,然后打印要交换的行、存储的数据和初始行,然后打印文件的其余部分:
$ cat swap.sh
#!/bin/sh
trap 'rm -f $T1' 0
T1=$(mktemp)
awk '
NR<init { print; next; }
NR==init { f = [=10=]; next; }
NR<end { print > t1; next; }
NR==end { print; system("cat "t1); print f; next; }
1
' init=${1?} end=${2?} t1=$T1
$ yes | sed 10q | nl -ba | ./swap.sh 4 8
1 y
2 y
3 y
8 y
5 y
6 y
7 y
4 y
9 y
10 y
我同意需要 2 次通过。第一步可以使用专为该任务设计的工具完成:
# $init and $end have been defined
endline=$( tail -n "+$end" file | head -n 1 )
awk -v init="$init" -v end="$end" -v endline="$endline" '
NR == init {saved = [=10=]; [=10=] = endline}
NR == end {[=10=] = saved}
{print}
' file
在函数中隐藏细节:
swap_lines () {
awk -v init="" \
-v end="" \
-v endline="$(tail -n "+" "" | head -n 1)" \
'
NR == init {saved = [=11=]; [=11=] = endline}
NR == end {[=11=] = saved}
1
' ""
}
seq 5 > file
swap_lines 2 4 file
1
4
3
2
5