Stampa tutto tranne le prime tre colonne


112

Troppo ingombrante:

awk '{print " "$4" "$5" "$6" "$7" "$8" "$9" "$10" "$11" "$12" "$13}' things

43
C'è qualche motivo per cui non puoi semplicemente usare cut -f3-?
Cascabel

1
@hhh bella .. mi piace l'idea di una risposta sommaria.
Chris Seymour

2
@Jefromi - perché ci sono problemi di buffering linea con il taglio, che awk non ha: stackoverflow.com/questions/14360640/...
sdaau


@Jefromi - inoltre cutnon ha regex prima delle {}azioni, quindi è molto più stupido con delimitatori di campo (numero variabile di spazi?), E devi specificarli manualmente. Penso che l'OP volesse sentire parlare di qualche shift Ncomando, che non esiste. Il più vicino è $1="";$2="";(...);print}, ma nel mio caso lascia alcuni spazi iniziali (probabilmente separatori).
Tomasz Gandor

Risposte:


50

Una soluzione che non aggiunge spazi bianchi iniziali o finali extra :

awk '{ for(i=4; i<NF; i++) printf "%s",$i OFS; if(NF) printf "%s",$NF; printf ORS}'

### Example ###
$ echo '1 2 3 4 5 6 7' |
  awk '{for(i=4;i<NF;i++)printf"%s",$i OFS;if(NF)printf"%s",$NF;printf ORS}' |
  tr ' ' '-'
4-5-6-7

Sudo_O propone un elegante miglioramento utilizzando l'operatore ternarioNF?ORS:OFS

$ echo '1 2 3 4 5 6 7' |
  awk '{ for(i=4; i<=NF; i++) printf "%s",$i (i==NF?ORS:OFS) }' |
  tr ' ' '-'
4-5-6-7

EdMorton offre una soluzione che preserva gli spazi bianchi originali tra i campi:

$ echo '1   2 3 4   5    6 7' |
  awk '{ sub(/([^ ]+ +){3}/,"") }1' |
  tr ' ' '-'
4---5----6-7

BinaryZebra fornisce anche due fantastiche soluzioni:
(queste soluzioni preservano anche gli spazi finali dalla stringa originale)

$ echo -e ' 1   2\t \t3     4   5   6 7 \t 8\t ' |
  awk -v n=3 '{ for ( i=1; i<=n; i++) { sub("^["FS"]*[^"FS"]+["FS"]+","",$0);} } 1 ' |
  sed 's/ /./g;s/\t/->/g;s/^/"/;s/$/"/'
"4...5...6.7.->.8->."

$ echo -e ' 1   2\t \t3     4   5   6 7 \t 8\t ' |
  awk -v n=3 '{ print gensub("["FS"]*([^"FS"]+["FS"]+){"n"}","",1); }' |
  sed 's/ /./g;s/\t/->/g;s/^/"/;s/$/"/'
"4...5...6.7.->.8->."

La soluzione data da larsr nei commenti è quasi corretta:

$ echo '1 2 3 4 5 6 7' | 
  awk '{for (i=3;i<=NF;i++) $(i-2)=$i; NF=NF-2; print $0}' | tr  ' ' '-'
3-4-5-6-7

Questa è la versione fissa e parametrizzata della soluzione larsr :

$ echo '1 2 3 4 5 6 7' | 
  awk '{for(i=n;i<=NF;i++)$(i-(n-1))=$i;NF=NF-(n-1);print $0}' n=4 | tr ' ' '-'
4-5-6-7

Tutte le altre risposte prima di settembre 2013 sono belle ma aggiungono spazi extra:


La risposta di EdMorton non ha funzionato per me (bash 4.1.2 (1) -release, GNU Awk 3.1.7 o bash 3.2.25 (1) -release, GNU Awk 3.1.5) ma si trova qui in un altro modo:echo ' This is a test' | awk '{print substr($0, index($0,$3))}'
elysch

