【问题标题】:Group every nth line command output in a CSV format以 CSV 格式对每第 n 行命令输出进行分组
【发布时间】:2017-01-27 02:45:57
【问题描述】:

正在寻找一种解析多行命令输出的方法;将nth 行的每个倍数分组并以 CSV 格式格式化。

$ cat file
ABC
123
62p4-123
DEF
456
62p4-456

我需要将 o/p 分组在每个 3rd 行,并以 3 的倍数分组,即第 3、第 6 和第 9 次,然后分别以逗号分隔格式分别为第 1、第 4 和第 7 次和第 2、第 5 和第 8 次

ABC,DEF
123,456
62p4-123,62p4-456

这只是一个示例格式,我的实际用例是json o/p,我想使用bash 中的工具/实用程序对其进行格式化,并且我不需要在jq 中使用格式化选项用于解析数据。

我在awk 中找到了几种方法,例如awk 'NR % 3 == 0',但我无法在其他行中重复此操作。

编辑:- 正在更新我从jq 返回的实际 JSON o/p 以获得最有效的解决方案

4496
http://xxx/yyy
/home/build/branches/mmm/file1
4497
http://xxx/yyy/zzz
/home/build/branches/mmm/file1
4498
http://xxx/yyy/zzz
/home/build/branches/mmm/otherfile.c

预期的 o/p

4496,4497,4498
http://xxx/yyy,http://xxx/yyy/zzz,http://xxx/yyy/zzz
/home/build/branches/mmm/file1,/home/build/branches/mmm/file1,home/build/branches/mmm/otherfile.c

【问题讨论】:

  • 更多行你期望什么输出? (请显示一个 9 或 12 行的输入)。
  • @gniourf_gniourf:更新!
  • 我明白了。不过,我不确定这是否可以容忍,因为我们显然有重复! :).
  • 好吧,如果我将答案粘贴为评论,然后删除这篇文章,这有意义吗?
  • 好吧,现在情况很奇怪:您的问题很好(提出得当等),而另一个问题则没有那么好(但是,可以通过温和的编辑来挽救)。我不知道该怎么办……等着看:D

标签: bash csv awk sed


【解决方案1】:

这里有一个更简单的方法

$ pr -2ts, file

ABC,DEF
123,456
62p4-123,62p4-456

对于其他输入,将列数更改为 3。

$ pr -3ts, file

4496,4497,4498
http://xxx/yyy,http://xxx/yyy/zzz,http://xxx/yyy/zzz
/home/build/branches/mmm/file1,/home/build/branches/mmm/file1,/home/build/branches/mmm/otherfile.c

如果您只知道行数,而不知道最终的列数,您可以这样做

$ pr -$(awk 'END{print NR/3}' file) -ts, file

【讨论】:

  • pr 看起来很简单!谢谢。但不幸的是不能使用它,因为它不属于GNU coreutils
  • @gniourf_gniourf:没有在我的 Mac 上测试它,单独安装了 GNU coreutils,因为pr --version 没有提到它是 coreutils 的一部分,所以没有费心尝试它。实际上,它适用于我的原生 FreeBSD 版本
  • 注意,需要提前知道行数才能将列数给pr。这意味着该文件必须被读取两次。
  • @karakfa 您添加的 pr+awk 解决方案将失败并出现隐晦的错误消息,因为输入文件为空,或者输入文件的行数不能被您想要的输出列数整除.你需要像pr -$(awk 'END{print (NR ? int((NR+2)/3) : 1)}' file) -ts, file 这样的东西来解决这两个问题,但我不确定是否能做到。
【解决方案2】:
$ awk '{k=((NR-1)%3)+1} {a[k]=(k in a ? a[k] "," : "") $0} END{for (i=1;i<=3;i++) print a[i]}' file
ABC,DEF
123,456
62p4-123,62p4-456

$ awk '{k=((NR-1)%3)+1} {a[k]=(k in a ? a[k] "," : "") $0} END{for (i=1;i<=3;i++) print a[i]}' file
4496,4497,4498
http://xxx/yyy,http://xxx/yyy/zzz,http://xxx/yyy/zzz
/home/build/branches/mmm/file1,/home/build/branches/mmm/file1,/home/build/branches/mmm/otherfile.c

【讨论】:

  • 没有 Ed 回答的 awk 解决方案? :) 没门!答案看起来与下面 anubhava 的答案中的逻辑相同。老实说,我期待您提供比这更简单的解决方案 :)
  • 这很简单。这和@anubhava 的答案之间的区别是我的具有创建然后使用从 1 到 n 的索引(即k=((NR-1)%n)+1))的惯用方式,因此在填充数组。
