Cara yang efisien untuk mengubah urutan file di Bash


110

Saya memiliki file besar yang dipisahkan tab dengan format seperti ini

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

Saya ingin mengubah urutannya dengan cara yang efisien hanya dengan menggunakan perintah bash (saya dapat menulis sepuluh atau lebih baris skrip Perl untuk melakukannya, tetapi harus lebih lambat untuk dieksekusi daripada fungsi bash asli). Jadi hasilnya akan terlihat seperti

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

Saya memikirkan solusi seperti ini

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

Tapi itu lambat dan sepertinya bukan solusi yang paling efisien. Saya telah melihat solusi untuk vi di posting ini , tetapi masih terlalu lambat. Ada pemikiran / saran / ide brilian? :-)


12
Apa yang membuat Anda berpikir bahwa akan ada skrip bash yang lebih cepat daripada skrip Perl? Inilah jenis masalah yang sangat dikuasai Perl.
Mark Pim

1
@mark, jika ini murni bash, mungkin lebih cepat daripada menggabungkan semua alat potong / sed dll. Tetapi sekali lagi, jika Anda mendefinisikan "bash" sebagai alat penggabung, maka hanya menulis skrip awk akan sebanding dengan pemrosesan teks Perl wrt.
ghostdog74

Tambahkan yang lain karena tidak memahami bagaimana perl akan menjadi lambat di sini. Lambat menulis kode? Lambat untuk dieksekusi? Saya benar-benar tidak menyukai perl, tetapi perl memang unggul dalam tugas semacam ini.
Corey Porter

Jika kolom / bidang Anda memiliki ukuran / lebar tetap, maka Anda dapat menggunakan pencarian file Python untuk menghindari pembacaan file Anda ke dalam memori. Apakah Anda memiliki ukuran / lebar kolom / bidang tetap?
tommy.carstensen

2
Siapapun yang berpikir skrip shell akan lebih cepat dari awk atau perl perlu membaca unix.stackexchange.com/questions/169716/… sehingga mereka dapat memahami mengapa tidak demikian.
Ed Morton

Jawaban:


115
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

keluaran

$ 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

Kinerja terhadap solusi Perl oleh Jonathan pada file 10.000 baris

$ 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 oleh Ed Morton (@ ghostdog74 jangan ragu untuk menghapusnya jika Anda tidak setuju).

Mungkin versi ini dengan beberapa nama variabel yang lebih eksplisit akan membantu menjawab beberapa pertanyaan di bawah ini dan secara umum menjelaskan apa yang dilakukan skrip. Ini juga menggunakan tab sebagai pemisah yang awalnya diminta OP sehingga akan menangani bidang kosong dan secara kebetulan meningkatkan sedikit keluaran untuk kasus khusus ini.

$ 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

Solusi di atas akan bekerja di sembarang awk (kecuali awk lama, rusak tentu saja - ada YMMV).

Solusi di atas memang membaca seluruh file ke dalam memori - jika file input terlalu besar untuk itu maka Anda dapat melakukan ini:

$ 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

yang menggunakan hampir tidak ada memori tetapi membaca file input sekali per jumlah kolom dalam satu baris sehingga akan jauh lebih lambat daripada versi yang membaca seluruh file ke dalam memori. Ini juga mengasumsikan jumlah bidang sama pada setiap baris dan menggunakan GNU awk for ENDFILEdan ARGINDtetapi awk apa pun dapat melakukan hal yang sama dengan pengujian pada FNR==1dan END.


Dan sekarang untuk menangani label baris dan kolom juga?
Jonathan Leffler

OK - Anda benar; data sampel Anda tidak cocok dengan data sampel pertanyaan, tetapi kode Anda berfungsi dengan baik pada data sampel pertanyaan dan memberikan keluaran yang diperlukan (beri atau ambil kosong vs spasi tab). Terutama kesalahanku.
Jonathan Leffler

