【问题标题】:An efficient way to transpose a file in Bash在 Bash 中转置文件的有效方法
【发布时间】:2010-12-16 08:01:50
【问题描述】:

我有一个巨大的制表符分隔文件,格式如下

X column1 column2 column3
row1 0 1 2
row2 3 4 5
row3 6 7 8
row4 9 10 11

我想转置它以仅使用 bash 命令的有效方式(我可以编写一个十左右行的 Perl 脚本来执行此操作,但执行起来应该比本机 bash 慢职能)。所以输出应该是这样的

X row1 row2 row3 row4
column1 0 3 6 9
column2 1 4 7 10
column3 2 5 8 11

我想到了这样的解决方案

cols=`head -n 1 input | wc -w`
for (( i=1; i <= $cols; i++))
do cut -f $i input | tr $'\n' $'\t' | sed -e "s/\t$/\n/g" >> output
done

但这很慢,而且似乎不是最有效的解决方案。我在this post 中看到了 vi 的解决方案,但它仍然太慢了。有什么想法/建议/绝妙的想法吗? :-)

【问题讨论】:

  • 是什么让您认为存在比 Perl 脚本更快的 bash 脚本?这正是 Perl 擅长的问题。
  • @mark,如果它是纯 bash,它可能比将所有这些 cut/sed 等工具链接在一起更快。但是话又说回来,如果您将“bash”定义为组合工具,那么只需编写一个 awk 脚本就可以与 Perl wrt 文本处理相媲美。
  • 添加另一个,因为不了解 perl 在这里有多慢。写代码慢?执行速度慢?我真的不喜欢 perl,但它确实擅长这种任务。
  • 如果您的列/字段具有固定的大小/宽度,那么您可以使用 Python 文件搜索来避免将文件读入内存。你有固定的列/字段大小/宽度吗?
  • 任何认为 shell 脚本比 awk 或 perl 更快的人都需要阅读unix.stackexchange.com/questions/169716/…,这样他们才能理解为什么不是这样。

标签: bash parsing unix transpose


【解决方案1】:
awk '
{ 
    for (i=1; i<=NF; i++)  {
        a[NR,i] = $i
    }
}
NF>p { p = NF }
END {    
    for(j=1; j<=p; j++) {
        str=a[1,j]
        for(i=2; i<=NR; i++){
            str=str" "a[i,j];
        }
        print str
    }
}' file

输出

$ more file
0 1 2
3 4 5
6 7 8
9 10 11

$ ./shell.sh
0 3 6 9
1 4 7 10
2 5 8 11

Jonathan 针对 10000 行文件的 Perl 解决方案的性能

$ head -5 file
1 0 1 2
2 3 4 5
3 6 7 8
4 9 10 11
1 0 1 2

$  wc -l < file
10000

$ time perl test.pl file >/dev/null

real    0m0.480s
user    0m0.442s
sys     0m0.026s

$ time awk -f test.awk file >/dev/null

real    0m0.382s
user    0m0.367s
sys     0m0.011s

$ time perl test.pl file >/dev/null

real    0m0.481s
user    0m0.431s
sys     0m0.022s

$ time awk -f test.awk file >/dev/null

real    0m0.390s
user    0m0.370s
sys     0m0.010s

由 Ed Morton 编辑(@ghostdog74 如果您不同意,请随时删除)。

也许这个带有一些更明确变量名的版本将有助于回答下面的一些问题,并大致阐明脚本在做什么。它还使用制表符作为 OP 最初要求的分隔符,因此它可以处理空字段,并且巧合的是,对于这种特殊情况,它会稍微修饰输出。

$ cat tst.awk
BEGIN { FS=OFS="\t" }
{
    for (rowNr=1;rowNr<=NF;rowNr++) {
        cell[rowNr,NR] = $rowNr
    }
    maxRows = (NF > maxRows ? NF : maxRows)
    maxCols = NR
}
END {
    for (rowNr=1;rowNr<=maxRows;rowNr++) {
        for (colNr=1;colNr<=maxCols;colNr++) {
            printf "%s%s", cell[rowNr,colNr], (colNr < maxCols ? OFS : ORS)
        }
    }
}