1
@elysch no, non funzionerà in generale, sembra funzionare solo dati alcuni valori di input specifici. Vedi il commento che ho aggiunto sotto il tuo commento sotto la mia risposta.
Ed Morton

1
Ciao @fedorqui. La mia risposta è la prima. Nella mia risposta originale stavo spiegando perché l'altra risposta non era corretta (spazi bianchi extra iniziali o finali). Alcune persone hanno proposto miglioramenti all'interno dei commenti. Abbiamo chiesto all'OP di scegliere una risposta più corretta e lui / lei ha selezionato la mia. Dopo che alcuni altri contributori hanno modificato la mia risposta per fare riferimento alla risposta (vedere la cronologia). È chiaro per te? Cosa mi consigli per migliorare la comprensibilità della mia risposta? Saluti ;-)
olibre

1
Hai assolutamente ragione e mi dispiace molto per il mio malinteso. Ho letto velocemente la risposta e non ho notato la tua risposta originale (sì, ho letto troppo velocemente). +1 per la risposta stessa usando il simpatico trucco di eseguire il loop fino a NF-1 e quindi stampare l'ultimo elemento per evitare spazi bianchi extra. E scusa ancora! (rimuoverò il mio commento in un giorno o giù di lì, per evitare incomprensioni da futuri lettori).
fedorqui 'SO stop harming'

1
Vorrei utilizzare una sorta di intestazione: <la tua risposta> e poi una regola orizzontale seguita da un grande titolo "confronto delle altre risposte". Altrimenti, sposta questo confronto su un'altra risposta, poiché apparentemente le persone tendono a preferire risposte brevi in ​​una visione "dammi il mio codice"
:)

75
awk '{for(i=1;i<4;i++) $i="";print}' file

4
Questo lascerebbe il primo posto in OFSquanto non si ha a che fare con lo NFspazio iniziale nei record.
Chris Seymour

70

usa il taglio

$ cut -f4-13 file

o se insisti su awk e $ 13 è l'ultimo campo

$ awk '{$1=$2=$3="";print}' file

altro

$ awk '{for(i=4;i<=13;i++)printf "%s ",$i;printf "\n"}' file

14
probabilmente è meglio usare "NF" di "13" nell'ultimo esempio.
glenn jackman

2
2 scenario che spetta all'OP decidere. se 13 è l'ultimo campo, usare NF va bene. In caso contrario, utilizzare 13 è appropriato.
ghostdog74

3
Il secondo deve eliminare 3 copie di OFS dall'inizio di $ 0. Terzo sarebbe meglio con printf "%s ",$i, dal momento che non sai se $ipotrebbe contenere %so simili. Ma questo stamperebbe uno spazio extra alla fine.
dubiousjim

38

Prova questo:

awk '{ $1=""; $2=""; $3=""; print $0 }'

1
Questo è bello per la sua dinamica. Puoi aggiungere colonne alla fine e non riscrivere i tuoi script.
MinceMan

1
Questo dimostra il problema esatto che la domanda sta cercando di affrontare, fai semplicemente il contrario. Che ne dici di stampare dal 100 ° campo? Nota per menzionare che non ti occupi, NFquindi lasci il comando OFS.
Chris Seymour

24

Il modo corretto per farlo è con un intervallo RE perché ti consente di indicare semplicemente quanti campi saltare e mantiene la spaziatura tra i campi per i campi rimanenti.

ad esempio, saltare i primi 3 campi senza influenzare la spaziatura tra i campi rimanenti dato che il formato di input che sembriamo discutere in questa domanda è semplicemente:

$ echo '1   2 3 4   5    6' |
awk '{sub(/([^ ]+ +){3}/,"")}1'
4   5    6

Se vuoi inserire spazi iniziali e non vuoti, ma di nuovo con il FS predefinito, allora è:

$ echo '  1   2 3 4   5    6' |
awk '{sub(/[[:space:]]*([^[:space:]]+[[:space:]]+){3}/,"")}1'
4   5    6

Se hai una FS che è una RE che non puoi negare in un set di caratteri, puoi convertirla prima in un singolo carattere (RS è l'ideale se è un singolo carattere poiché un RS NON PU apparire all'interno di un campo, altrimenti considera SUBSEP), quindi applicare la sostituzione dell'intervallo RE, quindi convertire in OFS. es. se catene di "." s separano i campi:

$ echo '1...2.3.4...5....6' |
awk -F'[.]+' '{gsub(FS,RS);sub("([^"RS"]+["RS"]+){3}","");gsub(RS,OFS)}1'
4 5 6

Ovviamente se OFS è un singolo carattere E non può apparire nei campi di input puoi ridurlo a:

$ echo '1...2.3.4...5....6' |
awk -F'[.]+' '{gsub(FS,OFS); sub("([^"OFS"]+["OFS"]+){3}","")}1'
4 5 6

Quindi hai lo stesso problema di tutte le soluzioni basate su loop che riassegnano i campi: gli FS vengono convertiti in OFS. Se questo è un problema, è necessario esaminare la funzione patsplit () di GNU awks.


Non ha funzionato per me (bash 4.1.2 (1) -release, GNU Awk 3.1.7 o bash 3.2.25 (1) -release, GNU Awk 3.1.5) ma echo ' This is a test' | awk '{print substr($0, index($0,$3))}'
ho

2
No, fallirà se $ 1 o $ 2 contengono la stringa su cui è impostato $ 3. Prova, ad esempio, echo ' That is a test' | awk '{print substr($0, index($0,$3))}'e scoprirai che il valore di a$ 3 corrisponde aall'interno Thatdi $ 1. In una versione molto vecchia di gawk come la tua, devi abilitare gli intervalli RE con la bandiera --re-interval.
Ed Morton

2
Hai ragione, non l'ho notato. A proposito, apprezzo molto il tuo commento. Molte volte ho voluto usare una regex con "{}" per specificare il numero di elementi e non ho mai visto "--re-interval" nell'uomo. +1 per te.
elysch

1
1è una condizione vera e quindi invoca l'azione awk predefinita di stampare il record corrente.
Ed Morton

1
idk quanto sia canonico ma ho aggiunto una risposta ora.
Ed Morton

10

Praticamente tutte le risposte attualmente aggiungono spazi iniziali, spazi finali o qualche altro problema di separazione. Per selezionare dal quarto campo in cui il separatore è uno spazio bianco e il separatore di output è un singolo spazio utilizzando awksarebbe:

awk '{for(i=4;i<=NF;i++)printf "%s",$i (i==NF?ORS:OFS)}' file

Per parametrizzare il campo di partenza potresti fare:

awk '{for(i=n;i<=NF;i++)printf "%s",$i (i==NF?ORS:OFS)}' n=4 file

E anche il campo finale:

awk '{for(i=n;i<=m=(m>NF?NF:m);i++)printf "%s",$i (i==m?ORS:OFS)}' n=4 m=10 file

6
awk '{$1=$2=$3="";$0=$0;$1=$1}1'

Ingresso

1 2 3 4 5 6 7

Produzione

4 5 6 7

4
echo 1 2 3 4 5| awk '{ for (i=3; i<=NF; i++) print $i }'

3
Oppure, per metterli sulla stessa riga, assegnare $ 3 a $ 1, ecc. E quindi modificare NF con il numero corretto di campi. echo 1 2 3 4 5| awk '{ for (i=3; i<=NF; i++) $(i-2)=$i; NF=NF-2; print $0 }'
larsr

Ciao @larsr. La riga di comando proposta è l'unica risposta corretta. Tutte le altre risposte aggiungono spazi extra (iniziali o finali). Si prega di inserire la riga di comando all'interno di una nuova risposta, la voterò a favore ;-)
olibre

