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

139
我有一个格式如下的巨大的制表符分隔文件:
X column1 column2 column3
row1 0 1 2
row2 3 4 5
row3 6 7 8
row4 9 10 11

我希望您能用bash命令高效地进行转换(我可以编写大约10行的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编辑器的解决方案,但仍然很慢。有什么想法/建议/创意吗? :-)


13
你为什么认为会有一个比Perl脚本更快的bash脚本存在呢?这正是Perl擅长解决的问题类型。 - Mark Pim
2
@mark,如果这是纯Bash,那么它可能比将所有的cut/sed等工具链接在一起更快。但是,如果你将“Bash”定义为组合工具,则编写一个awk脚本与Perl相比在文本处理方面是可比的。 - ghostdog74
再加一个不理解为什么Perl在这里会很慢的人。是写代码慢吗?还是执行慢?我真的不喜欢Perl,但它确实擅长处理这种任务。 - Corey Porter
如果您的列/字段具有固定的大小/宽度,则可以使用Python文件查找来避免将文件读入内存。您的列/字段是否具有固定的大小/宽度? - tommy.carstensen
3
任何认为Shell脚本比awk或perl更快的人需要阅读http://unix.stackexchange.com/questions/169716/why-is-using-a-shell-loop-to-process-text-considered-bad-practice,以便了解为什么情况并非如此。请注意,Shell循环处理文本被视为不良实践。 - Ed Morton
33个回答

0

简单的四行答案,保持易读。

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

0

使用R的一行代码...

  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

如果您只想直接打印转置形式,并且确定输入文件中的列/字段数(即NF)始终保持一致,则可以一次性完成所有操作:

 {m,g}awk '
  END {  _____ = ", "
            __ = gsub("\n", "&")
          ____ = NF
      
      for(___ ^= _<_; ___<=__; ___++) {
         for(_ = ___; _<=____; _+=__) {
              
         printf("%s%s",$_, ____<(_+__)\
                       ? "\n" : _____) }  } }' FS='[[:space:]]+' RS=

|

0, 3, 6, 9
1, 4, 7, 10
2, 5, 8, 11

关于一次性使用过多字段导致系统资源超载的问题,我已经使用非默认的OFS,在10.5秒内让mawk2分配了13.31亿个字段:
( date | mawk2 '$!NF=NF=(191)^+(OFS="4")'; )  

 7.82s user 2.30s system 96% cpu 10.499 total
     1  1,330,863,361

相比之下,mawk 1.3.4 只落后了一点,达到了 11.03 秒

( date | mawk '$!NF=NF=(191)^+(OFS="4")'; )  

8.37s user 2.28s system 96% cpu 11.031 total
     1  1,330,863,361

...但是惊人的gawk 5.1.1花费了6倍的时间来分配仅有39%的数量:

( date | LC_ALL=C gawk -be '$-_=NF=(151)^+(OFS="4")'; )  

14.59s user 34.98s system 77% cpu 1:04.38 total

     1  519,885,601

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接