【问题标题】:Which AWK program can do this manipulation?哪个 AWK 程序可以进行这种操作?
【发布时间】:2021-01-06 15:52:29
【问题描述】:

给定一个包含如下结构的文件(字段由 SP 或 HT 分隔)

4 5 6 2 9 8 4 8
m d 6 7 9 5 4 g
t 7 4 2 4 2 5 3
h 5 6 2 5 s 3 4
r 5 7 1 2 2 4 1
4 1 9 0 5 6 d f
x c a 2 3 4 5 9
0 0 3 2 1 4 q w

我需要哪个 AWK 程序才能获得以下输出?

  4 5
  m d
  t 7
  h 5
  r 5
  4 1
  x c
  0 0
  6 2
  6 7
  4 2
  6 2
  7 1
  9 0
  a 2
  3 2
  9 8
  9 5
  4 2
  5 s
  2 2
  5 6
  3 4
  1 4
  4 8
  4 g
  5 3
  3 4
  4 1
  d f
  5 9
  q w

提前感谢所有帮助。

后记

请记住,

  1. 我的输入文件比这个问题中描述的要大得多。

  2. 我的计算机科学技能严重受限。

  3. 这个任务已经交给我了。

【问题讨论】:

  • 欢迎来到 SO,非常鼓励原始发帖人在他们的问题中添加他们的努力(无论对错),所以请在您的问题中添加相同的内容,谢谢.
  • 什么是字段分隔符 - TAB 或空白?有多少列:8 列或更多?
  • 列数是预先知道的吗?
  • @TimurShtatland 有时字段分隔符都是制表符(HT),而在其他一些文件中,字段分隔符都是空白(SP)并且有超过 100 万列,但行总是 8。另外我我还希望能够以 4 或 8 或 16 或 32 或 64 等为一组选择输出,在示例中不一定是 2 组。
  • @RavinderSingh13 我第一次接触 AWK 是在昨天,几个小时前我刚刚下载了“Effective awk Programming: Universal Text Processing and Pattern Matching by Arnold Robbins”……我是传热传质的学校的实验室讲师......我真的需要这个解决方案,当然我会在努力解决我的新书时进行尝试

标签: awk


【解决方案1】:
awk -v n=4 '
    function join(start, end,    result, i) {
        for (i=start; i<=end; i++)
            result = result $i (i==end ? ORS : FS)
        return result
    }
    {
        c=0
        for (i=1; i<NF; i+=n) {
            c++
            col[c] = col[c] join(i, i+n-1)
        }
    }
    END {
        for (i=1; i<=c; i++)
            printf "%s", col[i]  # the value already ends with newline
    }
' file

info page 有一个关于 awk 的简短入门,所以也请阅读。


