Zmień kolejność kolumn, używając opcji Cut

138

Mam plik w następującym formacie

Kolumna1 Kolumna2
str1 1
str2 2
str3 3

Chcę, aby kolumny zostały uporządkowane. Spróbowałem poniżej polecenia

cut -f2,1 plik.txt

Polecenie nie zmienia kolejności kolumn. Każdy pomysł, dlaczego nie działa?

Dziękuję Ci.

Boolean
źródło

Odpowiedzi:

153

Dla strony podręcznika cut(1):

Użyj jednego i tylko jednego z -b, -c lub -f. Każda LISTA składa się z jednego zakresu lub wielu zakresów oddzielonych przecinkami. Wybrane wejście jest zapisywane w tej samej kolejności, w jakiej jest odczytywane, i jest zapisywane dokładnie raz.

Dociera najpierw do pola 1, więc jest drukowany, a następnie pole 2.

Użyj awkzamiast tego:

awk '{ print $2 " " $1}' file.txt
Ignacio Vazquez-Abrams
źródło
14
Szkoda, cutże nie obsługuje tego intuicyjnego polecenia zmiany kolejności. Tak czy inaczej, kolejna wskazówka: można użyć awk„s -FSoraz -OFSopcje użycie niestandardowego wejścia i wyjścia (separatory polowych jak -di --output-delimiterdla cut).
malana
13
Przepraszamy, FSjest opcja, OFSto zmienna. np.awk -v OFS=";" -F"\t" '{print $2,$1}'
malana
2
Uwaga dla użytkowników systemu Windows Git Bash: jeśli masz dziwne dane wyjściowe z powyższego polecenia, wyglądające jak kolumny nadpisujące się nawzajem, winny jest powrót karetki. Zmień EOL w swoim pliku z CRLF na LF.
jakub.g
1
Alternatywnie, jeśli nie chcesz zmieniać pliku wejściowego, możesz przesłać go potokiem | sed 's/\r//' | przed przesłaniem doawk
jakub.g
2
Ten jest bardzo prosty, ale może być przydatny dla niektórych, po prostu zamień spację na \ t do zmiany kolejności kart, a jeśli chcesz więcej kolumn, możesz to zrobić na przykładawk '{print $4 "\t" $2 "\t" $6 "\t" $7}' file
FatihSarigol.
65

Możesz także łączyć cuti paste:

paste <(cut -f2 file.txt) <(cut -f1 file.txt)

poprzez komentarze: można uniknąć bashizmów i usunąć jedno wystąpienie cięcia, wykonując:

paste file.txt file.txt | cut -f2,3
Justin Kaeser
źródło
3
Nie jestem pewien, czy kwalifikuje się to jako „sprytnie”, ale: f = plik.txt wklej <(cut -f2 $ f) <(cut -f1 $ f). Zauważam również, że ta metoda jest najłatwiejsza, gdy masz dużo kolumn i chcesz poruszać się po ich dużych blokach.
Michael Rusch
nie działa z komórkami o różnej długości w tej samej kolumnie
kraymer
2
@kraymer Co masz na myśli? cutdziała dobrze w przypadku kolumn o zmiennej długości, o ile masz unikalny separator kolumn.
tripleee
1
Aby wyeliminować zbędny plik, prawdopodobnie możesz użyć tee:
JJW5432
2
Można uniknąć bashizmów i usunąć jedną instancję cut, wykonując: paste file.txt file.txt | cut -f2,3
agc
7

używając samej powłoki,

while read -r col1 col2
do
  echo $col2 $col1
done <"file"
ghostdog74
źródło
Jest to bardzo często nieefektywne. Zwykle okaże się, że odpowiedni skrypt Awk jest na przykład znacznie szybszy. Należy również uważać, aby cytować wartości "$col2"i "$col1"- w danych mogą znajdować się metaznaki powłoki lub inne sztuczki.
tripleee
7

Możesz do tego użyć Perla:

perl -ane 'print "$F[1] $F[0]\n"' < file.txt
  • Opcja -e oznacza wykonanie polecenia po nim
  • -n oznacza czytanie wiersz po wierszu (otwórz plik, w tym przypadku STDOUT i zapętlaj po wierszach)
  • -a oznacza podzielenie takich linii na wektor o nazwie @F ("F" - jak Pole). Perl indeksuje wektory zaczynające się od 0 w przeciwieństwie do cięcia, które indeksuje pola zaczynające się od 1.
  • Możesz dodać wzorzec -F (bez spacji między -F i wzorcem ), aby użyć wzorca jako separatora pól podczas odczytu pliku zamiast domyślnych białych znaków

Zaletą uruchomienia perla jest to, że (jeśli znasz Perla) możesz wykonać znacznie więcej obliczeń na F niż przestawianie kolumn.

