Skuteczny sposób na transpozycję pliku w Bash

110

Mam ogromny plik rozdzielany tabulatorami, sformatowany w ten sposób

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

Chciałbym przetransponować to w efektywny sposób, używając tylko poleceń bash (mógłbym napisać około dziesięciu wierszy skryptu Perla, aby to zrobić, ale powinno to być wolniejsze niż natywne funkcje bash). Więc wyjście powinno wyglądać

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

Pomyślałem o takim rozwiązaniu

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

Ale jest powolny i nie wydaje się najbardziej wydajnym rozwiązaniem. Widziałem rozwiązanie dla vi w tym poście , ale nadal jest zbyt wolne. Jakieś przemyślenia / sugestie / genialne pomysły? :-)

Federico Giorgi
źródło
12
Dlaczego myślisz, że istniałby skrypt bash, który byłby szybszy niż skrypt Perla? W tym właśnie rodzaju problemu wyróżnia się Perl.
Mark Pim
1
@mark, jeśli to czysty bash, może to być szybsze niż łączenie wszystkich tych narzędzi cut / sed itp. Ale z drugiej strony, jeśli zdefiniujesz "bash" tak jak przy łączeniu narzędzi, wtedy samo napisanie skryptu awk będzie porównywalne z przetwarzaniem tekstu w Perl wrt.
ghostdog74
Dodaj kolejny, jeśli nie rozumiesz, jak bardzo perl byłby tutaj powolny. Czy wolno pisać kod? Powolne wykonanie? Naprawdę nie lubię perla, ale jest świetny w tego rodzaju zadaniach.
Corey Porter
Jeśli twoje kolumny / pola mają stały rozmiar / szerokość, możesz użyć funkcji wyszukiwania plików Python, aby uniknąć wczytywania pliku do pamięci. Czy masz stałe rozmiary / szerokości kolumn / pól?
tommy.carstensen
2
Każdy, kto uważa, że ​​skrypt powłoki byłby szybszy niż awk lub perl, musi przeczytać unix.stackexchange.com/questions/169716/, aby zrozumieć, dlaczego tak nie jest.
Ed Morton,

Odpowiedzi:

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

wynik

$ 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

Wydajność w porównaniu z rozwiązaniem Perla autorstwa Jonathana w pliku zawierającym 10000 linii

$ 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

EDYCJA autorstwa Eda Mortona (@ ghostdog74 możesz usunąć, jeśli nie zgadzasz się).

Może ta wersja z bardziej wyraźnymi nazwami zmiennych pomoże odpowiedzieć na niektóre z poniższych pytań i ogólnie wyjaśni, co robi skrypt. Używa również tabulatorów jako separatora, o który pierwotnie prosił OP, więc obsługuje puste pola i przypadkowo poprawia nieco wyjście w tym konkretnym przypadku.

$ 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

Powyższe rozwiązania będą działać w każdym awk (oprócz oczywiście starego, zepsutego awk - tam YMMV).

Powyższe rozwiązania jednak wczytują cały plik do pamięci - jeśli pliki wejściowe są na to za duże, możesz to zrobić:

$ 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

który prawie nie zużywa pamięci, ale czyta plik wejściowy raz na liczbę pól w wierszu, więc będzie on znacznie wolniejszy niż wersja, która wczytuje cały plik do pamięci. Zakłada również, że liczba pól jest taka sama w każdym wierszu i używa GNU awk dla ENDFILEi, ARGINDale każdy awk może zrobić to samo z testami na FNR==1i END.

ghostdog74
źródło
A teraz do obsługi etykiet wierszy i kolumn?
Jonathan Leffler
OK - masz rację; Twoje przykładowe dane nie pasują do przykładowych danych pytania, ale Twój kod działa dobrze na przykładowych danych pytania i daje wymagane dane wyjściowe (podaj lub weź puste w porównaniu z odstępami tabulacji). Przede wszystkim mój błąd.
Jonathan Leffler
Ciekawe czasy - zgadzam się, że w awk widzisz korzyści związane z wydajnością. Używałem MacOS X 10.5.8, który nie używa 'gawk'; i używałem Perla 5.10.1 (wersja 32-bitowa). Rozumiem, że twoje dane miały 10000 wierszy z 4 kolumnami w wierszu? W każdym razie nie ma to większego znaczenia; zarówno awk, jak i perl są realnymi rozwiązaniami (a rozwiązanie awk jest schludniejsze - „zdefiniowane” kontrole w moim Perlu są niezbędne do ostrzegania o wolnych uruchomieniach pod ścisłym / ostrzeżeniami) i żadne z nich nie jest garbem i oba prawdopodobnie będą znacznie szybsze niż oryginalne rozwiązanie skryptu powłoki.
Jonathan Leffler
Na mojej oryginalnej macierzy 2,2 GB rozwiązanie Perla jest nieco szybsze niż awk - 350,103 w porównaniu z 369,410 s. Używałem perla 5.8.8 64
Federico Giorgi
1
@ zx8754, że maksymalna liczba pól dotyczy tylko starego awk niezgodnego z POSIX. Prawdopodobnie niewiarygodnie niestety nazwany „nawk”. Nie dotyczy gawk ani innych nowoczesnych awks.
Ed Morton,
47

