Bashでファイルを転置する効率的な方法


110

このようにフォーマットされた巨大なタブ区切りファイルがあります

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の解決策を見ましたが、それでもまだ時間がかかりすぎています。考え/提案/素晴らしいアイデアはありますか?:-)


12
Perlスクリプトよりも高速になるbashスクリプトが存在すると思いますか?これはPerlのexcellsでいることを正確に問題の一種である。
マーク・ピム

1
@マーク、もしその純粋なbashなら、それらのすべてのカット/セッドなどのツールを一緒にチェーンするよりも速くなるかもしれません。しかし、繰り返しになりますが、ツールを組み合わせる場合のように「bash」を定義すると、awkスクリプトを作成するだけで、Perlのwrtテキスト処理と同等になります。
ghostdog74

ここでperlがどのように遅くなるかを理解しないためにもう1つ追加します。コードの作成に時間がかかりますか?実行に時間がかかりますか?私は本当にperlが嫌いですが、この種のタスクには優れています。
Corey Porter、

列/フィールドのサイズ/幅が固定されている場合は、Pythonファイルシークを使用して、ファイルをメモリに読み込まないようにすることができます。列/フィールドのサイズ/幅は固定されていますか?
tommy.carstensen 2013

2
シェルスクリプトがawkやperlよりも高速だと考える人は誰でもunix.stackexchange.com/questions/169716/…を読んで、そうでない理由を理解できるようにする必要があります。
Ed Morton

回答:


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

10000行ファイルでのJonathanによる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

エドモートンによる編集(不承認の場合は@ 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

ほとんどメモリを使用しませんが、行のフィールド数ごとに1回入力ファイルを読み取るため、ファイル全体をメモリに読み取るバージョンよりもはるかに遅くなります。また、フィールドの数は、各ライン上で同じであると仮定し、それはのためのGNU AWKを使用ENDFILEし、ARGINDしかし、任意のawkは上のテストで同じことを行うことができますFNR==1し、END


そして今、行と列のラベルも処理しますか?
ジョナサンレフラー、

OK-あなたは正しいです。サンプルデータは質問のサンプルデータと一致しませんが、コードは質問のサンプルデータで正常に動作し、必要な出力を提供します(指定するか、空白とタブの間隔を空けます)。主に私の間違い。
ジョナサンレフラー、

興味深いタイミング-awkでパフォーマンスが向上すると思います。「gawk」を使用しないMacOS X 10.5.8を使用していました。そして、私はPerl 5.10.1(32ビットビルド)を使用していました。あなたのデータは1行あたり4列の10000行だったと思いますか?とにかく、それは大した問題ではありません。awkとperlはどちらも実行可能なソリューションであり(awkソリューションはより優れています-私のPerlの「定義済み」チェックは、strict / warningsでのフリーランの警告に必要です)どちらも前かがみで、どちらも元の方法よりも高速である可能性がありますシェルスクリプトソリューション。
ジョナサンレフラー

私の元の2.2GBマトリックスでは、perlソリューションはawkより少し速い-350.103s対369.410s対perl 5.8.8 64ビットを使用していた
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に付属していますが、他のプラットフォームのパッケージマネージャーから利用できるはずです。

2番目のオプションは、Rubyを使用することです。

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

3番目のオプションは、使用することjqです:

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

jq -R .各入力行をJSON文字列リテラルとして出力し、()各行をJSONとして解析した後に入力行の配列を作成し、-s--slurp)JSON文字列リテラルではなく文字列の内容を出力します。演算子は、文字列を分割するためにオーバーロードされます。-r--raw-output/


