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


110

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

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的解决方案,但它仍然过慢。有什么想法/建议/好主意吗?:-)


12
是什么让您认为存在一个bash脚本要比Perl脚本快?这也正是这类问题是Perl excells英寸
马克·皮姆

1
@mark,如果纯粹是bash,它可能比将所有那些cut / sed等工具链接在一起的速度更快。但是话又说回来,如果您在组合工具中定义“ bash”,那么仅编写awk脚本就可以与Perl wrt文本处理媲美。
ghostdog74

添加另一个原因是不了解Perl在这里的运行速度。编写代码慢吗?执行慢吗?我确实不喜欢perl,但是它在这种任务上确实很出色。
科里·波特

如果您的列/字段具有固定的大小/宽度,则可以使用Python文件搜寻来避免将文件读入内存。您是否具有固定的列/字段大小/宽度?
tommy.carstensen 13-4-7

2
任何认为shell脚本比awk或perl都快的人都需要阅读unix.stackexchange.com/questions/169716/…,这样他们才能理解为什么不是这种情况。
Ed Morton

Answers:


114
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都可以对FNR==1and 进行相同的测试END


现在也可以处理行和列标签吗?
乔纳森·莱夫勒

好-你是对的;您的示例数据与问题的示例数据不匹配,但是您的代码在问题的示例数据上运行良好,并提供了所需的输出(给出或采用空白与制表符间距)。主要是我的错误。
乔纳森·莱夫勒

有趣的时机-我同意您会在awk中看到性能提升。我使用的是MacOS X 10.5.8,不使用“ gawk”;我使用的是Perl 5.10.1(32位版本)。我收集到您的数据是10000行,每行4列?无论如何,这无关紧要。awk和perl都是可行的解决方案(而awk解决方案更整洁-我的Perl中的“定义”检查对于严格/警告下的自由运行是必需的),而且都不是懈怠,而且两者都可能比原始方法快得多Shell脚本解决方案。
乔纳森·莱夫勒

在我最初的2.2GB矩阵上,perl解决方案比awk略快-350.103s与369.410s,当时我使用的是perl 5.8.8 64bit
Federico Giorgi

1
@ zx8754最大字段数仅适用于旧的非POSIX awk。可能不幸的是被命名为“ nawk”。它不适用于gawk或其他现代awks。
Ed Morton

47

另一种选择是使用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字符串文字的内容。该/操作符被重载到拆分字符串。


3
我不熟悉rs-感谢您的指导!(该链接是使用Debian;上游看来是mirbsd.org/MirOS/dist/mir/rs
tripleee

2
@lalebarde至少在rsOS X附带的实现中,-c仅将输入列分隔符设置为选项卡。
nisetama'3

2
@lalebarde,尝试使用bash的ANSI-C引用来获取制表符:$'\t'
glenn jackman

3
这是一个极端的情况,但是对于包含很多行的非常大的文件TTC TTA TTC TTC TTT,运行rs -c' ' -C' ' -T < rows.seq > cols.seqnamed rs: no memory: Cannot allocate memory。这是一个运行FreeBSD 11.0-RELEASE且系统内存为32 GB的系统。因此,我的猜测是rs将所有内容都放入RAM,这对速度有好处,但对大数据不利。
jrm

1
jq在766MB的文件上使用了21Gb的ram。40分钟后我将其杀死,但没有任何输出。
Glubbdrubb

30

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))

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


3
有一个小问题在这里:更换l.split()l.strip().split()(Python 2.7版),否则输出的最后一行是残缺的。适用于任意列分隔符,如果分隔符存储在variable中,请使用l.strip().split(sep)和。sep.join(c)sep
krlmlr 2012年

21

sourceforge上的转置项目就是一个类似于coreutil的C程序。

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

感谢您的链接。但是,在处理大型矩阵/文件时,它需要太多内存。
tommy.carstensen

它具有用于blocksize和fieldsize的参数:尝试调整-band -f参数。
飞羊

默认块大小(--block或-b)为10kb,默认字段大小(--fieldmax或-f)为64,所以不能这样。我试过了。(还是)感谢你的建议。
tommy.carstensen 2013年

1
与大小为2 GB的csv一起使用时效果很好。
徒弟

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

16

纯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 2013年

@bugloaf,您的桌子的角落有一个*。
Hello71 2014年