Inną opcją jest użycie rs:

rs -c' ' -C' ' -T

-czmienia separator kolumn wejściowych, -Czmienia separator kolumn wyjściowych i -Ttransponuje wiersze i kolumny. Nie używaj -tzamiast -T, ponieważ używa automatycznie obliczonej liczby wierszy i kolumn, która zwykle nie jest poprawna. rs, którego nazwa pochodzi od funkcji zmiany kształtu w APL, jest dostarczana z BSD i OS X, ale powinna być dostępna z menedżerów pakietów na innych platformach.

Drugą opcją jest użycie Rubiego:

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

Trzecią opcją jest użycie jq:

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

jq -R .wyświetla każdy wiersz wejściowy jako literał ciągu JSON, -s( --slurp) tworzy tablicę dla wierszy wejściowych po przeanalizowaniu każdego wiersza jako JSON, a -r( --raw-output) wyświetla zawartość ciągów zamiast literałów ciągów JSON. /Operator jest przeciążony, aby podzielić strun.

nisetama
źródło
3
Nie byłem zaznajomiony z rs- dzięki za wskaźnik! (Odsyłacz prowadzi do Debiana; strona upstream wygląda na mirbsd.org/MirOS/dist/mir/rs )
tripleee
2
@lalebarde Przynajmniej w implementacji, rsktóra pochodzi z OS X, -csam ustawia separator kolumn wejściowych na tabulator.
nisetama
2
@lalebarde, wypróbuj cytowanie ANSI-C basha, aby uzyskać znak tabulacji:$'\t'
glenn jackman
3
Jest to skrajny przypadek, ale dla bardzo dużego pliku z wieloma wierszami, np. TTC TTA TTC TTC TTTUruchamianie rs -c' ' -C' ' -T < rows.seq > cols.seqdaje rs: no memory: Cannot allocate memory. To jest system z systemem FreeBSD 11.0-RELEASE i 32 GB pamięci RAM. Domyślam się, że rsumieszcza wszystko w pamięci RAM, co jest dobre dla szybkości, ale nie dla dużych danych.
jrm
1
jq używał 21 GB pamięci RAM w pliku o wielkości 766 MB. Zabiłem go po 40 minutach bez wyjścia.
Glubbdrubb
30

Rozwiązanie w Pythonie:

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

Powyższe opiera się na następujących zasadach:

import sys

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

W tym kodzie założono, że każdy wiersz ma taką samą liczbę kolumn (wypełnienie nie jest wykonywane).

Stephan202
źródło
3
Jeden drobny problem tutaj: zamień l.split()na l.strip().split()(Python 2.7), w przeciwnym razie ostatnia linia wyjścia zostanie uszkodzona. Działa dla dowolnych separatorów kolumn, użyj l.strip().split(sep)i, sep.join(c)jeśli separator jest przechowywany w zmiennej sep.
krlmlr
21

transpozycji projektu na SourceForge jest coreutil-C jak program dokładnie to.

