123

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

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

4

31 に答える 31

125
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

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

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

于 2009-11-13T15:34:46.363 に答える
63

rs

rsmacOS にも付属する BSD ユーティリティですが、他のプラットフォームのパッケージ マネージャーから利用できます。これは、APL の reshape 関数にちなんで名付けられました。

一連のスペースとタブを列区切りとして使用します。

rs -T

タブを列区切りとして使用する:

rs -c -C -T

列区切りとしてコンマを使用します。

rs -c, -C, -T

-c入力列セパレータを-C変更し、出力列セパレータを変更します。単独の-cor-Cは、セパレーターをタブに設定します。-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

これは、最初の行の列数に基づいて列数が決定されるため、最初の行が 1 つ以上の空の列で終わるテーブルでは失敗します。

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

ガウク

$ seq 4|paste -d, - -|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?"\n":FS),a[i][j]}' FS=,
1,3
2,4

これは、gawk拡張機能である配列の配列を使用します。macOS には、nawk配列の配列をサポートしない 2007 以降のバージョンが付属しています。

スペースとタブ文字のシーケンスを折りたたまずにスペースをセパレータとして使用するには、 を使用しますFS='[ ]'

ルビー

$ seq 4|paste -d, - -|ruby -e'STDIN.map{|x|x.chomp.split(",",-1)}.transpose.each{|x|puts x*","}'
1,3
2,4

最後の空のフィールドの破棄-1を無効にする引数:split

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

jq

tp(){ jq -R .|jq --arg x "${1-$'\t'}" -sr 'map(./$x)|transpose|map(join($x))[]';}

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

R

$ printf %s\\n 1,2 3,4|Rscript -e 'write.table(t(read.table("stdin",sep=",")),"",sep=",",quote=F,col.names=F,row.names=F)'
1,3
2,4

に置き換えるRscriptR、実行中のコードが STDOUT にエコーされます。STDIN全体を読み取る前に終了するignoring SIGPIPE signalようなコマンドが続いた場合にも、エラーが発生します。head -n1

write.table出力ファイルの引数が空の文字列の場合、STDOUT に出力します。

于 2015-05-11T17:28:41.497 に答える
32

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

このコードは、すべての行に同じ数の列があることを前提としています (パディングは実行されません)。

于 2009-11-13T17:21:00.923 に答える
22

sourceforgeのトランスポーズプロジェクトは、まさにそのための coreutil に似た C プログラムです。

gcc transpose.c -o transpose
./transpose -t input > output #works with stdin, too.
于 2013-02-08T17:36:25.637 に答える
21

のように使用できるGNU datamashをご覧くださいdatamash transpose。将来のバージョンでは、クロス集計 (ピボット テーブル) もサポートします。

スペースで区切られた列でそれを行う方法は次のとおりです。

datamash transpose -t ' ' < file > transposed_file
于 2016-01-07T09:08:01.537 に答える
17

純粋な 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
于 2009-11-19T15:11:58.387 に答える
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 秒対 0.042 秒)。どちらも問題になりそうにありません。


MacOS X 10.5.8 上の Perl 5.10.1 (32 ビット) と awk ('-V' を指定した場合のバージョン 20040207) と 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 よりは遅いことに注意してください。明らかに、走行距離は異なります。

于 2009-11-14T19:54:04.930 に答える
9

これには専用のユーティリティがあり、

GNU データマッシュ ユーティリティ

apt install datamash  

datamash transpose < yourfile

このサイト、https://www.gnu.org/software/datamash/および http://www.thelinuxrain.com/articles/transposing-rows-and-columns-3-methodsから取得

于 2017-04-07T09:00:40.443 に答える
6

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

psc -r < inputfile | sc -W% - > outputfile
于 2009-11-13T16:54:28.947 に答える
5

私は通常、この要件に対して次の小さな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これは、最初のファイルが持っている列の最大量を追跡する必要があるため、それが出力する行数として使用されます。

于 2015-05-12T07:48:30.650 に答える
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
于 2009-11-13T16:08:51.030 に答える
3

一部の*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. tr& echo:

    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
    
于 2016-04-10T10:52:41.310 に答える
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;
于 2009-11-13T15:49:11.607 に答える
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
于 2010-03-21T22:39:57.687 に答える
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
于 2014-08-06T01:10:29.683 に答える
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

于 2014-05-06T21:41:21.810 に答える
2

ファイルから単一の (カンマ区切りの) 行 $N のみを取得して列に変換する場合:

head -$N file | tail -1 | tr ',' '\n'
于 2014-11-06T12:06:48.387 に答える
1

以下は、単純に各行を列に変換し、pasteそれらを -ing することに基づいた 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-descriptor を使用したかったのですが、動作させることができませんでした。

于 2014-12-07T03:08:45.357 に答える
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

どちら (出力行数が少ない場合は、前のコードよりも高速です)。

于 2016-01-28T22:46:04.777 に答える
0

これが 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
于 2014-08-26T03:03:40.417 に答える
0

私はゲームに少し遅れていますが、これはどうですか:

cat table.tsv | python -c "import pandas as pd, sys; pd.read_csv(sys.stdin, sep='\t').T.to_csv(sys.stdout, sep='\t')"

またはzcatgzipされている場合。

これはpandas、お使いのバージョンのpython

于 2021-10-12T17:40:18.123 に答える