Pengaturan waktu yang menarik - Saya setuju Anda melihat manfaat kinerja di awk. Saya menggunakan MacOS X 10.5.8, yang tidak menggunakan 'gawk'; dan saya menggunakan Perl 5.10.1 (32-bit build). Saya mengumpulkan bahwa data Anda adalah 10.000 baris dengan 4 kolom per baris? Bagaimanapun, itu tidak terlalu penting; baik awk dan perl adalah solusi yang layak (dan solusi awk lebih rapi - pemeriksaan 'yang ditentukan' di Perl saya diperlukan untuk peringatan yang berjalan bebas di bawah ketat / peringatan) dan tidak ada yang bungkuk dan keduanya cenderung jauh lebih cepat daripada aslinya solusi skrip shell.
Jonathan Leffler

Pada matriks asli 2.2GB saya, solusi perl sedikit lebih cepat daripada awk - 350.103s vs. 369.410s Saya menggunakan perl 5.8.8 64bit
Federico Giorgi

1
@ zx8754 bahwa jumlah maksimum bidang hanya berlaku untuk awk lama non-POSIX. Mungkin yang sangat disayangkan bernama "nawk". Ini tidak berlaku untuk gawk atau awks modern lainnya.
Ed Morton

47

Opsi lainnya adalah menggunakan rs:

rs -c' ' -C' ' -T

-cmengubah pemisah kolom masukan, -Cmengubah pemisah kolom keluaran, dan -Tmengubah urutan baris dan kolom. Jangan gunakan -talih-alih -T, karena menggunakan jumlah baris dan kolom yang dihitung secara otomatis yang biasanya tidak benar. rs, yang dinamai fungsi pembentukan ulang di APL, hadir dengan BSD dan OS X, tetapi seharusnya tersedia dari manajer paket di platform lain.

Opsi kedua adalah menggunakan Ruby:

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

Opsi ketiga adalah menggunakan jq:

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

jq -R .mencetak setiap baris masukan sebagai literal string JSON, -s( --slurp) membuat larik untuk baris masukan setelah mengurai setiap baris sebagai JSON, dan -r( --raw-output) mengeluarkan konten string alih-alih literal string JSON. The /operator kelebihan beban untuk string split.


3
Saya tidak terbiasa rs- terima kasih untuk penunjuknya! ( Tautannya ke Debian; bagian hulu tampaknya mirbsd.org/MirOS/dist/mir/rs )
tripleee

2
@lalebarde Setidaknya dalam implementasi rsyang datang dengan OS X, -csendiri set pemisah kolom input ke tab.
nisetama

2
@lalebarde, coba kutipan ANSI-C bash untuk mendapatkan karakter tab:$'\t'
glenn jackman

3
Ini adalah kasus yang ekstrim, tetapi untuk file yang sangat besar dengan banyak baris seperti TTC TTA TTC TTC TTT, menjalankan rs -c' ' -C' ' -T < rows.seq > cols.seqmemberi rs: no memory: Cannot allocate memory. Ini adalah sistem yang menjalankan FreeBSD 11.0-RELEASE dengan 32 GB ram. Jadi, tebakan saya adalah rsmenempatkan semuanya dalam RAM, yang bagus untuk kecepatan, tetapi tidak untuk data yang besar.
jrm

1
jq menggunakan ram 21Gb pada file 766MB. Saya membunuhnya setelah 40 menit tanpa hasil apa pun.
Glubbdrubb

30

Solusi 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

Di atas didasarkan pada yang berikut:

import sys

for c in zip(*(l.split() for l in sys.stdin.readlines() if l.strip())):
    print(' '.join(c))

Kode ini mengasumsikan bahwa setiap baris memiliki jumlah kolom yang sama (tidak ada padding yang dilakukan).


3
Satu masalah kecil di sini: Ganti l.split()dengan l.strip().split()(Python 2.7), jika tidak, baris terakhir dari output akan lumpuh. Berfungsi untuk pemisah kolom arbitrer, gunakan l.strip().split(sep)dan sep.join(c)jika pemisah Anda disimpan dalam variabel sep.
krlmlr

21

yang transpose proyek sourceforge adalah seperti coreutil C program yang tepat.

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

Terima kasih untuk tautannya. Namun, ini membutuhkan terlalu banyak memori, saat menangani matriks / file besar.
tommy.carstensen

ia memiliki argumen untuk ukuran blok dan ukuran bidang: coba ubah argumen -bdan -f.
domba terbang

