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

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

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

152947 次浏览
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

EDIT by Ed Morton 编辑: 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

上面的解决方案可以在任何地方工作(除了旧的,当然是破旧的 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

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

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;

对于您自己的示例,我所能看到的唯一改进是使用 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

如果你已经安装了 sc,你可以:

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

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

这段代码确实假设每一行具有相同数量的列(不执行填充)。

这里有一个比较稳定的 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 s 和0.042 s,两者都不太可能是问题。


在 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 慢。很明显,你的情况会有所不同。

纯 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

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

源锻炉上的 换位项目就是一个类似 coreutil 的 C 程序。

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

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

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

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

这里的 ls 是列数,其中可以用 head -n 1 input | wc -w替换4。

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

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

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


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

如果只想从文件中抓取一行(逗号分隔的) $N 并将其转换为一列:

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

下面是一个 Bash 一行程序,它基于简单地将每一行转换为一列,然后将它们进行 paste组合:

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: 我真的很想使用符号描述符,但是不能让它们起作用。

使用数组数组的 Gawk 版本:

tp(){ 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?RS:FS),a[i][j]}' "${1+FS=$1}";}

使用多维数组的普通 awk 版本(在我的基准测试中,它的速度是普通 awk 版本的两倍) :

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

MacOS 提供了 Brian Kerningham 2007年的 nawk版本,它不支持数组数组。

若要使用空格作为分隔符而不折叠多个空格序列,请使用 FS='[ ]'

是的

rs是一个 BSD 实用程序,也附带了 macOS,但是它应该可以从其他平台上的包管理器获得。它是以 APL 中的重塑函数命名的。

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

rs -T

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

rs -c -C -T

使用逗号作为列分隔符:

rs -c, -C, -T

-c改变输入列分隔符,-C改变输出列分隔符。一个单独的 -c-C将分隔符设置为 tab。-T转换行和列。

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

当使用 -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 -T根据第一行的列数确定列数,因此当第一行以一个或多个空列结束时,它会产生错误的结果:

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

R

t函数转换矩阵或数据框架:

Rscript -e 'write.table(t(read.table("stdin",sep=",",quote="",comment.char="")),sep=",",quote=F,col.names=F,row.names=F)'

如果用 R -e替换 Rscript -e,那么它会回显正在运行到 STDOUT 的代码,如果 R 命令后面跟着 head -n1这样的命令,而 head -n1在读取整个 STDIN 之前退出,那么它也会导致错误 ignoring SIGPIPE signal

如果输入不包含双引号或单引号,则可以删除 quote=""; 如果输入不包含以散列字符开头的行,则可以删除 comment.char=""

对于大输入文件,data.tablefreadfwriteread.tablewrite.table快:

$ seq 1e6|awk 'ORS=NR%1e3?FS:RS'>a
$ time Rscript --no-init-file -e 'write.table(t(read.table("a")),quote=F,col.names=F,row.names=F)'>/dev/null
real  0m1.061s
user  0m0.983s
sys   0m0.074s
$ time Rscript --no-init-file -e 'write.table(t(data.table::fread("a")),quote=F,col.names=F,row.names=F)'>/dev/null


real  0m0.599s
user  0m0.535s
sys   0m0.048s
$ time Rscript --no-init-file -e 'data.table::fwrite(t(data.table::fread("a")),sep=" ",col.names=F)'>t/b
x being coerced from class: matrix to data.table


real  0m0.375s
user  0m0.296s
sys   0m0.073s

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 字符串文本。将 /运算符重载为拆分字符串。

露比

ruby -e'STDIN.map{|x|x.chomp.split(",",-1)}.transpose.each{|x|puts x*","}'

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", "b", ""]
$ ruby -e'p" a  \tb ".split(/ /,-1)'
["", "a", "", "\tb", ""]

对于这个需求,我通常使用这个小的 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]打印回来,以便对给定的输入进行转换。

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

假设所有行的字段数相同,这个 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
#!/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

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

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

datamash transpose -t ' ' < file > transposed_file

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

它(对于输出行数较少的代码,比前面的代码更快)。

一些 * nix标准的一行程序,不需要临时文件。注意: OP 需要一个 有效率修复程序(比如更快) ,而顶级答案通常比这个答案更快。不管出于什么原因,这些一行程序是为那些喜欢 * nix 软件工具的人准备的。在罕见的情况下,(例如:。稀缺的 IO 和内存) ,这些代码片段实际上可以比一些顶级答案更快。

调用输入文件

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

    for f in 1 2 3 4 ; do cut -d ' ' -f $f foo | xargs echo ; done
    
  2. If we don't know how many columns foo has:

    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. Using set, which like xargs, has similar command line size based limitations:

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

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

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

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

datamash -W transpose infile > outfile

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

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

另一个重击变种

$ 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

我以前用过下面两个脚本来做类似的操作。第一个是在 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

简单的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

我有点迟到了,不如这样吧:

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,如果它是 gzip。

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

for i in $(seq $(head -n1 file.txt | tr ' ' '\n' | wc -l))
do
cut -d' ' -f"$i" file.txt | paste -s -d' ' -
done

或者

seq $(head -n1 file.txt | tr " " "\n" | wc -l) | xargs -I{} sh -c 'cut -d" " -f"{}" file.txt | paste -s -d" " -'

如果你只是想直接打印调换后的表单,并且确定 column/field count(即 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

至于担心同时使用太多字段过载系统资源,我已经让 mawk210.5 secs中使用非默认的 OFS来分配 1.331 billion 字段:

( 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 secs

( 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