如何与awk交换线只有一次通过和有限的内存使用?

时间:2015-05-04 15:03:34

标签: awk swap gawk

在之前的帖子中,显示了这个答案:answer user2138595,虽然很漂亮,但问题是你应该两次读取输入文件。

我希望制作一个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 $0;
    }else{
      memory = memory""$0"\n";
    }
  }else if(end == init){
    print $0;
  }else if(NR == init){
    flag = 0;
    memory_init = $0;
  }else{
    #NR == end
    print $0;
    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行

3 个答案:

答案 0 :(得分:2)

@JoseRicardoBustosM。如果不将init中的行保存到内存中的结束行之前,则无法在awk的一次传递中执行此操作。试想一下,在你已经阅读过的奇迹般地出现代替当前行的情况下,不可能获得N行。对此最好的解决方案绝对是一个简单的2遍方法,即在第一遍中保存线并在第二轮中使用它们。我将所有涉及grep-ing的解决方案或在" 2" -pass方法桶中使用getline循环包括在内。

FWIW就像我真正做到的那样(这是一种2遍方式):

$ cat swap_line.awk
BEGIN     { ARGV[ARGC]=ARGV[ARGC-1]; ARGC++ }
NR==FNR   { if (NR==end) tl=$0; next }
FNR==init { hd=$0; $0=tl; nr=NR-FNR; if (nr<end) next }
FNR==end  { $0=hd }
FNR==nr   { if (nr<end) $0 = $0 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

请注意,如果您对如何处理&#34; end&#34;没有特别要求。超过文件末尾的解决方案就是:

$ cat swap_line.awk
BEGIN     { ARGV[ARGC]=ARGV[ARGC-1]; ARGC++ }
NR==FNR   { if (NR==end) tl=$0; next }
FNR==init { hd=$0; $0=tl }
FNR==end  { $0=hd }
{ print }

如果你真的想要考虑一下(再次,只是为了阳光灿烂的日子):

$ cat swap_line.awk
NR==init { hd=$0; while ((getline<FILENAME)>0 && ++c<end); }
NR==end  { $0=hd }
{ print }

$ awk -v init=2 -v end=4 -f swap_line.awk input
1
4
3
2
5

我仍然认为最后一个作为&#34; 2&#34; -pass方法,如果我没有完全理解{{3}中列出的所有警告,我就不会这样做}。

答案 1 :(得分:2)

我认为你工作太辛苦了。这没有尝试处理极端情况(例如,如果end大于行数,则不会打印初始行,但可以在END块中轻松处理),因为我认为处理边缘情况模糊不清这个想法。即,打印直到到达要换出的行,然后将数据存储在文件中,然后打印行交换,存储的数据和初始行,然后打印文件的其余部分:

$ cat swap.sh
#!/bin/sh


trap 'rm -f $T1' 0
T1=$(mktemp)

awk '
        NR<init { print; next; }
        NR==init { f = $0; 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 :(得分:2)

我同意需要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 = $0; $0 = endline} 
    NR == end {$0 = saved} 
    {print}
' file

在函数中隐藏细节:

swap_lines () { 
    awk -v init="$1" \
        -v end="$2" \
        -v endline="$(tail -n "+$2" "$3" | head -n 1)" \
    '
        NR == init {saved = $0; $0 = endline}
        NR == end {$0 = saved}
        1
    ' "$3"
}
seq 5 > file
swap_lines 2 4 file
1
4
3
2
5