【解决方案3】:

一种普通的纯 Bash (≥4) 方法,使用 mapfile 读取内存中的整个文件。

#!/bin/bash

n=3

mapfile -t ary < file

for ((i=0;i<n;++i)); do
    for ((j=i;j<${#ary[@]};j+=n)); do
        (( j >= n )) && printf ,
        printf '%s' "${ary[j]}"
    done
    echo
done

这不是很有效(所以不要将它用于非常大的文件)。


一种变体是在读取文件时构建要输出的行。

#!/bin/bash

n=3
ary=()
linenb=0

while IFS= read -r line; do
    ((linenb>=n)) && ary[linenb%n]+=,
    ary[linenb++%n]+=$line
done < file

printf '%s\n' "${ary[@]}"

同样,这对于非常大的文件不是很有效(但它是纯 Bash!)。

【讨论】:

    【解决方案4】:
    $ cat ip.txt 
    4496
    http://xxx/yyy
    /home/build/branches/mmm/file1
    4497
    http://xxx/yyy/zzz
    /home/build/branches/mmm/file1
    4498
    http://xxx/yyy/zzz
    /home/build/branches/mmm/otherfile.c
    
    $ perl -lne '$i = ($.-1)%3; $f[$i] .= $f[$i] ? ",$_" : $_; END{print foreach (@f)}' ip.txt 
    4496,4497,4498
    http://xxx/yyy,http://xxx/yyy/zzz,http://xxx/yyy/zzz
    /home/build/branches/mmm/file1,/home/build/branches/mmm/file1,/home/build/branches/mmm/otherfile.c
    

    这是另一种解决方案,但在性能方面效率低下

    $ (sed -n '1~3p' ip.txt ; sed -n '2~3p' ip.txt ; sed -n '3~3p' ip.txt) | pr -ats, -$(echo $(wc -l < ip.txt)/3 | bc)
    4496,4497,4498
    http://xxx/yyy,http://xxx/yyy/zzz,http://xxx/yyy/zzz
    /home/build/branches/mmm/file1,/home/build/branches/mmm/file1,/home/build/branches/mmm/otherfile.c
    

    【讨论】:

    • 感谢您的解决方案!但我prefer 使用其他 bash 工具(如awk)的解决方案。如果没有其他方法,我会接受这个答案!
    • @Inian,好的.. 对perl 更满意,但不知道如何在awk 中编写相同的逻辑.. 使用 GNU sedprbc 为你工作?
    • 只要工具是GNU coreutils的一部分。
    【解决方案5】:

    你可以使用这个 awk:

    awk -v OFS=, 'NR<4{a[NR]=$0; next} {i=(NR%3?NR%3:3); a[i] = a[i] OFS $0} 
              END{for(i=1; i<=3; i++) print a[i]}' file
    
    4496,4497,4498
    http://xxx/yyy,http://xxx/yyy/zzz,http://xxx/yyy/zzz
    /home/build/branches/mmm/file1,/home/build/branches/mmm/file1,/home/build/branches/mmm/otherfile.c
    

    【讨论】:

    • 在第 3 行之后,此 awk 正在打印 line-3,line,请检查编辑后的答案,了解我在您编辑后的问题中得到的输出。
    • 这行得通,我知道它是如何工作的! :) 如果可能的话,花一些时间寻找更简单的解决方案
    • 我真的不得不接受这个stackoverflow.com/a/39574272/5291015,考虑到它的简单性和便携性来回答。如果我能接受多个答案就好了:)
    • @Inian 答案很简单,因为它不能解决问题。它的可移植性也比您得到的任何 awk 答案都差,因为它需要 GNU 工具,而 awk 解决方案可以在任何 UNIX 安装中按原样工作,而且效率较低,因为一旦添加了其余部分,它就必须读取输入文件两次解决方案,它不太健壮,因为它会在给定一些输入文件内容的情况下神秘地失败。
    • 感谢@EdMorton,我不能把它总结得更好。使用基于pr 的解决方案,读取文件两次并在行数为 3 的倍数时将其分解是一个很大的失望。
    猜你喜欢
    • 2015-04-29
    • 2014-02-17
    • 1970-01-01
    • 1970-01-01
    • 2013-03-16
    • 1970-01-01
    • 1970-01-01
    • 2021-12-19
    • 1970-01-01
    相关资源
    最近更新 更多