2
@bugloaf:正确引用变量应避免以下情况:printf "%s\t" "${array[$COUNTER]}"
已暂停,直到另行通知。

16

看一下可以像一样使用的GNU datamashdatamash transpose。将来的版本还将支持交叉列表(数据透视表)


9

这是完成此任务的适当的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和0.042s。两者都不是问题。


MacOS X 10.5.8上Perl 5.10.1(32位)vs awk(给定为-V时为20040207版本)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。您可以在我编辑过的帖子中看到我的结果
ghostdog74,2009年

4
得出的结论是:不同的平台,不同的软件版本,不同的结果。
ghostdog74

6

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

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

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


5

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

5

GNU datamash仅用一行代码和可能任意大的文件大小来非常适合此问题!

datamash -W transpose infile > outfile

3

骇人的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;

使用粘贴和临时文件只是多余的操作。您可以在内存本身内部进行操作,例如数组/哈希
ghostdog74

2
是的,但这不是意味着将所有内容都保留在内存中吗?我正在处理的文件大小约为2-20GB。
Federico Giorgi,2009年

3

我可以看到您自己的示例的唯一改进是使用了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

3

我通常使用此小awk片段来满足此要求:

  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],以便转置给定的输入。

这需要跟踪max初始文件的最大列数,以便将其用作要打印回的行数。


2

我使用了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

2

我只是在寻找类似的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

2

我一直在寻找一种解决方案,可以将任何类型的矩阵(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

2

如果只想从文件中抓取一行(用逗号分隔)$ N,然后将其变成一列:

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

2

不是很优雅,但是此“单行”命令可以快速解决问题:

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


2

另一种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

2

一些* nix标准的util一线式,不需要临时文件。注意:OP希望有一个有效的解决方案(即更快),并且最常见的答案通常比该答案快。 这些单行代码是出于各种原因而喜欢* nix 软件工具的人的。在极少数情况下(例如,稀缺的IO和内存),这些摘要实际上可能比某些最佳答案要快。

调用输入文件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. trecho

    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

2
所有这些都将比awk或perl解决方案慢几个数量级并且脆弱。阅读unix.stackexchange.com/questions/169716/…
Ed Morton

@EdMorton,谢谢,成功回答了我对您的速度问题的回答。关于“脆弱”:不是3),也不是其他,当程序员知道数据对于给定技术是安全的时候;并且POSIX兼容的shell代码不是比perl更稳定的标准吗?
agc

抱歉,我对perl非常了解。在这种情况下,使用的工具将是awkcutheadecho,等是没有比更POSIX兼容的shell代码awk的脚本是-它们都是在每一个UNIX安装标准。根本没有理由使用一组工具,这些工具组合在一起需要您在仅使用awk时就要注意输入文件的内容和执行脚本的目录,而最终结果既更快又更可靠。
Ed Morton

拜托,我不是反对awk,但条件会有所不同。原因1:for f in cut head xargs seq awk ; do wc -c $(which $f) ; done 当存储速度太慢或IO太低时,更大的解释器会使情况变得更糟,无论它们在更理想的情况下有多好。原因2:awk(或大多数语言)也比设计成能做一件事的小型utils承受着更陡峭的学习曲线。当运行时间比编码器工时便宜时,使用“软件工具”轻松编码可节省成本。
agc

1
#!/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

另一个版本 set eval


阅读unix.stackexchange.com/questions/169716/…,以了解该解决方案的部分但不是全部问题。
Ed Morton

1

另一个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

0

这是Haskell解决方案。用-O2编译时,对于重复的“ Hello world”输入行,它的运行速度比ghostdog的awk略快,并且比Stephan的薄包装c python 略慢。不幸的是,据我所知,GHC不支持传递命令行代码,因此您必须自己将其写入文件。它将把行截断为最短行的长度。

transpose :: [[a]] -> [[a]]
transpose = foldr (zipWith (:)) (repeat [])

main :: IO ()
main = interact $ unlines . map unwords . transpose . map words . lines

0

将整个数组存储在内存中的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

哪一个(对于较少的输出行,比以前的代码要快)。


0

这是一个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-descriptor,但无法使其正常工作。


如果要在大文件上执行,请确保设置闹钟。阅读unix.stackexchange.com/questions/169716/…以了解该方法的部分但不是全部问题。
Ed Morton

0

使用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) "

0

我以前在下面两个脚本中使用过类似的操作。第一个是在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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.