1
Ciao @sudo_O, stavo parlando con @larsr, della riga di comando che ha proposto nel suo commento. Ho impiegato circa cinque minuti prima di capire il quiproco (incomprensione). Sono d'accordo, la risposta di @Vetsin inserisce nuove righe ( ORS) tra i campi. Bravo per la tua iniziativa (mi piace la tua risposta). Saluti
olibre

3

Un altro modo per evitare di utilizzare l'istruzione print:

 $ awk '{$1=$2=$3=""}sub("^"FS"*","")' file

In awk quando una condizione è vera print è l'azione predefinita.


Questo ha tutti i problemi che ha la risposta di @lhf .. è solo più breve.
Chris Seymour

Ottima idea;) Meglio della mia risposta! (Ho già votato contro la tua risposta l'anno scorso) Cheers
olibre

Dovrebbe essere: awk '{$1=$2=$3=""}sub("^"OFS"+","")' filecome l'OFS ciò che rimane dopo aver modificato i contenuti di $ 1, $ 2 e $ 3.

3

Non posso credere che nessuno abbia offerto un semplice guscio:

while read -r a b c d; do echo "$d"; done < file

+1 per la soluzione simile ... Ma questo potrebbe avere problemi di prestazioni se fileè grande (> 10-30 KiB). Per file di grandi dimensioni la awksoluzione offre prestazioni migliori.
TrueY

3

Le opzioni da 1 a 3 hanno problemi con più spazi bianchi (ma sono semplici). Questo è il motivo per sviluppare le opzioni 4 e 5, che elaborano più spazi bianchi senza problemi. Ovviamente, se le opzioni 4 o 5 vengono utilizzate con n=0entrambe, manterrà qualsiasi spazio bianco iniziale comen=0 non suddivisi.

opzione 1

Una semplice soluzione di taglio (funziona con singoli delimitatori):

$ echo '1 2 3 4 5 6 7 8' | cut -d' ' -f4-
4 5 6 7 8

opzione 2

Forzare un ricalcolo di awk a volte risolve il problema (funziona con alcune versioni di awk) degli spazi iniziali aggiunti:

$ echo '1 2 3 4 5 6 7 8' | awk '{ $1=$2=$3="";$0=$0;} NF=NF'
4 5 6 7 8

Opzione 3

La stampa di ogni campo formattato con printfdarà più controllo:

$ echo '    1    2  3     4   5   6 7     8  ' |
  awk -v n=3 '{ for (i=n+1; i<=NF; i++){printf("%s%s",$i,i==NF?RS:OFS);} }'
4 5 6 7 8

Tuttavia, tutte le risposte precedenti modificano tutte le FS tra i campi in OFS. Creiamo un paio di soluzioni a questo.

Opzione 4

Un ciclo con sottotitoli per rimuovere campi e delimitatori è più portabile e non attiva una modifica di FS in OFS:

$ echo '    1    2  3     4   5   6 7     8  ' |
awk -v n=3 '{ for(i=1;i<=n;i++) { sub("^["FS"]*[^"FS"]+["FS"]+","",$0);} } 1 '
4   5   6 7     8

NOTA: "^ [" FS "] *" accetta un input con spazi iniziali.

Opzione 5

È del tutto possibile creare una soluzione che non aggiunga spazi bianchi iniziali o finali extra e preservare gli spazi bianchi esistenti usando la funzione gensubdi GNU awk, come questo:

$ echo '    1    2  3     4   5   6 7     8  ' |
awk -v n=3 '{ print gensub("["FS"]*([^"FS"]+["FS"]+){"n"}","",1); }'
4   5   6 7     8 

Può anche essere utilizzato per scambiare un elenco di campi dato un conteggio n:

$ echo '    1    2  3     4   5   6 7     8  ' |
  awk -v n=3 '{ a=gensub("["FS"]*([^"FS"]+["FS"]+){"n"}","",1);
                b=gensub("^(.*)("a")","\\1",1);
                print "|"a"|","!"b"!";
               }'
