このようにフォーマットされた巨大なタブ区切りファイルがあります
X column1 column2 column3
row1 0 1 2
row2 3 4 5
row3 6 7 8
row4 9 10 11
私はtransposebashコマンドのみを使用して効率的な方法でそれをしたいと思います(それを行うために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の解決策を見てきましたが、それでもまだ遅いです。何か考え/提案/素晴らしいアイデア? :-)
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回入力ファイルを読み取るため、ファイル全体をメモリに読み込むバージョンよりもはるかに遅くなります。また、フィールドの数は各行で同じであり、ENDFILE
およびARGIND
に対してGNU awkを使用しますが、awkはFNR==1
およびEND
のテストでも同じことができます。
別のオプションは、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文字列リテラルとして出力し、-s
(--Slurp
)は各行をJSONとして解析した後に入力行の配列を作成し、-r
(--raw-output
)は、JSON文字列リテラルの代わりに文字列の内容を出力します。 /
演算子は、文字列を分割するためにオーバーロードされます。
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))
このコードは、すべての行の列数が同じであると想定しています(パディングは実行されません)。
sourceforgeの transpose プロジェクトは、まさにそのためのcoreutilのようなCプログラムです。
gcc transpose.c -o transpose
./transpose -t input > output #works with stdin, too.
純粋な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
datamash transpose
のように使用できる GNU datamash をご覧ください。将来のバージョンでは、クロス集計もサポートされる予定です(ピボットテーブル)
これを行うための適度に堅実な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秒。どちらも問題になる可能性はありません。
Perl 5.10.1(32ビット)対awk( '-V'を指定した場合はバージョン20040207)vs 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よりも遅いことに注意してください。明らかに、走行距離は異なります。
sc
がインストールされている場合、次のことができます。
psc -r < inputfile | sc -W% - > outputfile
これには専用のユーティリティがあり、
apt install datamash
datamash transpose < yourfile
このサイトから取得 https://www.gnu.org/software/datamash/ および http://www.thelinuxrain.com/articles/transposing-rows-and-columns -3-メソッド
すべての行に同じ数のフィールドがあると仮定すると、この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
GNU datamash は、1行のコードと潜在的に任意の大きなファイルサイズでこの問題に最適です!
datamash -W transpose infile > outfile
ハック的な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
スニペットを使用します。
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
imum列の量を追跡する必要があるため、印刷する行数として使用されます。
私があなた自身の例で見ることができる唯一の改善は、実行されるプロセスの数とそれらの間でパイプされるデータの量を減らす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
同様の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
私は、あらゆる種類の行列(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
それほどエレガントではありませんが、この「単一行」コマンドは問題を迅速に解決します。
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
に置き換えることができます。
別の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
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
ファイルから単一の(コンマ区切り)行$ Nのみを取得して、列に変換する場合:
head -$N file | tail -1 | tr ',' '\n'
#!/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
を含む別のバージョン
いくつかの* nix標準ユーティリティワンライナー、一時ファイルは不要です。注:OPは効率的な修正(つまり、より高速)を望んでおり、通常、上位の回答はこの回答よりも高速です。 これらのワンライナーは* nixが好きな人向けです ソフトウェアツール 、何らかの理由で。まれに、(e.g。希少なIOおよびメモリ)、これらのスニペットは実際にいくつかの上位の回答よりも高速です。
入力ファイルを呼び出しますfoo。
fooに4つの列がある場合:
for f in 1 2 3 4 ; do cut -d ' ' -f $f foo | xargs echo ; done
カラム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
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
set
のようなxargs
を使用すると、同様のコマンドラインサイズに基づく制限があります。
for f in 1 2 3 4 ; do set - $(cut -d ' ' -f $f foo) ; echo $@ ; done
これがHaskellソリューションです。 -O2でコンパイルすると、ghostdogのawkよりもわずかに速く、Stephanのawkよりも少し遅くなります。 薄く包まれたc マシン上のpythonは、「Hello world」の入力行を繰り返します。残念ながら、コマンドラインコードを渡すためのGHCのサポートは、私が知る限り存在しないため、自分でファイルに書き込む必要があります。行を最も短い行の長さに切り捨てます。
transpose :: [[a]] -> [[a]]
transpose = foldr (zipWith (:)) (repeat [])
main :: IO ()
main = interact $ unlines . map unwords . transpose . map words . lines
配列全体をメモリに保存する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
これ(出力行の数が少ない場合は、前のコードよりも高速です)。
以下は、各行を列に変換し、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
tmp1
ファイルを作成して、空にならないようにします。
各行を読み取り、tr
を使用して列に変換します
新しい列をtmp1
ファイルに貼り付けます
結果をtmp1
にコピーします。
PS:io記述子を使いたかったのですが、機能させることができませんでした。