Bash'de bir dosyayı dönüştürmenin etkili bir yolu


110

Bunun gibi biçimlendirilmiş sekmeyle ayrılmış büyük bir dosyam var

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

Sadece bash komutlarını kullanarak verimli bir şekilde transpoze etmek istiyorum (Bunu yapmak için on ya da daha fazla satır Perl betiği yazabilirim, ancak çalıştırılması yerel bash işlevlerinden daha yavaş olmalı). Yani çıktı şöyle görünmeli

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

Bunun gibi bir çözüm düşündüm

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

Ancak yavaştır ve en etkili çözüm gibi görünmüyor. Bu yazıda vi için bir çözüm gördüm , ancak hala aşırı yavaş. Herhangi bir fikir / öneri / parlak fikir? :-)


12
Perl betiğinden daha hızlı olacak bir bash betiğinin olduğunu düşündüren nedir? Bu tam olarak Perl'in üstesinden geldiği türden bir sorundur.
Mark Pim

1
@mark, saf bash ise, tüm bu kesme / sed vb. araçları birbirine zincirlemekten daha hızlı olabilir. Ama yine de, "bash" ı, araçları birleştirmek gibi tanımlarsanız, o zaman sadece bir awk betiği yazmak Perl wrt metin işlemeye benzeyecektir.
ghostdog74

Perl'in burada ne kadar yavaş olacağını anlamamak için bir tane daha ekleyin. Kodu yazmak yavaş mı? Yürütmek yavaş mı? Perl'den gerçekten hoşlanmıyorum, ancak bu tür görevlerde mükemmel.
Corey Porter

Sütunlarınızın / alanlarınızın sabit bir boyutu / genişliği varsa, dosyanızı belleğe okumaktan kaçınmak için Python dosya arama özelliğini kullanabilirsiniz. Sabit sütun / alan boyutları / genişlikleriniz var mı?
tommy.carstensen

2
Bir kabuk betiğinin awk veya perl'den daha hızlı olacağını düşünen herkesin unix.stackexchange.com/questions/169716/… dosyasını okuması gerekir, böylece neden böyle olmadığını anlayabilirler.
Ed Morton

Yanıtlar:


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

çıktı

$ 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 tarafından 10000 satırlık bir dosyada Perl çözümüne karşı performans

$ 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 (@ ghostdog74, onaylamazsanız silmekten çekinmeyin).

Belki daha açık değişken adlarına sahip bu sürüm, aşağıdaki soruların bazılarının yanıtlanmasına yardımcı olur ve genel olarak betiğin ne yaptığını netleştirir. Ayrıca, OP'nin başlangıçta istediği ayırıcı olarak sekmeleri kullanır, böylece boş alanları ele alır ve tesadüfen, bu özel durum için çıktıyı biraz güzelleştirir.

$ 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

Yukarıdaki çözümler herhangi bir awk'de çalışacaktır (tabii ki eski, bozuk awk hariç - orada YMMV).

Yukarıdaki çözümler tüm dosyayı belleğe okur - eğer giriş dosyaları bunun için çok büyükse, o zaman bunu yapabilirsiniz:

$ 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

Neredeyse hiç bellek kullanmayan, ancak girdi dosyasını bir satırdaki alan sayısı başına bir kez okur, böylece tüm dosyayı belleğe okuyan sürümden çok daha yavaş olacaktır. Ayrıca alan sayısının her satırda aynı olduğunu varsayar ve için GNU awk kullanır ENDFILEve ARGINDancak herhangi bir awk, FNR==1ve üzerindeki testlerle aynı şeyi yapabilir END.


Ve şimdi sıra ve sütun etiketlerini de işlemek için?
Jonathan Leffler

Tamam - haklısın; örnek verileriniz sorunun örnek verileriyle eşleşmiyor, ancak kodunuz sorunun örnek verileri üzerinde iyi çalışıyor ve gerekli çıktıyı veriyor (boşluğa karşı sekme aralığı verin veya alın). Esasen benim hatam.
Jonathan Leffler