$ awk -f tst.awk file
X       row1    row2    row3    row4
column1 0       3       6       9
column2 1       4       7       10
column3 2       5       8       11

上述解决方案适用于任何 awk(当然,旧的、损坏的 awk 除外——还有 YMMV)。

上述解决方案确实将整个文件读入内存 - 如果输入文件太大,那么您可以这样做:

$ cat tst.awk
BEGIN { FS=OFS="\t" }
{ printf "%s%s", (FNR>1 ? OFS : ""), $ARGIND }
ENDFILE {
    print ""
    if (ARGIND < NF) {
        ARGV[ARGC] = FILENAME
        ARGC++
    }
}
$ awk -f tst.awk file
X       row1    row2    row3    row4
column1 0       3       6       9
column2 1       4       7       10
column3 2       5       8       11

它几乎不使用内存,但每行上的字段数读取一次输入文件,因此它比将整个文件读入内存的版本要慢得多。它还假设每行的字段数相同,并且它使用 GNU awk 处理 ENDFILEARGIND,但任何 awk 都可以对 FNR==1END 进行测试。

【讨论】:

  • 现在还要处理行和列标签吗?
  • 好的——你是对的;您的示例数据与问题的示例数据不匹配,但您的代码在问题的示例数据上运行良好,并提供了所需的输出(给或取空白与制表符间距)。主要是我的错误。
  • 注:awk has maximum number of fields size=32767.
  • @zx8754 最大字段数仅适用于旧的非 POSIX awk。可能是令人难以置信的不幸命名为“nawk”。它不适用于 gawk 或其他现代 awk。
  • @jerinisready 那篇文章有多个错误和不好的建议,它正确之处只是手册页中明确的绝对基础知识,因此最好避免。要了解 awk,请阅读 Arnold Robbins 所著的《Effective AWK Programming, 5th Edition》一书。
【解决方案2】:

rs

rs 是一个 BSD 实用程序,它也随 macOS 一起提供,但它可以从其他平台上的包管理器中获得。它以 APL 中的 reshape 函数命名。

使用空格和制表符序列作为列分隔符:

rs -T

使用制表符作为列分隔符:

rs -c -C -T

使用逗号作为列分隔符:

rs -c, -C, -T

-c 更改输入列分隔符,-C 更改输出列分隔符。单独的 -c-C 将分隔符设置为制表符。 -T 转置行和列。

不要使用-t而不是-T,因为它会自动选择输出列数,以便输出行填充显示的宽度(默认为80个字符,但可以使用@987654335更改@)。

当使用-C 指定输出列分隔符时,会在每行的末尾添加一个额外的列分隔符,但您可以使用sed 将其删除:

$ seq 4|paste -d, - -|rs -c, -C, -T
1,3,
2,4,
$ seq 4|paste -d, - -|rs -c, -C, -T|sed s/.\$//
1,3
2,4

这对于第一行以一个或多个空列结尾的表会失败,因为列数是根据第一行的列数确定的:

$ rs -c, -C, -T<<<$'1,\n3,4'
1,3,4,

呆呆

$ seq 4|paste -d, - -|awk '{for(i=1;i<=NF;i++)a[i][NR]=$i}END{for(i in a)for(j in a[i])printf"%s"(j==NR?"\n":FS),a[i][j]}' FS=,
1,3
2,4

这使用数组的数组,它是gawk 扩展。 macOS 附带了 2007 年的 nawk 版本,它不支持数组数组。

要使用空格作为分隔符而不折叠空格和制表符序列,请使用FS='[ ]'

红宝石

$ seq 4|paste -d, - -|ruby -e'STDIN.map{|x|x.chomp.split(",",-1)}.transpose.each{|x|puts x*","}'
1,3
2,4

split-1 参数禁止在末尾丢弃空字段:

$ ruby -e'p"a,,".split(",")'
["a"]
$ ruby -e'p"a,,".split(",",-1)'
["a", "", ""]

函数形式:

$ tp(){ ruby -e's=ARGV[0];STDIN.map{|x|x.chomp.split(s==" "?/ /:s,-1)}.transpose.each{|x|puts x*s}' -- "${1-$'\t'}";}
$ seq 4|paste -d, - -|tp ,
1,3
2,4