Ukuran blok default (--block atau -b) adalah 10kb dan ukuran field default (--fieldmax atau -f) adalah 64, jadi tidak mungkin. Saya mencoba. Terima kasih atas sarannya.
tommy.carstensen

1
Bekerja dengan baik dengan csv berukuran 2 GB.
Disiplin

2
Untuk file matriks dengan dimensi kira-kira 11k kali 5k, saya menemukan transpose.c menjadi ~ 7x lebih cepat dan ~ 5x lebih hemat memori daripada solusi awk pertama ghostdog74. Juga, saya menemukan bahwa kode awk "menggunakan hampir tidak ada memori" dari ghostdog74 tidak berfungsi dengan baik. Juga, perhatikan flag --limit di program transpose.c, yang secara default membatasi output ke dimensi 1k kali 1k.
ncemami

16

BASH murni, tanpa proses tambahan. Latihan yang bagus:

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

Ini berfungsi untuk file saya, meskipun menariknya mencetak daftar direktori untuk baris pertama tabel. Saya tidak cukup tahu BASH untuk mencari tahu mengapa.
bugloaf

@bugloaf meja Anda memiliki * di sudut.
Halo71

2
@bugloaf: Mengutip variabel dengan benar akan mencegah hal itu:printf "%s\t" "${array[$COUNTER]}"
Dijeda hingga pemberitahuan lebih lanjut.

16

Lihat GNU datamash yang bisa digunakan seperti datamash transpose. Versi mendatang juga akan mendukung tabulasi silang (tabel pivot)


9

Berikut ini skrip Perl yang cukup solid untuk melakukan pekerjaan itu. Ada banyak analogi struktural dengan awksolusi @ ghostdog74 .

#!/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";
}

Dengan ukuran data sampel, perbedaan kinerja antara perl dan awk dapat diabaikan (1 milidetik dari total 7). Dengan kumpulan data yang lebih besar (matriks 100x100, masing-masing entri 6-8 karakter), perl sedikit mengungguli awk - 0,026s vs 0,042s. Tidak ada yang mungkin menjadi masalah.


Pengaturan waktu representatif untuk Perl 5.10.1 (32-bit) vs awk (versi 20040207 ketika diberi '-V') vs gawk 3.1.7 (32-bit) di MacOS X 10.5.8 pada file yang berisi 10.000 baris dengan 5 kolom per baris:

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: 

Perhatikan bahwa melongo jauh lebih cepat dari awk di mesin ini, tapi masih lebih lambat dari perl. Jelas, jarak tempuh Anda akan bervariasi.


di sistem saya, melongo mengungguli perl. Anda dapat melihat hasil saya di posting saya yang diedit
ghostdog74

4
Kesimpulan yang dikumpulkan: platform berbeda, versi perangkat lunak berbeda, hasil berbeda.
ghostdog74

6

Jika Anda telah scmenginstal, Anda dapat melakukan:

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

4
Perhatikan bahwa ini mendukung jumlah baris yang terbatas karena scmenamai kolomnya sebagai satu atau kombinasi dari dua karakter. Batasannya adalah 26 + 26^2 = 702.
Thor


5

Dengan asumsi semua baris Anda memiliki jumlah bidang yang sama, program awk ini menyelesaikan masalah:

{for (f=1;f<=NF;f++) col[f] = col[f]":"$f} END {for (f=1;f<=NF;f++) print col[f]}

Dengan kata lain, saat Anda mengulang baris, untuk setiap bidang, buat f':' - string terpisah yang col[f]berisi elemen bidang itu. Setelah Anda selesai dengan semua baris, cetak setiap string tersebut di baris terpisah. Anda kemudian dapat mengganti ':' untuk pemisah yang Anda inginkan (katakanlah, spasi) dengan menyalurkan keluaran tr ':' ' '.

Contoh:

$ 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 sangat cocok untuk masalah ini dengan hanya satu baris kode dan kemungkinan besarnya ukuran file yang sewenang-wenang!

datamash -W transpose infile > outfile

3

Solusi perl hackish bisa seperti ini. Itu bagus karena tidak memuat semua file di memori, mencetak file temp menengah, dan kemudian menggunakan pasta yang sangat bagus

#!/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;

menggunakan file tempel dan temp hanyalah operasi tambahan yang tidak perlu. Anda hanya dapat melakukan manipulasi di dalam memori itu sendiri, misalnya array / hashes
ghostdog74