İlginç zamanlamalar - awk'ta bir performans avantajı gördüğünüzü kabul ediyorum. 'Gawk' kullanmayan MacOS X 10.5.8 kullanıyordum; ve Perl 5.10.1 (32-bit yapı) kullanıyordum. Verilerinizin, satır başına 4 sütun içeren 10000 satır olduğunu anladım. Her neyse, önemli değil; hem awk hem de perl uygulanabilir çözümlerdir (ve awk çözümü daha düzgündür - Perl'imdeki 'tanımlı' kontroller, sıkı / uyarılar altında uyarısız çalıştırmalar için gereklidir) ve hiçbiri eğik değildir ve her ikisi de orijinalinden çok daha hızlı olacaktır. kabuk komut dosyası çözümü.
Jonathan Leffler

Orijinal 2.2GB matrisimde, perl çözümü awk - 350.103s'den 369.410s'ye göre biraz daha hızlı. Perl 5.8.8 64bit kullanıyordum
Federico Giorgi

1
@ zx8754, maksimum alan sayısı yalnızca eski, POSIX olmayan bir awk için geçerlidir. Muhtemelen inanılmaz derecede ne yazık ki adı "nawk". Gawk veya diğer modern awks için geçerli değildir.
Ed Morton

47

Başka bir seçenek de kullanmaktır rs:

rs -c' ' -C' ' -T

-cgiriş sütun ayırıcısını -Cdeğiştirir, çıktı sütunu ayırıcısını -Tdeğiştirir ve satır ve sütunların yerini değiştirir. Bunun -tyerine kullanmayın -T, çünkü otomatik olarak hesaplanan sayıda satır ve sütun kullanır ve bu genellikle doğru değildir. rsAPL'deki yeniden şekillendirme işlevinden sonra adlandırılan, BSD'ler ve OS X ile birlikte gelir, ancak diğer platformlardaki paket yöneticilerinden edinilebilir olmalıdır.

İkinci bir seçenek Ruby kullanmaktır:

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

Üçüncü bir seçenek kullanmaktır jq:

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

jq -R .her girdi satırını bir JSON dizesi değişmezi olarak yazdırır, -s( --slurp) her satırı JSON olarak ayrıştırdıktan sonra girdi satırları için bir dizi oluşturur ve -r( --raw-output) JSON dizesi değişmezleri yerine dizelerin içeriğini çıkarır. /Operatör bölünmüş dizelerine aşırı yüklü.