s==" "?/ /:s 之所以在上面使用是因为当split 函数的参数是单个空格时,它会启用类似于 awk 的特殊行为,其中基于空格和制表符的连续运行来拆分字符串:

$ ruby -e'p" a  \tb ".split(/ /,-1)'
["", "a", "", "\tb", ""]
$ ruby -e'p" a  \tb ".split(" ",-1)'
["a", "b", ""]

jq

tp(){ jq -R .|jq --arg x "${1-$'\t'}" -sr 'map(./$x)|transpose|map(join($x))[]';}

jq -R . 将每个输入行打印为 JSON 字符串文字,-s (--slurp) 在将每一行解析为 JSON 后为输入行创建一个数组,-r (--raw-output) 输出内容字符串而不是 JSON 字符串文字。 / 运算符被重载以拆分字符串。

R

$ printf %s\\n 1,2 3,4|Rscript -e 'write.table(t(read.table("stdin",sep=",")),"",sep=",",quote=F,col.names=F,row.names=F)'
1,3
2,4

如果将Rscript 替换为R,它会将正在运行的代码回显到STDOUT。如果后面跟着类似head -n1 的命令,在读取整个 STDIN 之前退出,也会导致错误 ignoring SIGPIPE signal

write.table 在输出文件的参数为空字符串时打印到 STDOUT。