|4   5   6 7     8  | !    1    2  3     !

Naturalmente, in tal caso, l'OFS viene utilizzato per separare entrambe le parti della riga e lo spazio bianco finale dei campi viene ancora stampato.

Nota 1: ["FS"]* viene utilizzato per consentire spazi iniziali nella riga di input.


Ciao BZ La tua risposta è carina. Ma l'opzione 3 non funziona su stringhe che iniziano con uno spazio (ad esempio " 1 2 3 4 5 6 7 8 "). L'opzione 4 è carina ma lascia uno spazio iniziale usando una stringa che inizia con uno spazio. Pensi che sia risolvibile? Puoi usare il comando echo " 1 2 3 4 5 6 7 8 " | your awk script | sed 's/ /./g;s/\t/->/g;s/^/"/;s/$/"/'per verificare gli spazi
iniziali

Ciao @olibre. Il fatto che l'opzione 3 fallisca con uno spazio bianco è la ragione per sviluppare le opzioni 4 e 5. L'opzione 4 lascia uno spazio iniziale solo se l'input lo ha e n è impostato a 0 (n = 0). Questa credo sia la risposta corretta quando non c'è selezione di campi (niente per correggere IMO). Saluti.

Tutto ok. Grazie per le informazioni aggiuntive :-) Migliora la tua risposta fornendo queste informazioni extra :-) Saluti
olibre