gcc transpose.c -o transpose
./transpose -t input > output #works with stdin, too.
latające owce
źródło
Dzięki za link. Wymaga jednak zbyt dużej ilości pamięci przy dużych matrycach / plikach.
tommy.carstensen
ma argumenty dla rozmiaru bloku i rozmiaru pola: spróbuj poprawić argumenty -bi -f.
latające owce
Domyślny rozmiar bloku (--block lub -b) to 10kb, a domyślny rozmiar pola (--fieldmax lub -f) to 64, więc to nie może być to. Próbowałem. Jednak dzięki za sugestię.
tommy.carstensen
1
Działał dobrze z plikiem CSV o rozmiarze 2 GB.
discipulus
2
W przypadku pliku macierzy o wymiarach około 11k na 5k okazało się, że transpose.c jest ~ 7x szybszy i ~ 5x bardziej wydajny w pamięci niż pierwsze rozwiązanie awk ghostdog74. Zauważyłem również, że kod awk „prawie nie używa pamięci” z ghostdog74 nie działa poprawnie. Uważaj również na flagę --limit w programie transpose.c, która domyślnie ogranicza wyjście do wymiaru 1k na 1k.
ncemami
16

Pure BASH, bez dodatkowego procesu. Niezłe ćwiczenie:

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
Fritz G. Mehner
źródło
To zadziałało dla mojego pliku, chociaż co ciekawe, wypisuje listę katalogów dla pierwszego wiersza tabeli. Nie wiem wystarczająco BASH, aby dowiedzieć się, dlaczego.
bugloaf
@bugloaf Twój stół ma * w rogu.
Hello71
2
@bugloaf: Prawidłowe cytowanie zmiennych powinno zapobiec:printf "%s\t" "${array[$COUNTER]}"
Wstrzymano do odwołania.
16

Spójrz na GNU datamash, którego można używać np datamash transpose. Przyszła wersja będzie również obsługiwać tabele krzyżowe (tabele przestawne)

pixelbeat
źródło
9

Oto umiarkowanie solidny skrypt Perla do wykonania tej pracy. Istnieje wiele strukturalnych analogii z awkrozwiązaniem @ 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";
}

Przy wielkości przykładowych danych różnica w wydajności między perl i awk była pomijalna (1 milisekunda z 7 wszystkich). Przy większym zestawie danych (macierz 100x100, wpisy 6-8 znaków każdy) Perl nieco przewyższył awk - 0,026s vs 0,042s. Żaden z nich prawdopodobnie nie będzie problemem.


Reprezentatywne czasy dla Perla 5.10.1 (32-bitowego) w porównaniu z awk (wersja 20040207 z podanym `` -V '') i gawk 3.1.7 (32-bit) w systemie MacOS X 10.5.8 w pliku zawierającym 10000 wierszy z 5 kolumnami na linia:

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: 

Zauważ, że gawk jest znacznie szybszy niż awk na tej maszynie, ale wciąż wolniejszy niż perl. Oczywiście Twój przebieg będzie się różnić.

Jonathan Leffler
źródło
w moim systemie gawk przewyższa perl. możesz zobaczyć moje wyniki w moim zredagowanym poście
ghostdog74
4
zebrane wnioski: inna platforma, inna wersja oprogramowania, różne wyniki.
ghostdog74
6

Jeśli masz sczainstalowany, możesz wykonać:

psc -r < inputfile | sc -W% - > outputfile
Wstrzymano do odwołania.
źródło
4
Zauważ, że obsługuje ograniczoną liczbę wierszy, ponieważ scnazywa swoje kolumny jako jeden lub kombinację dwóch znaków. Limit jest 26 + 26^2 = 702.
Thor
5

Zakładając, że wszystkie wiersze mają taką samą liczbę pól, ten program awk rozwiązuje problem:

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

Innymi słowy, podczas wykonywania pętli nad wierszami, dla każdego pola frośnie oddzielony ciąg znaków „:” col[f]zawierający elementy tego pola. Gdy skończysz ze wszystkimi wierszami, wydrukuj każdy z tych ciągów w osobnym wierszu. Następnie możesz zastąpić „:” separatorem, który chcesz (powiedzmy, spacją), przepuszczając wyjście tr ':' ' '.

Przykład:

$ 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
Guilherme Freitas
źródło
5

GNU datamash doskonale nadaje się do tego problemu z tylko jedną linią kodu i potencjalnie dowolnie dużymi rozmiarami plików!

datamash -W transpose infile > outfile
Kumpel
źródło
3

Tak może wyglądać hakerskie rozwiązanie w języku Perl. To fajne, ponieważ nie ładuje całego pliku w pamięci, drukuje pośrednie pliki tymczasowe, a następnie używa wspaniałej pasty

#!/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;
Federico Giorgi
źródło
używanie plików wklej i tymczasowych to tylko dodatkowe niepotrzebne operacje. możesz po prostu dokonać manipulacji w samej pamięci, np. tablice / skróty
ghostdog74
2
Tak, ale czy nie oznaczałoby to trzymania wszystkiego w pamięci? Pliki, z którymi mam do czynienia, mają rozmiar około 2-20 GB.
Federico Giorgi
3