3
Aşina değildim rs- işaretçi için teşekkürler! (Bağlantı Debian'a; yukarı akış mirbsd.org/MirOS/dist/mir/rs gibi görünüyor )
üçlü

2
@lalebarde En azından rsOS X ile gelen uygulamada , -ctek başına giriş sütun ayırıcısını bir sekmeye ayarlar.
nisetama

2
@lalebarde, bir sekme karakteri elde etmek için bash'ın ANSI-C alıntılarını deneyin :$'\t'
glenn jackman

3
Bu aşırı bir durumdur, ancak çok sayıda satır içeren çok büyük bir dosya için TTC TTA TTC TTC TTT, koşu rs -c' ' -C' ' -T < rows.seq > cols.seqverir rs: no memory: Cannot allocate memory. Bu, 32 GB ram ile FreeBSD 11.0-RELEASE çalıştıran bir sistemdir. Öyleyse, benim tahminim rsher şeyi RAM'e koyar, bu hız için iyidir, ancak büyük veriler için değildir.
JRM

1
jq, 766 MB'lık bir dosyada 21 Gb ram kullandı. 40 dakika sonra herhangi bir çıktı olmadan onu öldürdüm.
Glubbdrubb

30

Bir Python çözümü:

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

Yukarıdakiler aşağıdakilere dayanmaktadır:

import sys

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

Bu kod, her satırın aynı sayıda sütuna sahip olduğunu varsayar (doldurma yapılmaz).


3
Burada bir küçük sorun: Değiştir l.split()tarafından l.strip().split()başka çıktının son satırında sakat (Python 2.7). İsteğe bağlı sütun ayırıcılar için çalışır, kullanın l.strip().split(sep)ve sep.join(c)ayırıcınız değişken olarak saklanıyorsa sep.
krlmlr

21

devrik sourceforge proje buna uygun bir coreutil benzeri C programıdır.

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

Bağlantı için teşekkürler. Ancak, büyük matrisler / dosyalar ile uğraşırken çok fazla bellek gerektirir.
tommy.carstensen

blok boyutu ve alan boyutu için argümanlara sahiptir: -bve -fargümanlarını değiştirmeyi deneyin .
uçan koyun

Varsayılan blok boyutu (--block veya -b) 10kb'dir ve varsayılan alan boyutu (--fieldmax veya -f) 64'tür, bu yüzden bu olamaz. Denedim. Yine de öneri için teşekkürler.
tommy.carstensen

1
2 GB boyutunda bir csv ile iyi çalıştı.
discipulus

2
Kabaca 11k'ye 5k boyutlarına sahip bir matris dosyası için, transpose.c'nin ghostdog74'ün ilk awk çözümünden ~ 7 kat daha hızlı ve ~ 5 kat daha fazla bellek verimli olduğunu buldum. Ayrıca, ghostdog74'ten "neredeyse hiç bellek kullanmıyor" awk kodunun düzgün çalışmadığını buldum. Ayrıca, varsayılan olarak çıktıyı 1k x 1k boyutuna sınırlayan transpose.c programındaki --limit bayrağına dikkat edin.
ncemami

16

Saf BASH, ek işlem yok. Güzel bir egzersiz:

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

Bu benim dosyam için çalıştı, ancak ilginç bir şekilde tablonun ilk satırı için bir dizin listesi yazdırıyor. Nedenini anlamaya yetecek kadar BASH bilmiyorum.
bugloaf

@bugloaf masanızın köşesinde * var.
Merhaba71

2
@bugloaf: Değişkenleri düzgün şekilde alıntılamak şunları engellemelidir:printf "%s\t" "${array[$COUNTER]}"
sonraki duyuruya kadar duraklatıldı.

16

GNU datamash gibi kullanılabilecek bir göz atın datamash transpose. Gelecekteki bir sürüm de çapraz tablolamayı destekleyecektir (pivot tablolar)


9

İşte işi yapmak için orta derecede sağlam bir Perl betiği. @ Ghostdog74'ün awkçözümü ile birçok yapısal benzerlik var .

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

Örnek veri boyutuyla, perl ve awk arasındaki performans farkı ihmal edilebilir düzeydeydi (toplam 7 milisaniye üzerinden 1 milisaniye). Daha büyük bir veri kümesiyle (100x100 matris, girişlerin her biri 6-8 karakter), perl biraz daha iyi performans gösterdi awk - 0.026s vs 0.042s. Muhtemelen ikisi de sorun teşkil etmez.


Her biri 5 sütun içeren 10.000 satır içeren bir dosyada, MacOS X 10.5.8'de Perl 5.10.1 (32 bit) ile awk ('-V' verildiğinde sürüm 20040207) ve gawk 3.1.7 (32 bit) için temsili zamanlamalar hat:

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: 

Bu makinede gawk'ın awk'tan çok daha hızlı olduğunu, ancak yine de perl'den daha yavaş olduğunu unutmayın. Açıkça, kilometreniz değişecektir.


benim sistemimde gawk perl'den daha iyi performans gösteriyor. sonuçlarımı düzenlenmiş gönderimde görebilirsiniz
ghostdog74

4
elde edilen sonuç: farklı platform, farklı yazılım sürümü, farklı sonuçlar.
ghostdog74

6

Yüklediyseniz şunları scyapabilirsiniz:

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

4
Bunun sınırlı sayıda satırı desteklediğini unutmayın, çünkü scsütunlarını bir veya iki karakter kombinasyonu olarak adlandırır. Sınır 26 + 26^2 = 702.
Thor


5

Tüm satırlarınızın aynı sayıda alana sahip olduğunu varsayarsak, bu awk programı sorunu çözer:

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

Diğer bir deyişle, satırlar üzerinde döngü oluşturduğunuzda, her alan için o alanın öğelerini içeren f':' ile ayrılmış bir dize büyür col[f]. Tüm satırları tamamladıktan sonra, bu dizelerin her birini ayrı bir satırda yazdırın. Ardından, çıktıyı borulayarak istediğiniz ayırıcı için ':' (örneğin bir boşluk) yerine kullanabilirsiniz tr ':' ' '.

Misal:

$ 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 veri kutusu , yalnızca tek bir kod satırı ve potansiyel olarak keyfi olarak büyük dosya boyutu ile bu soruna mükemmel bir şekilde uygundur!

datamash -W transpose infile > outfile

3

Bir hackish perl çözümü bunun gibi olabilir. Güzel, çünkü tüm dosyayı belleğe yüklemiyor, ara geçici dosyaları yazdırıyor ve ardından harika yapıştırmayı kullanıyor

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

yapıştırma ve geçici dosyaları kullanmak sadece fazladan gereksiz işlemlerdir. sadece belleğin içinde manipülasyon yapabilirsiniz, örneğin diziler / karmalar
ghostdog74

2
Evet, ama bu her şeyi hafızada tutmak anlamına gelmez mi? İlgilendiğim dosyaların boyutu 2-20 gb civarında.
Federico Giorgi

3

Kendi örneğinizde görebildiğim tek gelişme, çalıştırılan işlemlerin sayısını ve bunlar arasında aktarılan veri miktarını azaltacak olan awk kullanmaktır:

/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

Normalde bu küçük awkparçacığı bu gereksinim için kullanırım :

  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

Bu sadece tüm verileri iki boyutlu bir diziye yükler a[line,column]ve ardından a[column,line]verilen girdiyi transpoze edecek şekilde geri yazdırır .

Bu max, geri yazdırılacak satır sayısı olarak kullanılabilmesi için, ilk dosyanın sahip olduğu büyük sütun miktarını takip etmelidir.


2

Fgm'nin çözümünü kullandım (teşekkürler fgm!), Ancak her satırın sonundaki sekme karakterlerini ortadan kaldırmam gerekiyordu, bu yüzden betiği şu şekilde değiştirdim:

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

Ben sadece benzer bash tranpose arıyordum, ancak dolgu desteği var. İşte fgm'nin çözümüne dayanarak yazdığım senaryo çalışıyor gibi görünüyor. Yardımcı olabilirse ...

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

Her türlü matrisi (nxn veya mxn) her türlü veriyle (sayılar veya veriler) dönüştürmek için bir çözüm arıyordum ve aşağıdaki çözümü aldım:

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

Bir dosyadan yalnızca tek bir (virgülle ayrılmış) $ N satırı alıp bir sütuna dönüştürmek istiyorsanız:

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

2

Çok zarif değil, ancak bu "tek satırlı" komut sorunu hızlı bir şekilde çözer:

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

Burada cols, 4'ü ile değiştirebileceğiniz sütun sayısıdır head -n 1 input | wc -w.


2

Sahip awkolduğunuz bellek boyutuyla başka bir çözüm ve sınırlı girdi.

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

Bu, aynı dosyalanmış numara pozisyonunu bir araya getirir ve ENDilk sütunda ilk satır, ikinci sütunda ikinci satır vb. Olacak sonucu yazdırır.

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

2

Bazı * nix standartlarında tek satırlık dosyalar kullanılır, geçici dosyalar gerekmez. Not: OP verimli bir düzeltme istedi (yani daha hızlı) ve en iyi cevaplar genellikle bu cevaptan daha hızlıdır. Bu tek satırlık yazılımlar , neden ne olursa olsun * nix yazılım araçlarını sevenler içindir . Nadir durumlarda ( örneğin sınırlı GÇ ve bellek), bu parçacıklar aslında en iyi yanıtların bazılarından daha hızlı olabilir.

Giriş dosyasını foo olarak adlandırın .

  1. Foo'nun dört sütunu olduğunu biliyorsak :

    for f in 1 2 3 4 ; do cut -d ' ' -f $f foo | xargs echo ; done
  2. Foo'nun kaç tane sütunu olduğunu bilmiyorsak :

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

    xargsbir boyut sınırına sahiptir ve bu nedenle uzun bir dosyada eksik çalışma yapacaktır. Hangi boyut sınırı sisteme bağlıdır, örneğin:

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

    Gerçekte kullanabileceğimiz maksimum komut uzunluğu: 2088944

  3. tr& echo:

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

    ... veya sütun sayısı bilinmiyorsa:

    n=$(head -n 1 foo | wc -w)
    for f in $(seq 1 $n); do 
        cut -d ' ' -f $f foo | tr '\n' ' ' ; echo
    done
  4. Kullanmak set, xargsbenzer komut satırı boyutuna dayalı sınırlamalara sahiptir:

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

2
Bunların hepsi bir awk veya perl çözümünden daha yavaş ve kırılgandır. Unix.stackexchange.com/questions/169716/… adresini okuyun .
Ed Morton

@EdMorton, teşekkürler, hız endişelerinizi gidermek için cevabıma uygun giriş. Re "kırılgan": değil 3) ve programcı verinin belirli bir teknik için güvenli olduğunu bildiğinde diğerleri ; ve POSIX uyumlu kabuğu kodu daha kararlı bir standart olmayan Perl ?
agc

