当前位置:  开发笔记 > 编程语言 > 正文

一种在Bash中转置文件的有效方法

如何解决《一种在Bash中转置文件的有效方法》经验,为你挑选了10个好方法。

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

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

但它很慢,似乎不是最有效的解决方案.我在这篇文章中看到了vi的解决方案,但它仍然过慢.有什么想法/建议/精彩的想法吗?:-)



1> ghostdog74..:
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 ENDFILE,ARGIND但是任何awk都可以对on FNR==1和test进行相同的操作END.



2> nisetama..:

另一种选择是使用rs:

rs -c' ' -C' ' -T

-c更改输入列分隔符,-C更改输出列分隔符,并-T转置行和列.不要使用,-t而是-T使用自动计算的行数和列数通常不正确.rs,以APL中的reshape函数命名,附带BSD和OS X,但应该可以从其他平台上的包管理器获得.

第二种选择是使用Ruby:

ruby -e'puts readlines.map(&:split).transpose.map{|x|x*" "}'

第三种选择是使用jq:

jq -R .|jq -sr 'map(./" ")|transpose|map(join(" "))[]'

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


这是一个极端的情况,但是对于像`TTC TTA TTC TTC TTT`这样的很多行的非常大的文件,运行`rs -c''-C''-T cols.seq`给出了`rs:没有内存:无法分配内存`.这是一个运行FreeBSD 11.0-RELEASE的系统,内存为32 GB.因此,我的猜测是`rs`将所有内容放入RAM中,这对速度有利,但对于大数据则不行.
我不熟悉[`rs`](https://packages.debian.org/jessie/rs) - 感谢指针!(链接到Debian;上游似乎是https://www.mirbsd.org/MirOS/dist/mir/rs/)
@lalebarde至少在OS X附带的`rs`的实现中,`-c`单独将输入列分隔符设置为选项卡.
@lalebarde,尝试使用bash的[ANSI-C引用](https://www.gnu.org/software/bash/manual/bashref.html#ANSI_002dC-Quoting)获取制表符:`$'\ t'`

3> Stephan202..:

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.strip().split()`(Python 2.7)替换`l.split()`,否则输出的最后一行会被削弱.适用于任意列分隔符,如果您的分隔符存储在变量`sep`中,请使用`l.strip().split(sep)`和`sep.join(c)`.

4> flying sheep..:

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

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


对于尺寸大约为11k×5k的矩阵文件,我发现transpose.c比ghostdog74的第一个awk解决方案快〜7倍,内存效率高5倍.另外,我发现ghostdog74的"几乎没有内存使用"awk代码无法正常工作.另外,请注意transpose.c程序中的--limit标志,默认情况下将输出限制为1k维1k.

5> Fritz G. Meh..:

纯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


@bugloaf:正确引用变量应该防止:`printf"%s\t""$ {array [$ COUNTER]}"`

6> pixelbeat..:

看看可以使用的GNU datamashdatamash transpose.未来版本还将支持交叉制表(数据透视表)


这有效:`datamash -t''transpose
7> Jonathan Lef..:

这是一个适度的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.026s vs 0.042s.两者都不是一个问题.


Perl 5.10.1(32位)与awk(版本20040207,给定'-V')的代表性时序对比MacOS X 10.5.8上的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慢.显然,您的里程会有所不同.


结论聚集:不同的平台,不同的软件版本,不同的结果.

8> Paused until..:

如果已sc安装,则可以执行以下操作:

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


请注意,这支持有限数量的行,因为`sc`将其列命名为一个或两个字符的组合.限制为"26 + 26 ^ 2 = 702".

9> nelaaro..:

有一个专用的实用程序,

GNU datamash实用程序

apt install datamash  

datamash transpose < yourfile

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



10> Guilherme Fr..:

假设你的所有行都有相同数量的字段,这个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

推荐阅读
雨天是最美
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有