Jedyne ulepszenie, jakie widzę w twoim własnym przykładzie, to użycie awk, który zmniejszy liczbę uruchomionych procesów i ilość danych przesyłanych między nimi:

/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
Simon C.
źródło
3

Zwykle używam tego małego awkfragmentu do tego wymagania:

  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

To po prostu ładuje wszystkie dane do dwuwymiarowej tablicy, a[line,column]a następnie drukuje je z powrotem jako a[column,line], aby transponowało dane wejście.

To musi śledzić maximum liczby kolumn, które ma plik początkowy, tak aby był używany jako liczba wierszy do wydrukowania.

fedorqui 'SO przestań szkodzić'
źródło
2

Użyłem rozwiązania fgm (dzięki fgm!), Ale musiałem wyeliminować znaki tabulacji na końcu każdego wiersza, więc zmodyfikowałem skrypt w ten sposób:

#!/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
dtw
źródło
2

Po prostu szukałem podobnego efektu bash, ale z obsługą wypełnienia. Oto skrypt, który napisałem w oparciu o rozwiązanie fgm, który wydaje się działać. Jeśli to może pomóc ...

#!/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
user3251704
źródło
2

Szukałem rozwiązania do transpozycji dowolnego rodzaju macierzy (nxn lub mxn) z dowolnymi danymi (liczbami lub danymi) i otrzymałem następujące rozwiązanie:

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
Inny. Chemik
źródło
2

Jeśli chcesz tylko pobrać pojedynczą (oddzieloną przecinkami) linię $ N z pliku i przekształcić ją w kolumnę:

head -$N file | tail -1 | tr ',' '\n'
allanbcampbell
źródło
2

Niezbyt eleganckie, ale to polecenie „jednowierszowe” szybko rozwiązuje problem:

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

Tutaj cols to liczba kolumn, w których można zamienić 4 na head -n 1 input | wc -w.

Felipe
źródło
2

Inne awkrozwiązanie i ograniczone dane wejściowe z wielkością posiadanej pamięci.

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

Spowoduje to połączenie każdej pozycji numeru pola w jedną i ENDwyświetlenie wyniku, który byłby pierwszym wierszem w pierwszej kolumnie, drugim wierszem w drugiej kolumnie itd. Wyświetli:

X row1 row2 row3 row4
column1 0 3 6 9
column2 1 4 7 10
column3 2 5 8 11
αғsнιη
źródło
2

Niektóre standardowe * nix wykorzystują jednowierszowe pliki, nie są potrzebne żadne pliki tymczasowe. Uwaga: PO chciał skutecznej poprawki (tj. Szybszej), a najlepsze odpowiedzi są zwykle szybsze niż ta odpowiedź. Te jednowierszowe są dla tych, którzy lubią narzędzia programowe * nix , z jakichkolwiek powodów. W rzadkich przypadkach ( np Ograniczone we / wy i pamięć), te fragmenty mogą być w rzeczywistości szybsze niż niektóre z najpopularniejszych odpowiedzi.

Wywołaj plik wejściowy foo .

  1. Jeśli wiemy, że foo ma cztery kolumny:

    for f in 1 2 3 4 ; do cut -d ' ' -f $f foo | xargs echo ; done
  2. Jeśli nie wiemy, ile kolumn ma foo :

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

    xargsma limit rozmiaru i dlatego niekompletna praca z długim plikiem. Jaki limit rozmiaru jest zależny od systemu, np .:

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

    Maksymalna długość polecenia, którego moglibyśmy faktycznie użyć: 2088944

  3. tr& echo:

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

    ... lub jeśli liczba kolumn jest nieznana:

    n=$(head -n 1 foo | wc -w)
    for f in $(seq 1 $n); do 
        cut -d ' ' -f $f foo | tr '\n' ' ' ; echo
    done
  4. Używanie set, które podobnie xargs, ma podobne ograniczenia związane z rozmiarem wiersza poleceń:

    for f in 1 2 3 4 ; do set - $(cut -d ' ' -f $f foo) ; echo $@ ; done