Spotkał
źródło
perlrun (1) twierdzi, że -a domyślnie ustawia -n, ale jeśli uruchomię bez ustawienia -n, nie wydaje się zapętlać. dziwny.
Trenton
Jaka wersja? perl -ae printdziała jak catdla mnie
pwes
5

Używając join:

join -t $'\t' -o 1.2,1.1 file.txt file.txt

Uwagi:

  • -t $'\t'W GNU join bardziej intuicyjny -t '\t' bez$ zawiedzie, ( coreutils v8.28 i wcześniej?); prawdopodobnie jest to błąd, którego obejście $powinno być konieczne. Zobacz: znak separatora łączenia unixowego .

  • joinpotrzebuje dwóch nazw plików, mimo że pracujemy tylko nad jednym plikiem. Dwukrotne użycie tej samej nazwy skłania joindo wykonania żądanej czynności.

  • W przypadku systemów o niskich zasobach joinzajmuje mniej miejsca niż niektóre narzędzia używane w innych odpowiedziach:

    wc -c $(realpath `which cut join sed awk perl`) | head -n -1
      43224 /usr/bin/cut
      47320 /usr/bin/join
     109840 /bin/sed
     658072 /usr/bin/gawk
    2093624 /usr/bin/perl
    
agc
źródło
3

Właśnie pracowałem nad czymś bardzo podobnym, nie jestem ekspertem, ale pomyślałem, że podzielę się poleceniami, których użyłem. Miałem plik csv z wieloma kolumnami, z którego wymagałem tylko 4 kolumn, a następnie musiałem je zmienić.

Mój plik to pipe '|' rozdzielane, ale można je zamienić.

LC_ALL=C cut -d$'|' -f1,2,3,8,10 ./file/location.txt | sed -E "s/(.*)\|(.*)\|(.*)\|(.*)\|(.*)/\3\|\5\|\1\|\2\|\4/" > ./newcsv.csv

Trzeba przyznać, że jest naprawdę szorstki i gotowy, ale można go dostosować do własnych potrzeb!

Chris Rymer
źródło
To nie odpowiada na postawione pytanie. W duchu przepełnienia stosu poświęć czas na rozwiązanie problemu przed wysłaniem wiadomości.
Bill Gale
0

Korzystanie z sed

Użyj seda z zagnieżdżonymi podwyrażeniami podstawowych wyrażeń regularnych, aby przechwycić i zmienić kolejność zawartości kolumny. To podejście najlepiej sprawdza się, gdy istnieje ograniczona liczba cięć w celu zmiany kolejności kolumn, jak w tym przypadku.

Podstawową ideą jest otoczenie interesujących części wzorca wyszukiwania znakami \(i \), które mogą być odtwarzane we wzorcu zastępczym, \#gdzie gdzie #oznacza sekwencyjną pozycję wyrażenia podrzędnego we wzorcu wyszukiwania.

Na przykład:

$ echo "foo bar" | sed "s/\(foo\) \(bar\)/\2 \1/"

plony:

bar foo

Tekst poza podwyrażeniem jest skanowany, ale nie jest zachowywany do odtworzenia w ciągu zastępczym.

Chociaż pytanie nie dotyczyło kolumn o stałej szerokości, omówimy je tutaj, ponieważ jest to godna miara każdego przedstawionego rozwiązania. Dla uproszczenia załóżmy, że plik jest rozdzielany spacjami, chociaż rozwiązanie można rozszerzyć o inne separatory.

Zapadające się spacje

Aby zilustrować najprostsze użycie, załóżmy, że wiele spacji można zwinąć do pojedynczych spacji, a wartości drugiej kolumny są zakończone znakiem EOL (a nie spacjami).

Plik:

bash-3.2$ cat f
Column1    Column2
str1       1
str2       2
str3       3
bash-3.2$ od -a f
0000000    C   o   l   u   m   n   1  sp  sp  sp  sp   C   o   l   u   m
0000020    n   2  nl   s   t   r   1  sp  sp  sp  sp  sp  sp  sp   1  nl
0000040    s   t   r   2  sp  sp  sp  sp  sp  sp  sp   2  nl   s   t   r
0000060    3  sp  sp  sp  sp  sp  sp  sp   3  nl 
0000072

Przekształcać:

bash-3.2$ sed "s/\([^ ]*\)[ ]*\([^ ]*\)[ ]*/\2 \1/" f
Column2 Column1
1 str1
2 str2
3 str3
bash-3.2$ sed "s/\([^ ]*\)[ ]*\([^ ]*\)[ ]*/\2 \1/" f | od -a
0000000    C   o   l   u   m   n   2  sp   C   o   l   u   m   n   1  nl
0000020    1  sp   s   t   r   1  nl   2  sp   s   t   r   2  nl   3  sp
0000040    s   t   r   3  nl
0000045

Zachowywanie szerokości kolumn

Rozszerzmy teraz tę metodę do pliku z kolumnami o stałej szerokości, jednocześnie zezwalając na różne szerokości kolumn.