【讨论】:

  • 我不熟悉rs -- 感谢您的指点! (链接指向 Debian;上游似乎是 mirbsd.org/MirOS/dist/mir/rs
  • @lalebarde 至少在 OS X 自带的rs 的实现中,-c 单独将输入列分隔符设置为制表符。
  • @lalebarde,尝试使用 bash 的 ANSI-C quoting 获取制表符:$'\t'
  • 这是一个极端的例子,但是对于像TTC TTA TTC TTC TTT 这样包含很多行的非常大的文件,运行rs -c' ' -C' ' -T &lt; rows.seq &gt; cols.seq 会得到rs: no memory: Cannot allocate memory。这是一个运行 FreeBSD 11.0-RELEASE 和 32 GB 内存的系统。所以,我的猜测是rs 将所有内容都放在 RAM 中,这有利于速度,但不适用于大数据。
  • jq 在一个 766MB 的文件上使用了 21Gb 的内存。我在 40 分钟后杀死了它,没有任何输出。
【解决方案3】:

Python 解决方案:

python -c "import sys; print('\n'.join(' '.join(c) for c in zip(*(l.split() for l in sys.stdin.readlines() if l.strip()))))" < input > output

以上是基于以下几点:

import sys

for c in zip(*(l.split() for l in sys.stdin.readlines() if l.strip())):
    print(' '.join(c))

此代码确实假设每一行都有相同的列数(不执行填充)。

【讨论】:

  • 这里有一个小问题:将l.split() 替换为l.strip().split() (Python 2.7),否则输出的最后一行会被破坏。适用于任意列分隔符,如果您的分隔符存储在变量 sep 中,请使用 l.strip().split(sep)sep.join(c)
【解决方案4】:

sourceforge 上的 transpose 项目是一个类似 coreutil 的 C 程序。

gcc transpose.c -o transpose
./transpose -t input > output #works with stdin, too.

【讨论】:

  • 感谢您的链接。但是,在处理大型矩阵/文件时,它需要太多内存。
  • 它有块大小和字段大小的参数:尝试调整 -b-f 参数。
  • 默认块大小(--block 或 -b)为 10kb,默认字段大小(--fieldmax 或 -f)为 64,所以不可能。我试过了。不过感谢您的建议。
  • 适用于大小为 2 GB 的 csv。
  • 对于尺寸约为 11k x 5k 的矩阵文件,我发现 transpose.c 比 ghostdog74 的第一个 awk 解决方案快约 7 倍,内存效率提高约 5 倍。此外,我发现 ghostdog74 中的“几乎不使用内存”awk 代码无法正常工作。此外,请注意 transpose.c 程序中的 --limit 标志,它默认将输出限制为 1k x 1k 的维度。
【解决方案5】:

看看GNU datamash,它可以像datamash transpose一样使用。 未来版本还将支持交叉表(数据透视表)

以下是使用空格分隔列的方法:

datamash transpose -t ' ' < file > transposed_file

【讨论】:

    【解决方案6】:

    纯 BASH,没有额外的过程。一个很好的练习:

    declare -a array=( )                      # we build a 1-D-array
    
    read -a line < "$1"                       # read the headline
    
    COLS=${#line[@]}                          # save number of columns
    
    index=0
    while read -a line ; do
        for (( COUNTER=0; COUNTER<${#line[@]}; COUNTER++ )); do
            array[$index]=${line[$COUNTER]}
            ((index++))
        done
    done < "$1"
    
    for (( ROW = 0; ROW < COLS; ROW++ )); do
      for (( COUNTER = ROW; COUNTER < ${#array[@]}; COUNTER += COLS )); do
        printf "%s\t" ${array[$COUNTER]}
      done
      printf "\n" 
    done
    

    【讨论】:

    • 这适用于我的文件,尽管有趣的是它会打印出表格第一行的目录列表。我不知道足够多的 BASH 来找出原因。
    • @bugloaf 你的桌子角落有一个 *。
    • @bugloaf:正确引用变量应该可以防止:printf "%s\t" "${array[$COUNTER]}"
    【解决方案7】:

    GNU datamash 非常适合这个问题,只需要一行代码和可能任意大的文件大小!

    datamash -W transpose infile > outfile
    

    【讨论】:

      【解决方案8】:

      这是一个中等强度的 Perl 脚本来完成这项工作。与@ghostdog74 的awk 解决方案有很多结构上的类比。

      #!/bin/perl -w
      #
      # SO 1729824
      
      use strict;
      
      my(%data);          # main storage
      my($maxcol) = 0;
      my($rownum) = 0;
      while (<>)
      {
          my(@row) = split /\s+/;
          my($colnum) = 0;
          foreach my $val (@row)
          {
              $data{$rownum}{$colnum++} = $val;
          }
          $rownum++;
          $maxcol = $colnum if $colnum > $maxcol;
      }
      
      my $maxrow = $rownum;
      for (my $col = 0; $col < $maxcol; $col++)
      {
          for (my $row = 0; $row < $maxrow; $row++)
          {
              printf "%s%s", ($row == 0) ? "" : "\t",
                      defined $data{$row}{$col} ? $data{$row}{$col} : "";
          }
          print "\n";
      }
      

      对于样本数据大小,perl 和 awk 之间的性能差异可以忽略不计(总共 7 毫秒中的 1 毫秒)。对于更大的数据集(100x100 矩阵,每个条目 6-8 个字符),perl 的性能略优于 awk - 0.026 秒对 0.042 秒。两者都不是问题。


      MacOS X 10.5.8 上的 Perl 5.10.1(32 位)vs awk(版本 20040207,当给定“-V”时)vs gawk 3.1.7(32 位)在包含 10,000 行的文件上的代表性时序每行 5 列:

      Osiris JL: time gawk -f tr.awk xxx  > /dev/null
      
      real    0m0.367s
      user    0m0.279s
      sys 0m0.085s
      Osiris JL: time perl -f transpose.pl xxx > /dev/null
      
      real    0m0.138s
      user    0m0.128s
      sys 0m0.008s
      Osiris JL: time awk -f tr.awk xxx  > /dev/null
      
      real    0m1.891s
      user    0m0.924s
      sys 0m0.961s
      Osiris-2 JL: 
      

      请注意,在这台机器上 gawk 比 awk 快得多,但仍然比 perl 慢。显然,您的里程会有所不同。

      【讨论】:

      • 在我的系统上,gawk 的性能优于 perl。你可以在我编辑的帖子中看到我的结果
      • 结论汇总:不同的平台,不同的软件版本,不同的结果。
      【解决方案9】:

      有一个专门为此而构建的实用程序,

      GNU datamash utility

      apt install datamash  
      
      datamash transpose < yourfile
      

      取自本站,https://www.gnu.org/software/datamash/http://www.thelinuxrain.com/articles/transposing-rows-and-columns-3-methods

      【讨论】:

        【解决方案10】:

        假设你所有的行都有相同数量的字段,这个 awk 程序解决了这个问题:

        {for (f=1;f<=NF;f++) col[f] = col[f]":"$f} END {for (f=1;f<=NF;f++) print col[f]}
        

        换句话说,当您遍历行时,对于每个字段f 都会增长一个以':'分隔的字符串col[f],其中包含该字段的元素。完成所有行后,将这些字符串中的每一个打印在单独的行中。然后,您可以通过 tr ':' ' ' 管道输出来替换您想要的分隔符(例如空格)。

        例子:

        $ echo "1 2 3\n4 5 6"
        1 2 3
        4 5 6
        
        $ echo "1 2 3\n4 5 6" | awk '{for (f=1;f<=NF;f++) col[f] = col[f]":"$f} END {for (f=1;f<=NF;f++) print col[f]}' | tr ':' ' '
         1 4
         2 5
         3 6
        

        【讨论】:

          【解决方案11】:

          如果你安装了sc,你可以这样做:

          psc -r < inputfile | sc -W% - > outputfile
          

          【讨论】:

          • 请注意,这支持有限数量的行,因为sc 将其列命名为一个字符或两个字符的组合。限制为26 + 26^2 = 702
          【解决方案12】:

          我通常使用这个小awksn-p 来满足这个要求:

            awk '{for (i=1; i<=NF; i++) a[i,NR]=$i
                  max=(max<NF?NF:max)}
                  END {for (i=1; i<=max; i++)
                        {for (j=1; j<=NR; j++) 
                            printf "%s%s", a[i,j], (j==NR?RS:FS)
                        }
                  }' file
          

          这只是将所有数据加载到二维数组a[line,column] 中,然后将其打印回a[column,line],以便转置给定的输入。

          这需要跟踪初始文件具有的maximum 列数,以便将其用作要打印回来的行数。

          【讨论】:

            【解决方案13】:

            一个 hackish perl 解决方案可能是这样的。这很好,因为它不会将所有文件加载到内存中,打印中间临时文件,然后使用奇妙的粘贴

            #!/usr/bin/perl
            use warnings;
            use strict;
            
            my $counter;
            open INPUT, "<$ARGV[0]" or die ("Unable to open input file!");
            while (my $line = <INPUT>) {
                chomp $line;
                my @array = split ("\t",$line);
                open OUTPUT, ">temp$." or die ("unable to open output file!");
                print OUTPUT join ("\n",@array);
                close OUTPUT;
                $counter=$.;
            }
            close INPUT;
            
            # paste files together
            my $execute = "paste ";
            foreach (1..$counter) {
                $execute.="temp$counter ";
            }
            $execute.="> $ARGV[1]";
            system $execute;
            

            【讨论】:

            • 使用粘贴和临时文件只是额外的不必要的操作。您可以在内存本身内部进行操作,例如数组/哈希
            • 是的,但这不是意味着将所有内容都保存在内存中吗?我正在处理的文件大小约为 2-20GB。
            【解决方案14】:

            我可以看到您自己的示例的唯一改进是使用 awk,这将减少正在运行的进程数量以及它们之间通过管道传输的数据量:

            /bin/rm output 2> /dev/null
            
            cols=`head -n 1 input | wc -w` 
            for (( i=1; i <= $cols; i++))
            do
              awk '{printf ("%s%s", tab, $'$i'); tab="\t"} END {print ""}' input
            done >> output
            

            【讨论】:

              【解决方案15】:

              一些*nix 标准的单行工具,不需要临时文件。注意:OP 想要一个高效 修复(即更快),而最佳答案通常比这个答案更快。 这些单线是为那些喜欢 *nix software tools 的人准备的,无论出于何种原因。在极少数情况下(例如稀缺的 IO 和内存),这些 sn-ps 实际上可能比一些顶级答案更快。

              调用输入文件foo

              1. 如果我们知道 foo 有四列:

                for f in 1 2 3 4 ; do cut -d ' ' -f $f foo | xargs echo ; done
                
              2. 如果我们不知道 foo 有多少列:

                n=$(head -n 1 foo | wc -w)
                for f in $(seq 1 $n) ; do cut -d ' ' -f $f foo | xargs echo ; done
                

                xargs 有大小限制,因此会对长文件造成不完整的工作。大小限制取决于系统,例如:

                { timeout '.01' xargs --show-limits ; } 2>&1 | grep Max
                

                我们实际可以使用的最大命令长度:2088944

              3. tr & echo:

                for f in 1 2 3 4; do cut -d ' ' -f $f foo | tr '\n\ ' ' ; echo; done
                

                ...或者如果列数未知:

                n=$(head -n 1 foo | wc -w)
                for f in $(seq 1 $n); do 
                    cut -d ' ' -f $f foo | tr '\n' ' ' ; echo
                done
                
              4. 使用set,类似于xargs,具有类似的基于命令行大小的限制:

                for f in 1 2 3 4 ; do set - $(cut -d ' ' -f $f foo) ; echo $@ ; done
                

              【讨论】:

              • 这些都比 awk 或 perl 解决方案慢几个数量级,而且很脆弱。阅读unix.stackexchange.com/questions/169716/…
              • @EdMorton,谢谢,我回答的合格介绍,以解决您的速度问题。关于“脆弱”:当程序员知道数据对于给定技术是安全的时,不是3),也不是其他人;难道 POSIX 兼容的 shell 代码不是比 perl 更稳定的标准吗?
              • 对不起,我对 perl 很感兴趣。在这种情况下,要使用的工具是awkcutheadecho 等与 POSIX 兼容的 shell 代码并不比 awk 脚本更多——它们都是每个 UNIX 安装的标准。根本没有理由使用一组工具,这些工具组合起来需要您小心输入文件的内容和执行脚本的目录,而您可以只使用 awk 并且最终结果更快且更健壮.
              • 拜托,我不反对awk,但条件会有所不同。原因 #1:for f in cut head xargs seq awk ; do wc -c $(which $f) ; done 当存储太慢或 IO 太低时,更大的解释器会使事情变得更糟,无论它们在更理想的情况下有多好。原因 2:awk(或大多数任何语言)也比旨在做好一件事的小型实用程序更陡峭的学习曲线。当运行时间比编码工时便宜时,使用“软件工具”轻松编码可以节省资金。
              【解决方案16】:

              我使用了 fgm 的解决方案(感谢 fgm!),但需要消除每行末尾的制表符,因此修改了脚本:

              #!/bin/bash 
              declare -a array=( )                      # we build a 1-D-array
              
              read -a line < "$1"                       # read the headline
              
              COLS=${#line[@]}                          # save number of columns
              
              index=0
              while read -a line; do
                  for (( COUNTER=0; COUNTER<${#line[@]}; COUNTER++ )); do
                      array[$index]=${line[$COUNTER]}
                      ((index++))
                  done
              done < "$1"
              
              for (( ROW = 0; ROW < COLS; ROW++ )); do
                for (( COUNTER = ROW; COUNTER < ${#array[@]}; COUNTER += COLS )); do
                  printf "%s" ${array[$COUNTER]}
                  if [ $COUNTER -lt $(( ${#array[@]} - $COLS )) ]
                  then
                      printf "\t"
                  fi
                done
                printf "\n" 
              done
              

              【讨论】:

                【解决方案17】:

                我只是在寻找类似的 bash 转置,但支持填充。这是我根据 fgm 的解决方案编写的脚本,它似乎有效。如果有帮助的话……

                #!/bin/bash 
                declare -a array=( )                      # we build a 1-D-array
                declare -a ncols=( )                      # we build a 1-D-array containing number of elements of each row
                
                SEPARATOR="\t";
                PADDING="";
                MAXROWS=0;
                index=0
                indexCol=0
                while read -a line; do
                    ncols[$indexCol]=${#line[@]};
                ((indexCol++))
                if [ ${#line[@]} -gt ${MAXROWS} ]
                    then
                         MAXROWS=${#line[@]}
                    fi    
                    for (( COUNTER=0; COUNTER<${#line[@]}; COUNTER++ )); do
                        array[$index]=${line[$COUNTER]}
                        ((index++))
                
                    done
                done < "$1"
                
                for (( ROW = 0; ROW < MAXROWS; ROW++ )); do
                  COUNTER=$ROW;
                  for (( indexCol=0; indexCol < ${#ncols[@]}; indexCol++ )); do
                if [ $ROW -ge ${ncols[indexCol]} ]
                    then
                      printf $PADDING
                    else
                  printf "%s" ${array[$COUNTER]}
                fi
                if [ $((indexCol+1)) -lt ${#ncols[@]} ]
                then
                  printf $SEPARATOR
                    fi
                    COUNTER=$(( COUNTER + ncols[indexCol] ))
                  done
                  printf "\n" 
                done
                

                【讨论】:

                  【解决方案18】:

                  我正在寻找一种解决方案,用任何类型的数据(数字或数据)转置任何类型的矩阵(nxn 或 mxn)并得到以下解决方案:

                  Row2Trans=number1
                  Col2Trans=number2
                  
                  for ((i=1; $i <= Line2Trans; i++));do
                      for ((j=1; $j <=Col2Trans ; j++));do
                          awk -v var1="$i" -v var2="$j" 'BEGIN { FS = "," }  ; NR==var1 {print $((var2)) }' $ARCHIVO >> Column_$i
                      done
                  done
                  
                  paste -d',' `ls -mv Column_* | sed 's/,//g'` >> $ARCHIVO
                  

                  【讨论】:

                    【解决方案19】:

                    如果您只想从文件中获取单个(逗号分隔)行 $N 并将其转换为列:

                    head -$N file | tail -1 | tr ',' '\n'
                    

                    【讨论】:

                      【解决方案20】:

                      不是很优雅,但是这个“单行”命令很快解决了问题:

                      cols=4; for((i=1;i<=$cols;i++)); do \
                                  awk '{print $'$i'}' input | tr '\n' ' '; echo; \
                              done
                      

                      这里 cols 是列数,您可以将 4 替换为 head -n 1 input | wc -w

                      【讨论】:

                        【解决方案21】:

                        另一个awk 解决方案和有限的输入与您拥有的内存大小。

                        awk '{ for (i=1; i<=NF; i++) RtoC[i]= (RtoC[i]? RtoC[i] FS $i: $i) }
                            END{ for (i in RtoC) print RtoC[i] }' infile
                        

                        这将每个相同的字段编号位置连接在一起,并在END 中打印结果,即第一列中的第一行、第二列中的第二行等。 将输出:

                        X row1 row2 row3 row4
                        column1 0 3 6 9
                        column2 1 4 7 10
                        column3 2 5 8 11
                        

                        【讨论】:

                          【解决方案22】:
                          #!/bin/bash
                          
                          aline="$(head -n 1 file.txt)"
                          set -- $aline
                          colNum=$#
                          
                          #set -x
                          while read line; do
                            set -- $line
                            for i in $(seq $colNum); do
                              eval col$i="\"\$col$i \$$i\""
                            done
                          done < file.txt
                          
                          for i in $(seq $colNum); do
                            eval echo \${col$i}
                          done
                          

                          另一个带有seteval的版本

                          【讨论】:

                          【解决方案23】:

                          这是一个 Bash 单行,它基于简单地将每一行转换为一列并将它们paste-ing 在一起:

                          echo '' > tmp1;  \
                          cat m.txt | while read l ; \
                                      do    paste tmp1 <(echo $l | tr -s ' ' \\n) > tmp2; \
                                            cp tmp2 tmp1; \
                                      done; \
                          cat tmp1
                          

                          m.txt:

                          0 1 2
                          4 5 6
                          7 8 9
                          10 11 12
                          
                          1. 创建tmp1 文件,使其不为空。

                          2. 读取每一行并使用tr将其转换为一列

                          3. 将新列粘贴到tmp1 文件中

                          4. 将结果复制回tmp1

                          PS:我真的很想使用 io-descriptors 但无法让它们工作。

                          【讨论】:

                          【解决方案24】:

                          另一个 bash 变体

                          $ cat file 
                          XXXX    col1    col2    col3
                          row1    0       1       2
                          row2    3       4       5
                          row3    6       7       8
                          row4    9       10      11
                          

                          脚本

                          #!/bin/bash
                          
                          I=0
                          while read line; do
                              i=0
                              for item in $line; { printf -v A$I[$i] $item; ((i++)); }
                              ((I++))
                          done < file
                          indexes=$(seq 0 $i)
                          
                          for i in $indexes; {
                              J=0
                              while ((J<I)); do
                                  arr="A$J[$i]"
                                  printf "${!arr}\t"
                                  ((J++))
                              done
                              echo
                          }
                          

                          输出

                          $ ./test 
                          XXXX    row1    row2    row3    row4    
                          col1    0       3       6       9   
                          col2    1       4       7       10  
                          col3    2       5       8       11
                          

                          【讨论】:

                            【解决方案25】:

                            这是一个 Haskell 解决方案。当使用 -O2 编译时,它的运行速度比 ghostdog 的 awk 稍快,但比我机器上 Stephan 的 thinly Wrap c python 的重复“Hello world”输入行运行速度稍慢。不幸的是,据我所知,GHC 对传递命令行代码的支持并不存在,因此您必须自己将其写入文件。它会将行截断为最短行的长度。

                            transpose :: [[a]] -> [[a]]
                            transpose = foldr (zipWith (:)) (repeat [])
                            
                            main :: IO ()
                            main = interact $ unlines . map unwords . transpose . map words . lines
                            

                            【讨论】:

                              【解决方案26】:

                              将整个数组存储在内存中的 awk 解决方案

                                  awk '$0!~/^$/{    i++;
                                                split($0,arr,FS);
                                                for (j in arr) {
                                                    out[i,j]=arr[j];
                                                    if (maxr<j){ maxr=j}     # max number of output rows.
                                                }
                                          }
                                  END {
                                      maxc=i                 # max number of output columns.
                                      for     (j=1; j<=maxr; j++) {
                                          for (i=1; i<=maxc; i++) {
                                              printf( "%s:", out[i,j])
                                          }
                                          printf( "%s\n","" )
                                      }
                                  }' infile
                              

                              但我们可以“遍历”文件的次数与需要的输出行数一样多:

                              #!/bin/bash
                              maxf="$(awk '{if (mf<NF); mf=NF}; END{print mf}' infile)"
                              rowcount=maxf
                              for (( i=1; i<=rowcount; i++ )); do
                                  awk -v i="$i" -F " " '{printf("%s\t ", $i)}' infile
                                  echo
                              done
                              

                              哪个(输出行数少时比前面的代码快)。

                              【讨论】:

                                【解决方案27】:

                                使用 R 的 oneliner...

                                  cat file | Rscript -e "d <- read.table(file('stdin'), sep=' ', row.names=1, header=T); write.table(t(d), file=stdout(), quote=F, col.names=NA) "
                                

                                【讨论】:

                                  【解决方案28】:

                                  我之前使用过以下两个脚本来执行类似的操作。第一个是在 awk 中,它比在“纯”bash 中的第二个要快得多。您也许可以将其调整到您自己的应用程序中。

                                  awk '
                                  {
                                      for (i = 1; i <= NF; i++) {
                                          s[i] = s[i]?s[i] FS $i:$i
                                      }
                                  }
                                  END {
                                      for (i in s) {
                                          print s[i]
                                      }
                                  }' file.txt
                                  
                                  declare -a arr
                                  
                                  while IFS= read -r line
                                  do
                                      i=0
                                      for word in $line
                                      do
                                          [[ ${arr[$i]} ]] && arr[$i]="${arr[$i]} $word" || arr[$i]=$word
                                          ((i++))
                                      done
                                  done < file.txt
                                  
                                  for ((i=0; i < ${#arr[@]}; i++))
                                  do
                                      echo ${arr[i]}
                                  done
                                  

                                  【讨论】:

                                    【解决方案29】:

                                    简单的 4 行答案,保持可读性。

                                    col="$(head -1 file.txt | wc -w)"
                                    for i in $(seq 1 $col); do
                                        awk '{ print $'$i' }' file.txt | paste -s -d "\t"
                                    done
                                    

                                    【讨论】:

                                      【解决方案30】:

                                      我玩游戏有点晚了,但是这个怎么样:

                                      cat table.tsv | python -c "import pandas as pd, sys; pd.read_csv(sys.stdin, sep='\t').T.to_csv(sys.stdout, sep='\t')"

                                      zcat(如果已压缩)。

                                      这是假设您在您的python 版本中安装了pandas

                                      【讨论】:

                                        猜你喜欢
                                        • 1970-01-01
                                        • 1970-01-01
                                        • 1970-01-01
                                        • 1970-01-01
                                        • 2021-01-03
                                        • 1970-01-01
                                        • 1970-01-01
                                        相关资源
                                        最近更新 更多