2
Ya, tapi bukankah itu berarti menyimpan semuanya dalam ingatan? File yang saya tangani berukuran sekitar 2-20gb.
Federico Giorgi

3

Satu-satunya peningkatan yang dapat saya lihat pada contoh Anda sendiri adalah menggunakan awk yang akan mengurangi jumlah proses yang dijalankan dan jumlah data yang disalurkan di antara mereka:

/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

Saya biasanya menggunakan awkpotongan kecil ini untuk persyaratan ini:

  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

Ini hanya memuat semua data ke dalam array dua dimensi a[line,column]dan kemudian mencetaknya kembali sebagai a[column,line], sehingga mentransposisi input yang diberikan.

Ini perlu melacak maxjumlah kolom yang dimiliki file awal, sehingga digunakan sebagai jumlah baris untuk dicetak kembali.


2

Saya menggunakan solusi fgm (terima kasih fgm!), Tetapi perlu menghilangkan karakter tab di akhir setiap baris, jadi ubah skripnya menjadi:

#!/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

Saya hanya mencari pesta yang serupa tetapi dengan dukungan untuk bantalan. Berikut adalah skrip yang saya tulis berdasarkan solusi fgm, yang tampaknya berfungsi. Jika itu bisa membantu ...

#!/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

Saya sedang mencari solusi untuk mengubah semua jenis matriks (nxn atau mxn) dengan semua jenis data (angka atau data) dan mendapatkan solusi berikut:

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

Jika Anda hanya ingin mengambil satu baris (dipisahkan koma) $ N dari file dan mengubahnya menjadi kolom:

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

2

Tidak terlalu elegan, tetapi perintah "satu baris" ini menyelesaikan masalah dengan cepat:

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

Di sini kolom adalah jumlah kolom, di mana Anda dapat mengganti 4 dengan head -n 1 input | wc -w.


2

awkSolusi lain dan input terbatas dengan ukuran memori yang Anda miliki.

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

Ini menggabungkan setiap posisi nomor yang sama menjadi bersama-sama dan ENDmencetak hasil yang akan menjadi baris pertama di kolom pertama, baris kedua di kolom kedua, dll. Akan menampilkan:

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

2

Beberapa standar * nix menggunakan satu baris, tidak perlu file temporer. NB: OP menginginkan perbaikan yang efisien , (yaitu lebih cepat), dan jawaban teratas biasanya lebih cepat dari jawaban ini. Ini satu-liners adalah untuk mereka yang suka * nix perangkat lunak , untuk alasan apapun. Dalam kasus yang jarang terjadi, ( mis. IO & memori yang langka), cuplikan ini sebenarnya bisa lebih cepat daripada beberapa jawaban teratas.

Panggil file input foo .

  1. Jika kita tahu foo memiliki empat kolom:

    for f in 1 2 3 4 ; do cut -d ' ' -f $f foo | xargs echo ; done
  2. Jika kita tidak tahu berapa banyak kolom yang dimiliki foo :

    n=$(head -n 1 foo | wc -w)
    for f in $(seq 1 $n) ; do cut -d ' ' -f $f foo | xargs echo ; done

    xargsmemiliki batas ukuran dan karena itu akan membuat pekerjaan tidak lengkap dengan file yang panjang. Berapa batas ukuran yang bergantung pada sistem, misalnya:

    { timeout '.01' xargs --show-limits ; } 2>&1 | grep Max

    Panjang perintah maksimum yang sebenarnya dapat kami gunakan: 2088944

  3. tr& echo:

    for f in 1 2 3 4; do cut -d ' ' -f $f foo | tr '\n\ ' ' ; echo; done

    ... atau jika # kolom tidak diketahui:

    n=$(head -n 1 foo | wc -w)
    for f in $(seq 1 $n); do 
        cut -d ' ' -f $f foo | tr '\n' ' ' ; echo
    done
  4. Menggunakan set, yang seperti xargs, memiliki batasan berbasis ukuran baris perintah yang serupa:

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

2
Itu semua akan menjadi lipat lebih lambat dari solusi awk atau perl dan rapuh. Baca unix.stackexchange.com/questions/169716/… .
Ed Morton

