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? :-)
Odpowiedzi:
wynik
Wydajność w porównaniu z rozwiązaniem Perla autorstwa Jonathana w pliku zawierającym 10000 linii
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.
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ć:
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
ENDFILE
i,ARGIND
ale każdy awk może zrobić to samo z testami naFNR==1
iEND
.źródło
Inną opcją jest użycie
rs
:-c
zmienia separator kolumn wejściowych,-C
zmienia separator kolumn wyjściowych i-T
transponuje wiersze i kolumny. Nie używaj-t
zamiast-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:
Trzecią opcją jest użycie
jq
: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.źródło
rs
- dzięki za wskaźnik! (Odsyłacz prowadzi do Debiana; strona upstream wygląda na mirbsd.org/MirOS/dist/mir/rs )rs
która pochodzi z OS X,-c
sam ustawia separator kolumn wejściowych na tabulator.$'\t'
TTC TTA TTC TTC TTT
Uruchamianiers -c' ' -C' ' -T < rows.seq > cols.seq
dajers: no memory: Cannot allocate memory
. To jest system z systemem FreeBSD 11.0-RELEASE i 32 GB pamięci RAM. Domyślam się, żers
umieszcza wszystko w pamięci RAM, co jest dobre dla szybkości, ale nie dla dużych danych.Rozwiązanie w Pythonie:
Powyższe opiera się na następujących zasadach:
W tym kodzie założono, że każdy wiersz ma taką samą liczbę kolumn (wypełnienie nie jest wykonywane).
źródło
l.split()
nal.strip().split()
(Python 2.7), w przeciwnym razie ostatnia linia wyjścia zostanie uszkodzona. Działa dla dowolnych separatorów kolumn, użyjl.strip().split(sep)
i,sep.join(c)
jeśli separator jest przechowywany w zmiennejsep
.transpozycji projektu na SourceForge jest coreutil-C jak program dokładnie to.
źródło
-b
i-f
.Pure BASH, bez dodatkowego procesu. Niezłe ćwiczenie:
źródło
printf "%s\t" "${array[$COUNTER]}"
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)źródło
Oto umiarkowanie solidny skrypt Perla do wykonania tej pracy. Istnieje wiele strukturalnych analogii z
awk
rozwiązaniem @ ghostdog74 .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:
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ć.
źródło
Jeśli masz
sc
zainstalowany, możesz wykonać:źródło
sc
nazywa swoje kolumny jako jeden lub kombinację dwóch znaków. Limit jest26 + 26^2 = 702
.Jest do tego celu zbudowane narzędzie,
Narzędzie GNU Datamash
Zaczerpnięte z tej witryny, https://www.gnu.org/software/datamash/ i http://www.thelinuxrain.com/articles/transposing-rows-and-columns-3-methods
źródło
Zakładając, że wszystkie wiersze mają taką samą liczbę pól, ten program awk rozwiązuje problem:
Innymi słowy, podczas wykonywania pętli nad wierszami, dla każdego pola
f
roś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ścietr ':' ' '
.Przykład:
źródło
GNU datamash doskonale nadaje się do tego problemu z tylko jedną linią kodu i potencjalnie dowolnie dużymi rozmiarami plików!
źródło
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
źródło
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:
źródło
Zwykle używam tego małego
awk
fragmentu do tego wymagania:To po prostu ładuje wszystkie dane do dwuwymiarowej tablicy,
a[line,column]
a następnie drukuje je z powrotem jakoa[column,line]
, aby transponowało dane wejście.To musi śledzić
max
imum liczby kolumn, które ma plik początkowy, tak aby był używany jako liczba wierszy do wydrukowania.źródło
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:
źródło
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 ...
źródło
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:
źródło
Jeśli chcesz tylko pobrać pojedynczą (oddzieloną przecinkami) linię $ N z pliku i przekształcić ją w kolumnę:
źródło
Niezbyt eleganckie, ale to polecenie „jednowierszowe” szybko rozwiązuje problem:
Tutaj cols to liczba kolumn, w których można zamienić 4 na
head -n 1 input | wc -w
.źródło
Inne
awk
rozwiązanie i ograniczone dane wejściowe z wielkością posiadanej pamięci.Spowoduje to połączenie każdej pozycji numeru pola w jedną i
END
wyświetlenie wyniku, który byłby pierwszym wierszem w pierwszej kolumnie, drugim wierszem w drugiej kolumnie itd. Wyświetli:źródło
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 .
Jeśli wiemy, że foo ma cztery kolumny:
Jeśli nie wiemy, ile kolumn ma foo :
xargs
ma limit rozmiaru i dlatego niekompletna praca z długim plikiem. Jaki limit rozmiaru jest zależny od systemu, np .:tr
&echo
:... lub jeśli liczba kolumn jest nieznana:
Używanie
set
, które podobniexargs
, ma podobne ograniczenia związane z rozmiarem wiersza poleceń:źródło
awk
.cut
,head
,echo
, Itd. Nie są bardziej zgodne z POSIX Shellcode niżawk
skrypt 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 .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.inna wersja z
set
eval
źródło
Kolejny wariant basha
Scenariusz
Wynik
źródło
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.źródło
Rozwiązanie awk, które przechowuje całą tablicę w pamięci
Ale możemy „chodzić” po pliku tyle razy, ile potrzeba wierszy wyjściowych:
Który (dla małej liczby wierszy wyjściowych jest szybszy niż poprzedni kod).
źródło
Oto jedna linijka Bash, która polega na prostym przekonwertowaniu każdej linii na kolumnę i
paste
połączeniu ich razem:m.txt:
tworzy
tmp1
plik, więc nie jest pusty.czyta każdy wiersz i przekształca go w kolumnę przy użyciu
tr
wkleja nową kolumnę do
tmp1
plikukopie wynikają z powrotem do
tmp1
.PS: Naprawdę chciałem użyć deskryptorów io, ale nie mogłem ich zmusić do działania.
źródło
Oneliner wykorzystujący R ...
źródło
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.
źródło