Perfetto :-) Che peccato che il tuo utente sia disabilitato :-(
olibre

1

Cut ha un flag --complement che rende facile (e veloce) eliminare le colonne. La sintassi risultante è analoga a ciò che si desidera fare, rendendo la soluzione più facile da leggere / comprendere. Il complemento funziona anche nel caso in cui desideri eliminare colonne non contigue.

$ foo='1 2 3 %s 5 6 7'
$ echo "$foo" | cut --complement -d' ' -f1-3
%s 5 6 7
$

Puoi spiegare di più la tua risposta per favore?
Zulu

La modifica di cui sopra aiuta nella comprensione? Il punto è usare la bandiera di taglio del complemento. La soluzione dovrebbe essere un'implementazione più rapida e concisa rispetto alle soluzioni basate su AWK o perl. Inoltre, è possibile tagliare colonne arbitrarie.
Michael Back

1

Soluzione Perl che non aggiunge spazi bianchi iniziali o finali:

perl -lane 'splice @F,0,3; print join " ",@F' file

L' @Farray perl autosplit inizia all'indice 0mentre i campi awk iniziano con$1


Soluzione Perl per dati delimitati da virgole:

perl -F, -lane 'splice @F,0,3; print join ",",@F' file

Soluzione Python:

python -c "import sys;[sys.stdout.write(' '.join(line.split()[3:]) + '\n') for line in sys.stdin]" < file


0

Per me la soluzione più compatta e conforme alla richiesta è

$ a='1   2\t \t3     4   5   6 7 \t 8\t '; 
$ echo -e "$a" | awk -v n=3 '{while (i<n) {i++; sub($1 FS"*", "")}; print $0}'

E se hai più righe da elaborare come ad esempio il file foo.txt , non dimenticare di reimpostare i su 0:

$ awk -v n=3 '{i=0; while (i<n) {i++; sub($1 FS"*", "")}; print $0}' foo.txt

Grazie del tuo forum.


0

Dato che ero infastidito dalla prima risposta altamente votata ma sbagliata, ho trovato abbastanza per scrivere una risposta lì, e qui le risposte sbagliate sono contrassegnate come tali, ecco la mia parte. Non mi piacciono le soluzioni proposte perché non vedo motivo per rendere la risposta così complessa.

Ho un registro in cui dopo $ 5 con un indirizzo IP può essere più testo o nessun testo. Ho bisogno di tutto, dall'indirizzo IP alla fine della riga se ci fosse qualcosa dopo $ 5. Nel mio caso, questo è effettivamente con un programma awk, non un oneliner awk, quindi awk deve risolvere il problema. Quando provo a rimuovere i primi 4 campi usando la vecchia risposta carina e più votata ma completamente sbagliata:

echo "  7 27.10.16. Thu 11:57:18 37.244.182.218 one two three" | awk '{$1=$2=$3=$4=""; printf "[%s]\n", $0}'

sputa una risposta sbagliata e inutile (ho aggiunto [] per dimostrare):

[    37.244.182.218 one two three]

Invece, se le colonne hanno una larghezza fissa fino a quando il punto di taglio e awk è necessario, la risposta corretta e abbastanza semplice è:

echo "  7 27.10.16. Thu 11:57:18 37.244.182.218 one two three" | awk '{printf "[%s]\n", substr($0,28)}'

che produce l'output desiderato:

[37.244.182.218 one two three]

0

Ho trovato quest'altra possibilità, forse potrebbe essere utile anche ...

awk 'BEGIN {OFS=ORS="\t" }; {for(i=1; i<14; i++) print $i " "; print $NF "\n" }' your_file

Nota: 1. Per dati tabulari e dalla colonna $ 1 a $ 14


0

Usa taglio:

cut -d <The character between characters> -f <number of first column>,<number of last column> <file name>

ad esempio: se hai file1contenuto:car.is.nice.equal.bmw

Esegui: cut -d . -f1,3 file1 verrà stampatocar.is.nice


Sembra che la tua soluzione potrebbe essere arretrata. Si prega di rivedere il titolo della domanda Stampa tutte * tranne * le prime tre colonne
Stefan Crain

-1

Questo non è molto lontano da alcune delle risposte precedenti, ma risolve un paio di problemi:

cols.sh:

#!/bin/bash
awk -v s=$1 '{for(i=s; i<=NF;i++) printf "%-5s", $i; print "" }'

Che ora puoi chiamare con un argomento che sarà la colonna di partenza:

$ echo "1 2 3 4 5 6 7 8 9 10 11 12 13 14" | ./cols.sh 3 
3    4    5    6    7    8    9    10   11   12   13   14

O:

$ echo "1 2 3 4 5 6 7 8 9 10 11 12 13 14" | ./cols.sh 7 
7    8    9    10   11   12   13   14

Questo è 1 indicizzato; se preferisci zero indicizzato, usa i=s + 1invece.

Inoltre, se desideri avere argomenti per l'indice iniziale e l' indice finale, modifica il file in:

#!/bin/bash
awk -v s=$1 -v e=$2 '{for(i=s; i<=e;i++) printf "%-5s", $i; print "" }'

Per esempio:

$ echo "1 2 3 4 5 6 7 8 9 10 11 12 13 14" | ./cols.sh 7 9 
7    8    9

Il %-5sallinea il risultato come colonne di 5 caratteri; se questo non è sufficiente, aumenta il numero o usa %s(con uno spazio) invece se non ti interessa l'allineamento.


-1

Soluzione basata su printf di AWK che evita% problem ed è unica in quanto non restituisce nulla (nessun carattere di ritorno) se ci sono meno di 4 colonne da stampare:

awk 'NF > 3 { for(i=4; i<NF; i++) printf("%s ", $(i)); print $(i) }'

test:

$ x='1 2 3 %s 4 5 6'
$ echo "$x" | awk 'NF > 3 { for(i=4; i<NF; i++) printf("%s ", $(i)); print $(i) }'
%s 4 5 6
$ x='1 2 3'
$ echo "$x" | awk 'NF > 3 { for(i=4; i<NF; i++) printf("%s ", $(i)); print $(i) }'
$ x='1 2 3 '
$ echo "$x" | awk 'NF > 3 { for(i=4; i<NF; i++) printf("%s ", $(i)); print $(i) }'
$
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.