3
私はよく知りませんrsでした-ポインターに感謝します!(リンクはDebianです。アップストリームはmirbsd.org/MirOS/dist/mir/rsのようです
tripleee '26 / 11/15

2
@lalebarde少なくともrsOS Xに同梱されているものの実装では、-c単独で入力列セパレータをタブに設定します。
nisetama 2016年

2
@lalebarde、トライはbashの引用ANSI-Cタブ文字を取得するには:$'\t'
グレンはジャックマン

3
これは極端なケースですが、のようTTC TTA TTC TTC TTTに多くの行を含む非常に大きなファイルの場合、を実行するとrs -c' ' -C' ' -T < rows.seq > cols.seqが生成されrs: no memory: Cannot allocate memoryます。これは32 GBのRAMを備えたFreeBSD 11.0-RELEASEを実行しているシステムです。だから、私の推測でrsはすべてをRAMに置くことになります。これは速度には良いですが、大きなデータには向いていません。
jrm 2017

1
jqは766MBのファイルで21GbのRAMを使用しました。何も出力されずに、40分後に殺しました。
Glubbdrubb 2018年

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
ここでの1つの小さな問題:(Python 2.7)に置き換えl.split()てください。l.strip().split()そうしないと、出力の最後の行が機能しなくなります。任意の列セパレーターで機能します。セパレーターが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 2013

blocksizeとfieldsizeの引数が-bあり-fます。引数とを微調整してみてください。
空飛ぶ羊

デフォルトのブロックサイズ(--blockまたは-b)は10kbであり、デフォルトのフィールドサイズ(--fieldmaxまたは-f)は64であるため、それは不可能です。私は試した。しかし提案をありがとう。
tommy.carstensen 2013

1
サイズ2 GBのcsvでうまく機能しました。
弟子2016年

2
およそ11k x 5kの次元を持つマトリックスファイルの場合、transpose.cはghostdog74の最初のawkソリューションよりも7倍高速で、メモリ効率が5倍以上高いことがわかりました。また、ghostdog74からの「メモリをほとんど使用しない」awkコードが適切に機能しないこともわかりました。また、transpose.cプログラムの--limitフラグにも注意してください。これは、デフォルトで出力を1k x 1kに制限します。
ncemami 2016年

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 datamashを見てくださいdatamash 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.026秒vs 0.042秒。どちらも問題になる可能性は低いです。


Perl 5.10.1(32ビット)とawk( '-V'が指定されている場合はバージョン20040207)とgawk 3.1.7(32ビット)の比較タイミングライン:

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

4
収集された結論:異なるプラットフォーム、異なるソフトウェアバージョン、異なる結果。
ghostdog74

6

scインストールした場合は、次のことができます。

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

4
これは、scその列に1つまたは2つの文字の組み合わせとして名前を付けるため、サポートされる行数には制限があることに注意してください。制限は26 + 26^2 = 702です。
トール


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は、この問題に完全に適しています。コードは1行だけで、ファイルサイズが勝手に大きくなる可能性があります。

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 '13

2
ええ、でもそれはすべてをメモリに保持することを意味するのではないでしょうか?私が扱っているファイルのサイズは約2〜20 GBです。
フェデリコジョルジ

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

これは、すべてのデータを2次元配列にロードし、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

ファイルから1つの(カンマ区切り)行$ 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、最初の列の最初の行、2番目の列の2番目の行などの結果が出力されます。出力は次のようになります。

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

2

一部の* nix標準ユーティリティワンライナー、一時ファイルは必要ありません。注意:OPは効率的な修正(つまりより高速)を望んでおり、通常、上位の回答はこの回答よりも高速です。 これらのワンライナーは、何らかの理由で* nix ソフトウェアツールが好きな人向けです。まれに、(例えば希少IO&メモリ)は、これらのスニペットは、実際に速く、トップの回答の一部よりもすることができます。

入力ファイルfooを呼び出します。

  1. fooに4つの列があることがわかっている場合:

    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、ありがとう、あなたの速度の懸念に対処するための私の回答のqualifedイントロ 「壊れやすい」について:3)ではなく、プログラマー特定の手法に対してデータが安全であることをプログラマーが知っている場合もそうです。そして、POSIX互換シェルコードはperlよりも安定した標準ではありませんか?
agc

申し訳ありませんが、perlについてはidkを参照してください。この場合、使用するツールはになりますawkcutheadecho、などよりも多くPOSIX互換のシェルコードされているawkスクリプトはありません-彼らはすべてのすべてのUNIXインストールに標準装備されています。一連のツールを組み合わせて使用​​する理由はありません。これらのツールを組み合わせて、入力ファイルの内容と、awkを使用するだけで最終的な結果がより速く、より堅牢な場合にスクリプトを実行するディレクトリに注意する必要があります。 。
Ed Morton

私はアンチawkではありませんが、条件はさまざまです。理由#1:for f in cut head xargs seq awk ; do wc -c $(which $f) ; done ストレージが遅すぎる、またはIOが低すぎる場合、インタープリターが大きくなると、より理想的な状況でどれほど優れていても、事態は悪化します。理由#2:awk(またはほとんどすべての言語)も、1つのことをうまく行うように設計された小さなutilよりも学習曲線が急になっています。ランタイムがコーダーの工数よりも安い場合、「ソフトウェアツール」を使用した簡単なコーディングでコストを節約できます。
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を指定してコンパイルすると、ゴーストドッグの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

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

以下は、各行を列に変換し、pasteそれらを一緒に結合することに基づくBashワンライナーです。

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を使いたかったのですが、動作させることができませんでした。


大きなファイルでアラームを実行する場合は、必ず目覚まし時計を設定してください。unix.stackexchange.com/questions/169716/…を読んで、そのアプローチの問題のすべてではなく一部を理解してください。
Ed Morton

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

以下の2つのスクリプトを使用して、以前に同様の操作を行いました。1つ目はawkで、2つ目は「純粋な」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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.