Plik:

bash-3.2$ cat f2
Column1    Column2
str1       1
str2       2
str3       3
bash-3.2$ od -a f2
0000000    C   o   l   u   m   n   1  sp  sp  sp  sp   C   o   l   u   m
0000020    n   2  nl   s   t   r   1  sp  sp  sp  sp  sp  sp  sp   1  sp
0000040   sp  sp  sp  sp  sp  nl   s   t   r   2  sp  sp  sp  sp  sp  sp
0000060   sp   2  sp  sp  sp  sp  sp  sp  nl   s   t   r   3  sp  sp  sp
0000100   sp  sp  sp  sp   3  sp  sp  sp  sp  sp  sp  nl
0000114

Przekształcać:

bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f2
Column2 Column1
1       str1      
2       str2      
3       str3      
bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f2 | od -a
0000000    C   o   l   u   m   n   2  sp   C   o   l   u   m   n   1  sp
0000020   sp  sp  nl   1  sp  sp  sp  sp  sp  sp  sp   s   t   r   1  sp
0000040   sp  sp  sp  sp  sp  nl   2  sp  sp  sp  sp  sp  sp  sp   s   t
0000060    r   2  sp  sp  sp  sp  sp  sp  nl   3  sp  sp  sp  sp  sp  sp
0000100   sp   s   t   r   3  sp  sp  sp  sp  sp  sp  nl 
0000114

Wreszcie, chociaż przykład pytania nie zawiera łańcuchów o nierównej długości, to wyrażenie sed potwierdza ten przypadek.

Plik:

bash-3.2$ cat f3
Column1    Column2
str1       1      
string2    2      
str3       3      

Przekształcać:

bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f3
Column2 Column1   
1       str1      
2       string2   
3       str3    
bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f3 | od -a
0000000    C   o   l   u   m   n   2  sp   C   o   l   u   m   n   1  sp
0000020   sp  sp  nl   1  sp  sp  sp  sp  sp  sp  sp   s   t   r   1  sp
0000040   sp  sp  sp  sp  sp  nl   2  sp  sp  sp  sp  sp  sp  sp   s   t
0000060    r   i   n   g   2  sp  sp  sp  nl   3  sp  sp  sp  sp  sp  sp
0000100   sp   s   t   r   3  sp  sp  sp  sp  sp  sp  nl 
0000114

Porównanie z innymi metodami zmiany kolejności kolumn pod powłoką

  • Co zaskakujące, jak na narzędzie do manipulacji plikami, awk nie nadaje się dobrze do wycinania od pola do końca rekordu. W sed można to osiągnąć za pomocą wyrażeń regularnych, np. \(xxx.*$\)Gdzie xxxjest wyrażenie pasujące do kolumny.

  • Używanie podpowłok wklejania i wycinania jest trudne podczas implementacji wewnątrz skryptów powłoki. Kod działający z wiersza poleceń nie jest analizowany po przeniesieniu do skryptu powłoki. Przynajmniej takie było moje doświadczenie (które skłoniło mnie do takiego podejścia).

Bill Gale
źródło
0

Rozwijanie odpowiedzi z @Met, również przy użyciu Perla:
Jeśli dane wejściowe i wyjściowe są rozdzielane znakami TAB:

perl -F'\t' -lane 'print join "\t", @F[1, 0]' in_file

Jeśli dane wejściowe i wyjściowe są rozdzielane spacjami:

perl -lane 'print join " ", @F[1, 0]' in_file

Tutaj
-emówi Perlowi, aby szukał kodu w tekście, a nie w oddzielnym pliku skryptu,
-nodczytuje wejście 1 wiersz na raz,
-lusuwa separator rekordów wejściowych ( \nna * NIX) po przeczytaniu wiersza (podobnie jak chomp) i dodaje wyjście separator rekordów ( \nna * NIX) do każdego print,
-adzieli wiersz wejściowy białymi znakami na tablicę @F,
-F'\t'w połączeniu z -adzieli wiersz wejściowy na TAB-ach zamiast białych znaków na tablicę @F.

@F[1, 0]to tablica składająca się z drugiego i pierwszego elementu tablicy @F, w podanej kolejności. Pamiętaj, że tablice w Perlu są indeksowane przez zero, podczas gdy pola w cutsą indeksowane 1. Więc pola w @F[0, 1]są takie same jak te w cut -f1,2.

Zwróć uwagę, że taka notacja umożliwia bardziej elastyczną manipulację danymi wejściowymi niż w niektórych innych odpowiedziach zamieszczonych powyżej (które są w porządku w przypadku prostego zadania). Na przykład:

# reverses the order of fields:
perl -F'\t' -lane 'print join "\t", reverse @F' in_file

# prints last and first fields only:
perl -F'\t' -lane 'print join "\t", @F[-1, 0]' in_file
Timur Shtatland
źródło