基准测试

  1. 创建一个包含 1,000,000 列和 8 行的输入文件(由 OP 指定)

    #!perl
    my $cols = 2**20; # 1,048,576
    my $rows = 8;
    my @alphabet=( 'a'..'z', 0..9 );
    my $size = scalar @alphabet;
    
    for ($r=1; $r <= $rows; $r++) {
        for ($c = 1; $c <= $cols; $c++) {
            my $idx = int rand $size;
            printf "%s ", $alphabet[$idx];
        }
        printf "\n";
    }
    
    $ perl createfile.pl > input.file
    $ wc input.file
           8  8388608 16777224 input.file
    
  2. time各种实现:我用shell,所以定时输出和bash的不一样

    • 我的 awk

      $ time awk -f columnize.awk -v n=4 input.file > output.file
      
      ________________________________________________________
      Executed in    3.62 secs   fish           external
         usr time    3.49 secs    0.24 millis    3.49 secs
         sys time    0.11 secs    1.96 millis    0.11 secs
      
      $ wc output.file
       2097152  8388608 16777216 output.file
      
    • 帖木儿的 perl:

      $ time perl -lan columnize.pl input.file > output.file
      
      ________________________________________________________
      Executed in    3.25 secs   fish           external
         usr time    2.97 secs    0.16 millis    2.97 secs
         sys time    0.27 secs    2.87 millis    0.27 secs
      
    • Ravinder 的 awk

      $ time awk -f columnize.ravinder input.file > output.file
      
      ________________________________________________________
      Executed in    4.01 secs   fish           external
         usr time    3.84 secs    0.18 millis    3.84 secs
         sys time    0.15 secs    3.75 millis    0.14 secs
      
    • kvantour 的 awk,第一个版本

      $ time awk -f columnize.kvantour -v n=4 input.file > output.file
      
      ________________________________________________________
      Executed in    3.84 secs   fish           external
         usr time    3.71 secs  166.00 micros    3.71 secs
         sys time    0.11 secs  1326.00 micros    0.11 secs
      
    • kvantour 的第二个 awk 版本:Crtl-C 几分钟后中断

      $ time awk -f columnize.kvantour2 -v n=4 input.file > output.file
      ^C
      ________________________________________________________
      Executed in  260.80 secs   fish           external
         usr time  257.39 secs    0.13 millis  257.39 secs
         sys time    1.68 secs    2.72 millis    1.67 secs
      
      $ wc output.file
       9728 38912 77824 output.file
      

      $0=a[j] 行非常昂贵,因为它每次都必须将字符串解析为字段。

    • dawg 的蟒蛇

      $ timeout 60s fish -c 'time python3 columnize.py input.file 4 > output.file'
      [... 60 seconds later ...]
      $ wc output.file
       2049  8196 16392 output.file
      
  3. 另一个有趣的数据点:使用不同的 awk 实现。我在通过自制软件安装了 GNU awk 和 mawk 的 Mac 上

    • 列多,行少

      $ time gawk -f columnize.awk -v n=4 input.file > output.file
      
      ________________________________________________________
      Executed in    3.78 secs   fish           external
         usr time    3.62 secs  174.00 micros    3.62 secs
         sys time    0.13 secs  1259.00 micros    0.13 secs
      
      $ time /usr/bin/awk -f columnize.awk -v n=4 input.file > output.file
      
      ________________________________________________________
      Executed in   17.73 secs   fish           external
         usr time   14.95 secs    0.20 millis   14.95 secs
         sys time    2.72 secs    3.45 millis    2.71 secs
      
      $ time mawk -f columnize.awk -v n=4 input.file > output.file
      
      ________________________________________________________
      Executed in    2.01 secs   fish           external
         usr time  1892.31 millis    0.11 millis  1892.21 millis
         sys time   95.14 millis    2.17 millis   92.97 millis
      
    • 多行少列,此测试在 MacBook Pro、6 核 Intel cpu、16GB 内存上花费了半个多小时

      $ time mawk -f columnize.awk -v n=4 input.file > output.file
      
      ________________________________________________________
      Executed in   32.30 mins   fish           external
         usr time   23.58 mins    0.15 millis   23.58 mins
         sys time    8.63 mins    2.52 millis    8.63 mins
      

【讨论】:

  • 好奇:请用数百万行和 8 列重试您的基准测试?
  • 欢迎你这样做,但这不是OP的场景。
  • 问题在于日期的形状。数百万 rows x 8 与数百万 columns x 8。如果您有数百万 rows,这些 awk(kvantour 的第二个除外)非常棒慢的。如果你有数百万个,那么写的 python 就超级慢。
  • @dawg,我确实在输入文件转置的情况下尝试了几次运行(几列多行),我可以确认您的观察结果:python 很快,我的 awk 很慢
【解决方案2】:

使用这个 Perl 脚本:

perl -lane '
push @rows, [@F];
END {
    my $delim = "\t";
    my $cols_per_group = 2;
    my $col_start = 0;
    while ( 1 ) {
         for my $row ( @rows ) {
             print join $delim, @{$row}[ $col_start .. ($col_start + $cols_per_group - 1) ];
         }
         $col_start += $cols_per_group;
         last if ($col_start + $cols_per_group - 1) > $#F;
    } 
}
' in_file > out_file

Perl 单行程序使用这些命令行标志:
-e:告诉 Perl 查找内联代码,而不是在文件中。
-n:循环输入一行一次,默认将其分配给$_
-l:在执行内联代码之前剥离输入行分隔符(默认为 *NIX 上的"\n"),并在打印时附加它。-a : 在空格或-F 选项中指定的正则表达式上将$_ 拆分为数组@F

此脚本将文件读入内存。这对于大多数现代计算机和相关文件大小来说都可以。

每一行在空白处被分割(使用-F'\t' 用于TAB 作为分隔符)到数组@F 中。每行对该数组的引用作为元素存储在数组@rows 中。读取文件后,脚本结束(在END { ... } 块中),文件的内容以列组的形式打印,每组有$cols_per_group 列。列由$delim 分隔。

另请参阅:
perldoc perlrun: how to execute the Perl interpreter: command line switches

【讨论】:

  • @two_big_a_pac 根据您的 cmets 更新了答案。
  • 即使在较大的文件上也很快。不错!
【解决方案3】:

您能否尝试使用仅在 GNU awk 中显示的示例进行跟踪、编写和测试。

awk '
{
  for(i=1;i<=NF;i+=2){
    arr[i]=(arr[i]?arr[i] ORS :"")$i OFS $(i+1)
  }
}
END{
  for(i=1;i<=NF;i+=2){
    print arr[i]
  }
}' Input_file

【讨论】:

  • 当心:对于较大的文件,这会变得超级慢。尝试制作示例 12M 并尝试此脚本。
  • @dawg,感谢您在这里的警告,我没有用大文件测试它,这就是为什么我只用显示的示例编写检查,如果以后可以尝试添加另一个解决方案,谢谢.
  • 重读 cmets,我的警告可能为时过早。 awk 在处理数百万行的数据时变得超级慢,但他说他有数百万列并且有 8 行。不同的分析。
【解决方案4】:

既然我们都喜欢 awk,这里还有一个:

awk -v n=2 '{for(i=1;i<=NF;++i) { j=int((i-1)/n); a[j] = a[j] $i (i%n==0 || i==NF ?ORS:OFS) }}
            END{for(j=0;j<=int(NF/n);j++) printf "%s", a[j]}'

这将准确输出 OP 请求的内容。

它是如何工作的?

Awk 对它读取的每条记录/行执行操作。对于每条记录,它处理所有字段并将它们附加到存储在数组a 中的一组字符串中。它以a[1] 包含第一个n 列的方式处理它。 a[2] 第二组n 列等。字段号和字符串号的关系基于公式int((i-1)/n)

创建字符串时,我们会尝试跟踪是否需要添加字段分隔符OFS 或新行(记录分隔符ORS)。我们根据字段编号的模数和我们期望的总列数(即n)来决定这一点。请注意,如果我们处理最后一个字段,我们总是使用 ORS。

另一种方法: 多亏了commentDawg,才发现上面代码有缺陷。他告诉我们,当移动到大文件时,程序的扩展性非常差。造成这种情况的真正原因不是 100% 已知的,但我认为这是由于不断地不得不通过像 a[j] = a[j] $i (i%n==0 || i==NF ?ORS:OFS) 那样执行操作来重写内存。这可以通过仅缓冲整个文件并在最后执行所有操作来消除:

awk -v n=2 '{a[NR]=$0}
            END{ for(i=1;i<=NF;i+=n)
                   for(j=1;j<=NR;++j) {
                      $0=a[j]
                      for(k=0;k<n&&(i+k<=NF);++k)
                         printf "%s%s", $(i+k), ((i+k==NF || ((i+k) % n == 0)) ? ORS : OFS) 
                   }
            }' file

注意:后者似乎只对少量列有效。这是因为使用$0=a[j] 进行的不断重新拆分。由于大量字段,拆分需要更多时间。这个系统的复杂度是O(NF^2*NR)

最后一种替代方法:虽然第一种解决方案对于大量列和少量行来说很快,但第二种解决方案对于少量列和大量行来说很快。您可以在下面找到一个速度不快但稳定且为转置文件生成相同时序的最终版本。

awk -v n=2 '{ for(i=1;i<=NF;i+=n) {
                s=""
                for(k=0;k<n&&(i+k<=NF);++k) 
                   s=s  $(i+k)  ((i+k==NF || ((i+k) % n == 0)) ? "ORS" : OFS);
                a[i,NR]=s 
             }
            }
            END{for(i=1;i<=NF;i+=n)for(j=1;j<=NR;++j) printf "%s",a[i,j]}' file

【讨论】:

  • 非常感谢,这正是我正在寻找的输出形式。我已经在 bash 中使用相同的示例对其进行了测试,我希望明天早上首先在办公室使用真实的数据集再次对其进行测试。
  • @two_big_a_pac 很高兴听到。如果这些答案中的任何一个对您有所帮助,请随时接受。我会选择你最容易理解的答案。
  • 当心:对于较大的文件,这会变得超级慢。尝试制作示例 12M 并尝试此脚本。
  • @dawg 这是 12MB 的差评,但你的 python 更快:(python: 4.72s, awk: 5.15s)
  • 你的第二个速度明显快了!干得好!!
【解决方案5】:

我的旧答案在下面,不再适用...

您可以将此 awk 用于可能包含数百万行或数百万列的文件。基本方案是将所有值吸到一个数组中,然后在最后使用索引算法和嵌套循环来获得正确的顺序:

$ cat col.awk
{
    for (i=1; i<=NF; i++) {
        vals[++numVals] = $i
        }
    }
    END {
    for(col_offset=0; col_offset + cols <= NF; col_offset+=cols) {
        for (i=1; i<=numVals; i+=NF) {
            for(j=0; j<cols; j++) {
                printf "%s%s", vals[i+j+col_offset], (j<cols-1 ? FS : ORS)
                }
        } 
    }
}

$ awk -f col.awk -v cols=2 file
4 5
m d
t 7
h 5
...
3 4
4 1
d f
5 9
q w

我的旧答案是基于在大多数具有大量行的 awk 中看到的显着减速。

有关减速的更多讨论,请参阅this question

以下原始答案中的 cmets 不再适用。


老答案

这里只是为了保持一致性...

这里的awk 解决方案对小文件都很好。它们的共同点是文件要么需要适合 RAM,要么如果文件不适合 RAM,操作系统虚拟内存是可接受的后备。但是对于较大的文件,由于awk 的时间呈指数增长,您可能会得到非常糟糕的结果。对于您的文件的12MB 版本,内存中的 awk 变得非常慢.

如果有数百万个而不是数百万个,就会出现这种情况。

内存解决方案的唯一替代方法是多次读取文件或自己管理临时文件。 (或者使用在内部管理 VM 的脚本语言,例如 Perl 或 Python……Timur Shatland 的 Perl 即使处理大文件也很快。)

awk 没有一个简单的机制来多次循环文件直到一个过程完成。您需要使用 shell 来执行此操作并多次调用 awk

这是一个python 脚本,它逐行读取文件并打印cols 列数,直到打印完所有原始列

$ cat sys.py
import sys
filename=sys.argv[1]
cols=int(sys.argv[2])
offset=0
delimiter=" "

with open(filename, "r") as f:
    max_cols=len(f.readline().split())

while offset<max_cols:
    with open(filename, "r") as f:  
        for line in f:
            col_li=line.rstrip().split()
            l=len(col_li)
            max_cols=l if max_cols>l else l
            print(delimiter.join(col_li[offset:offset+cols]))

        offset+=cols

这有悖常理,但多次读取文件通常比吞下整个文件要快得多且效率更高——如果吞下会导致数据更大的错误结果。

那么与本文中的awks 之一相比,它的表现如何?让我们计时吧。

根据您的示例,内存中的 awk 可能会更快:

$ cat file
4 5 6 2 9 8 4 8
m d 6 7 9 5 4 g
t 7 4 2 4 2 5 3
h 5 6 2 5 s 3 4
r 5 7 1 2 2 4 1
4 1 9 0 5 6 d f
x c a 2 3 4 5 9
0 0 3 2 1 4 q w

$ time python pys.py file 2 >file2
real    0m0.027s
user    0m0.009s
sys 0m0.016s

$ time awk -v n=2 '{for(i=1;i<=NF;++i) { 
  j=int((i-1)/n); a[j] = a[j] $i (i%n==0 || i==NF ?ORS:OFS) }}
  END{for(j=0;j<=int(NF/n);j++) printf "%s", a[j]}' file >file3
real    0m0.009s
user    0m0.003s
sys 0m0.003s

这是真的。但是,让我们用这个 Python 脚本将文件放大 1000 倍:

txt='''\
4 5 6 2 9 8 4 8
m d 6 7 9 5 4 g
t 7 4 2 4 2 5 3
h 5 6 2 5 s 3 4
r 5 7 1 2 2 4 1
4 1 9 0 5 6 d f
x c a 2 3 4 5 9
0 0 3 2 1 4 q w
'''

with open('/tmp/file', 'w') as f:
    f.write(txt*1000)              # change the 1000 to the multiple desired

# file will have 8000 lines and about 125KB

以同样的方式重新运行这些时间,你会得到:

#python
real    0m0.061s
user    0m0.044s
sys 0m0.015s

# awk
real    0m0.050s
user    0m0.043s
sys 0m0.004s

大约在同一时间...现在通过将原始文件乘以 100,000 得到 800,000 行和 12MB 并再次运行计时来使文件更大:

# python
real    0m3.475s
user    0m3.434s
sys 0m0.038s

#awk
real    22m45.118s
user    16m40.221s
sys 6m4.652s

对于 12MB 的文件,内存中的方法基本上无法使用,因为这台计算机上的 VM 系统需要进行大量磁盘交换来管理特定类型的内存分配。可能是O n**2 或更糟。这台电脑是2019 Mac Pro 16核Xeon 192GB内存,所以不是硬件...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多