agc
źródło
2
Wszystko to byłoby o rząd wielkości wolniejsze niż rozwiązanie awk lub perl i kruche. Przeczytaj unix.stackexchange.com/questions/169716/… .
Ed Morton,
@EdMorton, dzięki, zakwalifikowane wprowadzenie do mojej odpowiedzi, aby rozwiązać Twoje obawy dotyczące szybkości. Re „kruchy”: nie 3) , ani innych, gdy programista wie, że dane są bezpieczne dla danej techniki; i czy kod powłoki zgodny z POSIX nie jest bardziej stabilnym standardem niż perl ?
agc,
przepraszam, wiem dużo o perlu. W tym przypadku narzędziem do użycia byłoby awk. cut, head, echo, Itd. Nie są bardziej zgodne z POSIX Shellcode niż awkskrypt jest - wszyscy oni są standardem w każdej instalacji systemu UNIX. Po prostu nie ma powodu, aby używać zestawu narzędzi, które w połączeniu wymagałyby od ciebie ostrożności co do zawartości twojego pliku wejściowego i katalogu, z którego wykonujesz skrypt, kiedy możesz po prostu użyć awk, a wynik końcowy jest szybszy i bardziej niezawodny .
Ed Morton,
Proszę, nie jestem anty- awk , ale warunki są różne. Powód 1: for f in cut head xargs seq awk ; do wc -c $(which $f) ; done Gdy przechowywanie jest zbyt wolne lub IO jest zbyt niskie, więksi tłumacze pogarszają sytuację, bez względu na to, jak dobrze byliby w bardziej idealnych warunkach. Powód 2: awk (lub prawie każdy inny język) również cierpi z powodu bardziej stromej krzywej uczenia się niż małe narzędzie zaprojektowane do dobrego wykonywania jednej rzeczy. Gdy czas pracy jest tańszy niż liczba godzin pracy programisty, łatwe kodowanie za pomocą „narzędzi programowych” pozwala zaoszczędzić pieniądze.
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

inna wersja z set eval

Dyno Fu
źródło
Przeczytaj unix.stackexchange.com/questions/169716/…, aby zrozumieć niektóre, ale nie wszystkie, problemy związane z tym rozwiązaniem.
Ed Morton,
1

Kolejny wariant basha

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

Scenariusz

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

Wynik

$ ./test 
XXXX    row1    row2    row3    row4    
col1    0       3       6       9   
col2    1       4       7       10  
col3    2       5       8       11
Ivan
źródło
0

Oto rozwiązanie firmy Haskell. Po skompilowaniu z -O2 działa nieco szybciej niż awk ghostdoga i nieco wolniej niż cienko opakowany c- python Stephana na moim komputerze dla powtarzających się linii wejściowych „Hello world”. Niestety GHC nie ma wsparcia dla przekazywania kodu wiersza poleceń, o ile wiem, więc będziesz musiał sam zapisać go do pliku. Obcina wiersze do długości najkrótszego rzędu.

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

main :: IO ()
main = interact $ unlines . map unwords . transpose . map words . lines
stelleg
źródło
0

Rozwiązanie awk, które przechowuje całą tablicę w pamięci

    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

Ale możemy „chodzić” po pliku tyle razy, ile potrzeba wierszy wyjściowych:

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

Który (dla małej liczby wierszy wyjściowych jest szybszy niż poprzedni kod).


źródło
0

Oto jedna linijka Bash, która polega na prostym przekonwertowaniu każdej linii na kolumnę i pastepołączeniu ich razem:

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. tworzy tmp1plik, więc nie jest pusty.

  2. czyta każdy wiersz i przekształca go w kolumnę przy użyciu tr

  3. wkleja nową kolumnę do tmp1pliku

  4. kopie wynikają z powrotem do tmp1.

PS: Naprawdę chciałem użyć deskryptorów io, ale nie mogłem ich zmusić do działania.

kirill_igum
źródło
Upewnij się, że ustawiłeś budzik, jeśli zamierzasz to wykonać na dużym pliku. Przeczytaj unix.stackexchange.com/questions/169716/…, aby zrozumieć niektóre, ale nie wszystkie, problemy związane z tym podejściem.
Ed Morton,
0

Oneliner wykorzystujący 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) "
dputhier
źródło
0

Użyłem poniżej dwóch skryptów do wykonywania podobnych operacji wcześniej. Pierwsza jest w awk, która jest o wiele szybsza niż druga w „czystym” bashu. Możesz być w stanie dostosować go do własnej aplikacji.

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
Sam
źródło