üzgünüm, perl hakkında çok şey bilmiyorum. Bu durumda kullanılacak araç olacaktır awk. cut, head, echoVb hiçbir bir daha POSIX uyumlu kabuk kodudur awkscript - hepsi her UNIX yüklemesinde standarttır. Sadece awk kullanabildiğiniz ve sonuç daha hızlı ve daha sağlam olduğu zaman, girdi dosyanızın ve komut dosyasını çalıştırdığınız dizinin içeriği konusunda dikkatli olmanızı gerektiren bir dizi aracı kullanmak için hiçbir neden yoktur. .
Ed Morton

Lütfen, anti- awk değilim , ama koşullar değişebilir. Neden 1: for f in cut head xargs seq awk ; do wc -c $(which $f) ; done Depolama çok yavaş olduğunda veya IO çok düşük olduğunda, daha büyük tercümanlar daha ideal koşullar altında ne kadar iyi olurlarsa olsunlar işleri daha da kötüleştirir. Neden # 2: awk (veya herhangi bir dil), aynı zamanda bir şeyi iyi yapmak için tasarlanmış küçük bir kullanımdan daha dik bir öğrenme eğrisinden muzdariptir. Çalışma zamanı kodlayıcı çalışma saatlerinden daha ucuz olduğunda, "yazılım araçlarıyla" kolay kodlama paradan tasarruf sağlar.
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