@EdMorton, terima kasih, perkenalan yang memenuhi syarat dari jawaban saya untuk mengatasi masalah kecepatan Anda. Re "rapuh": bukan 3) , dan tidak juga yang lain ketika pemrogram mengetahui bahwa data aman untuk teknik tertentu; dan bukankah kode shell yang kompatibel dengan POSIX merupakan standar yang lebih stabil daripada perl ?
agc

maaf, saya tidak tahu banyak tentang perl. Dalam hal ini alat yang akan digunakan adalah awk. cut, head, echo, Dll tidak lebih POSIX kompatibel kode shell dari sebuah awkscript - mereka semua adalah standar pada setiap instalasi UNIX. Tidak ada alasan untuk menggunakan seperangkat alat yang dalam kombinasi tersebut mengharuskan Anda untuk berhati-hati tentang konten file input Anda dan direktori tempat Anda menjalankan skrip ketika Anda dapat menggunakan awk dan hasil akhirnya lebih cepat serta lebih kuat .
Ed Morton

Tolong, saya bukan anti- awk , tapi kondisinya berbeda-beda. Alasan # 1: for f in cut head xargs seq awk ; do wc -c $(which $f) ; done Ketika penyimpanan terlalu lambat atau IO terlalu rendah, penerjemah yang lebih besar memperburuk keadaan tidak peduli seberapa baik mereka dalam keadaan yang lebih ideal. Alasan # 2: awk , (atau sebagian besar bahasa lainnya), juga mengalami kurva belajar yang lebih curam daripada util kecil yang dirancang untuk melakukan satu hal dengan baik. Ketika waktu proses lebih murah daripada jam kerja pembuat kode, pengkodean mudah dengan "perangkat lunak" dapat menghemat uang.
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

versi lain dengan set eval


Baca unix.stackexchange.com/questions/169716/… untuk memahami beberapa, tetapi tidak semua, masalah dengan solusi itu.
Ed Morton

1

Varian bash lainnya

$ cat file 
XXXX    col1    col2    col3
row1    0       1       2
row2    3       4       5
row3    6       7       8
row4    9       10      11

Naskah

#!/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
}

Keluaran

$ ./test 
XXXX    row1    row2    row3    row4    
col1    0       3       6       9   
col2    1       4       7       10  
col3    2       5       8       11

0

Inilah solusi Haskell. Ketika dikompilasi dengan -O2, ini berjalan sedikit lebih cepat dari awk ghostdog dan sedikit lebih lambat dari python c yang dibungkus tipis Stephan pada mesin saya untuk jalur input "Halo dunia" yang berulang. Sayangnya dukungan GHC untuk meneruskan kode baris perintah sejauh yang saya tahu tidak ada, jadi Anda harus menuliskannya ke file sendiri. Ini akan memotong baris menjadi panjang baris terpendek.

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

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

0

Solusi awk yang menyimpan seluruh array dalam memori

    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

Tetapi kita dapat "menjalankan" file tersebut sebanyak baris keluaran yang dibutuhkan:

#!/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

Yang (untuk jumlah baris keluaran yang rendah lebih cepat dari kode sebelumnya).


0

Berikut ini Bash one-liner yang didasarkan pada konversi setiap baris ke kolom dan pastemenggabungkannya:

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. membuat tmp1file jadi tidak kosong.

  2. membaca setiap baris dan mengubahnya menjadi kolom menggunakan tr

  3. menempelkan kolom baru ke tmp1file

  4. salinan hasil kembali menjadi tmp1.

PS: Saya benar-benar ingin menggunakan io-descriptors tetapi tidak bisa membuatnya berfungsi.


Pastikan untuk mengatur jam alarm jika Anda akan menjalankannya pada file besar. Baca unix.stackexchange.com/questions/169716/… untuk memahami beberapa, tetapi tidak semua, masalah dengan pendekatan itu.
Ed Morton

0

Seorang oneliner menggunakan 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

Saya telah menggunakan dua skrip di bawah ini untuk melakukan operasi serupa sebelumnya. Yang pertama di awk yang jauh lebih cepat daripada yang kedua di bash "murni". Anda mungkin dapat menyesuaikannya dengan aplikasi Anda sendiri.

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
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.