Riorganizzare le colonne usando il taglio


135

Sto avendo un file nel seguente formato

Colonna1 Colonna2
str1 1
str2 2
str3 3

Voglio che le colonne vengano riorganizzate. Ho provato sotto il comando

cut -f2,1 file.txt

Il comando non riordina le colonne. Qualche idea sul perché non funzioni?

Grazie.

Risposte:


148

Per la cut(1)pagina man:

Usa uno e solo uno di -b, -c o -f. Ogni ELENCO è composto da un intervallo o da molti intervalli separati da virgole. L'input selezionato viene scritto nello stesso ordine in cui viene letto e viene scritto esattamente una volta.

Raggiunge prima il campo 1, quindi viene stampato, seguito dal campo 2.

Usa awkinvece:

awk '{ print $2 " " $1}' file.txt

12
È un peccato cutche non supporti questo comando di riordino intuitivo. Comunque, un altro consiglio: puoi usare awkle opzioni -FSe le -OFSopzioni per usare separatori di campi di input e output personalizzati (like -de --output-delimiterfor cut).
Malana,

12
Siamo spiacenti, FSè un'opzione, OFSè una variabile. es.awk -v OFS=";" -F"\t" '{print $2,$1}'
malana,

2
Nota per gli utenti Windows di Git Bash: se si dispone di un output strano dal comando sopra, che assomiglia a colonne che si sovrascrivono, la colpa è del ritorno a capo. Cambia EOL nel tuo file da CRLF a LF.
jakub.g,

1
In alternativa, se non si desidera modificare il file di input, è possibile reindirizzarlo | sed 's/\r//' | prima di eseguire il piping aawk
jakub.g

2
Questo è molto semplice ma potrebbe essere utile per alcuni, è sufficiente sostituire lo spazio con \ t per riordinare le schede e, nel caso in cui si desideri più colonne, è possibile farlo come ad esempioawk '{print $4 "\t" $2 "\t" $6 "\t" $7}' file
FatihSarigol

64

Puoi anche combinare cute paste:

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

tramite commenti: è possibile evitare i bashismi e rimuovere un'istanza di taglio facendo:

paste file.txt file.txt | cut -f2,3

3
Non sono sicuro se questo si qualifica come "abilmente", ma: f = file.txt incolla <(cut -f2 $ f) <(cut -f1 $ f). Inoltre, noto che questo metodo è il più semplice quando hai molte colonne e vuoi spostarti in blocchi di grandi dimensioni.
Michael Rusch,

non funziona con celle di lunghezza variabile nella stessa colonna
kraymer

2
@kraymer Cosa intendi? cutfunziona bene per colonne di lunghezza variabile purché si disponga di un separatore di colonne univoco.
Tripleee

1
Per eliminare il file ridondante potresti probabilmente usare tee:
JJW5432

2
È possibile evitare gli bashismi e rimuovere un'istanza cutfacendo: paste file.txt file.txt | cut -f2,3
agc

7

usando solo la shell,

while read -r col1 col2
do
  echo $col2 $col1
done <"file"

Questo è molto spesso inefficiente. In genere, ad esempio, lo script Awk corrispondente è molto più veloce. Dovresti anche fare attenzione a citare i valori "$col2"e "$col1"- potrebbero esserci metacaratteri della shell o altri shenanigans nei dati.
Tripleee

7

Puoi usare Perl per questo:

perl -ane 'print "$F[1] $F[0]\n"' < file.txt
  • -e opzione significa eseguire il comando dopo di esso
  • -n significa leggere riga per riga (aprire il file, in questo caso STDOUT, e scorrere le righe)
  • -a significa dividere tali linee in un vettore chiamato @F ("F" - come Field). Perl indicizza i vettori a partire da 0 a differenza del taglio che indicizza i campi a partire dal modulo 1.
  • È possibile aggiungere il modello -F (senza spazio tra -F e pattern ) per utilizzare pattern come separatore di campo durante la lettura del file anziché lo spazio bianco predefinito

Il vantaggio di eseguire perl è che (se conosci Perl) puoi fare molto più calcolo su F che riorganizzare le colonne.


perlrun (1) afferma -a imposta in modo implicito -n ma se corro senza -n set, non sembra funzionare in loop. dispari.
Trenton,

Quale versione? perl -ae printfunziona come catper me
pwes

5

Utilizzando join:

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