ile başka bir versiyon set eval


Bu çözümle ilgili sorunların tümünü olmasa da bazılarını anlamak için unix.stackexchange.com/questions/169716/… adresini okuyun .
Ed Morton

1

Başka bir bash çeşidi

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

Senaryo

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

Çıktı

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

0

İşte bir Haskell çözümü. -O2 ile derlendiğinde, hayalet köpeğin awk'sinden biraz daha hızlı ve tekrarlanan "Merhaba dünya" giriş satırları için makinemdeki Stephan'ın ince sarılmış c python'undan biraz daha yavaş çalışır . Maalesef GHC'nin komut satırı kodunu iletme desteği anlayabildiğim kadarıyla mevcut değil, bu yüzden bunu bir dosyaya kendiniz yazmanız gerekecek. Satırları en kısa satırın uzunluğuna kadar keser.

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

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

0

Tüm diziyi bellekte depolayan bir awk çözümü

    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

Ancak, çıktı satırları gerektiği kadar dosyayı "gezdirebiliriz":

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

Hangisi (düşük sayıda çıktı satırı için önceki koddan daha hızlıdır).


0

İşte her satırı bir sütuna dönüştürmeye ve pastebunları bir araya getirmeye dayanan bir Bash tek satırlık :

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. tmp1dosya oluşturur, böylece boş olmaz.

  2. her satırı okur ve kullanarak bir sütuna dönüştürür tr

  3. yeni sütunu tmp1dosyaya yapıştırır

  4. kopyalar geri döner tmp1.

Not: Gerçekten io-tanımlayıcıları kullanmak istedim ama onları çalıştıramadım.


Bunu büyük bir dosyada çalıştıracaksanız bir çalar saat ayarladığınızdan emin olun. Bu yaklaşımla ilgili sorunların hepsini olmasa da bazılarını anlamak için unix.stackexchange.com/questions/169716/… adresini okuyun .
Ed Morton

0

R kullanan bir oneliner ...

  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

Daha önce benzer işlemleri yapmak için aşağıda iki komut dosyası kullandım. Birincisi, "saf" bash olan ikinciden çok daha hızlı olan awk içinde. Kendi uygulamanıza uyarlayabilirsiniz.

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.