Appunti:

  • -t $'\t'In GNU join il più intuitivo -t '\t' , senza il $fallisce, ( coreutils v8.28 e precedenti?); è probabilmente un bug che una soluzione alternativa $dovrebbe essere necessaria. Vedi: unix join separator char .

  • joinha bisogno di due nomi di file, anche se c'è solo un file su cui si sta lavorando. Usare lo stesso nome due volte joinper eseguire l'azione desiderata.

  • Per i sistemi con risorse limitate joinoffre un footprint inferiore rispetto ad alcuni degli strumenti utilizzati in altre risposte:

    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

3

Ho appena lavorato su qualcosa di molto simile, non sono un esperto ma ho pensato di condividere i comandi che ho usato. Avevo un CSV multi colonna di cui avevo bisogno solo di 4 colonne e quindi avevo bisogno di riordinarle.

Il mio file era pipe '|' delimitato ma che può essere sostituito.

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

Certo, è davvero grezzo e pronto, ma può essere modificato per adattarlo!


Questo non risponde alla domanda posta. Nello spirito di overflow dello stack, ti ​​preghiamo di dedicare il tempo per rispondere a un problema prima di pubblicare.
Bill Gale,

0

Usando sed

Utilizzare sed con le sottoespressioni nidificate dell'espressione regolare di base per acquisire e riordinare il contenuto della colonna. Questo approccio è più adatto in presenza di un numero limitato di tagli per riordinare le colonne, come in questo caso.

L'idea di base è di circondare parti interessanti del modello di ricerca con \(e \), che possono essere riprodotte nel modello di sostituzione con \#dove #rappresenta la posizione sequenziale della sottoespressione nel modello di ricerca.

Per esempio:

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

rendimenti:

bar foo

Il testo esterno a una sottoespressione viene scansionato ma non conservato per la riproduzione nella stringa di sostituzione.

Sebbene la domanda non abbia discusso colonne a larghezza fissa, ne discuteremo qui poiché si tratta di una misura degna di qualsiasi soluzione proposta. Per semplicità supponiamo che il file sia delimitato da spazi sebbene la soluzione possa essere estesa per altri delimitatori.

Spazi collassanti

Per illustrare l'utilizzo più semplice, supponiamo che più spazi possano essere compressi in spazi singoli e che i valori della seconda colonna siano terminati con EOL (e non riempito di spazio).

File:

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

Trasformare:

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

Preservare le larghezze delle colonne

Ora estendiamo il metodo a un file con colonne a larghezza costante, consentendo allo stesso tempo che le colonne abbiano larghezze diverse.

File:

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

Trasformare:

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

Infine, sebbene l'esempio della domanda non abbia stringhe di lunghezza diversa, questa espressione sed supporta questo caso.

File:

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

Trasformare:

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

Confronto con altri metodi di riordino delle colonne sotto shell

  • Sorprendentemente per uno strumento di manipolazione dei file, awk non è adatto per tagliare da un campo alla fine della registrazione. In sed questo può essere realizzato usando espressioni regolari, ad esempio \(xxx.*$\)dove si xxxtrova l'espressione da abbinare alla colonna.

  • L'uso di incolla e taglia sottotitoli diventa complicato quando si implementano script di shell. Il codice che funziona dalla riga di comando non riesce ad analizzare quando viene portato all'interno di uno script di shell. Almeno questa è stata la mia esperienza (che mi ha spinto a questo approccio).


0

Espandendo la risposta di @Met, anche usando Perl:
se l'input e l'output sono delimitati da TAB:

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

Se l'input e l'output sono delimitati da spazi bianchi:

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

Qui,
-edice a Perl di cercare il codice inline, piuttosto che in un file di script separato,
-nlegge l'input 1 riga alla volta,
-lrimuove il separatore del record di input ( \nsu * NIX) dopo aver letto la riga (simile a chomp) e aggiunge output il separatore di record ( \nsu * NIX) per ciascuno print,
-adivide la linea di input su uno spazio bianco in un array @F,
-F'\t'in combinazione con -adivide la linea di input su TAB, anziché uno spazio su un array @F.

@F[1, 0]è l'array composto dal 2o e 1o elemento dell'array @F, in questo ordine. Ricorda che gli array in Perl sono indicizzati zero, mentre i campi in cutsono 1 indicizzati. Quindi i campi in @F[0, 1]sono gli stessi campi incut -f1,2 .

Si noti che tale notazione consente una manipolazione più flessibile dell'input rispetto ad alcune altre risposte postate sopra (che vanno bene per un compito semplice). Per esempio:

# 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.