Imprimer tout sauf les trois premières colonnes

112

Trop encombrant:

awk '{print " "$4" "$5" "$6" "$7" "$8" "$9" "$10" "$11" "$12" "$13}' things
hhh
la source
43
Y a-t-il une raison pour laquelle vous ne pouvez pas simplement utiliser cut -f3-?
Cascabel
1
@hhh gentil .. J'aime l'idée d'une réponse sommaire.
Chris Seymour
2
@Jefromi - parce qu'il y a des problèmes de mise en mémoire tampon de ligne avec cut, ce que awk n'a pas: stackoverflow.com/questions/14360640/…
sdaau
@Jefromi - cutn'a pas non plus de regex avant les {}actions, et puis c'est beaucoup plus stupide avec les délimiteurs de champ (nombre variable d'espaces?), Et vous devez les spécifier manuellement. Je pense que le PO voulait entendre parler d'une shift Ncommande, qui n'existe pas. Le plus proche est $1="";$2="";(...);print}, mais dans mon cas, il laisse des espaces au début (probablement des séparateurs).
Tomasz Gandor

Réponses:

50

Une solution qui n'ajoute pas d' espaces blancs supplémentaires de début ou de fin :

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 propose une amélioration élégante utilisant l'opérateur ternaireNF?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 donne une solution préservant les espaces d'origine entre les champs:

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

BinaryZebra fournit également deux solutions géniales:
(ces solutions préservent même les espaces de fin de la chaîne d'origine)

$ 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 solution donnée par larsr dans les commentaires est presque correcte:

$ 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

Voici la version fixe et paramétrée de la solution 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

Toutes les autres réponses avant septembre 2013 sont bien mais ajoutez des espaces supplémentaires:

olibre
la source
La réponse d'EdMorton n'a pas fonctionné pour moi (bash 4.1.2 (1) -release, GNU Awk 3.1.7 ou bash 3.2.25 (1) -release, GNU Awk 3.1.5) mais trouvé ici une autre façon:echo ' This is a test' | awk '{print substr($0, index($0,$3))}'
elysch
1
@elysch non, cela ne fonctionnera pas en général, cela semble juste fonctionner étant donné certaines valeurs d'entrée spécifiques. Voir le commentaire que j'ai ajouté ci-dessous votre commentaire sous ma réponse.
Ed Morton
1
Salut @fedorqui. Ma réponse est la première. Dans ma réponse initiale, j'expliquais pourquoi l'autre réponse n'était pas correcte (espace blanc supplémentaire de début ou de fin). Certaines personnes ont proposé des améliorations dans les commentaires. Nous avons demandé au PO de choisir une réponse plus correcte et il / elle a choisi la mienne. Après que d'autres contributeurs aient édité ma réponse pour y faire référence (voir l'historique). Est-ce clair pour vous? Que me conseillez-vous pour améliorer la compréhensibilité de ma réponse? Cheers ;-)
olibre
1
Vous avez tout à fait raison et je suis désolé pour mon malentendu. J'ai lu rapidement la réponse et n'ai pas remarqué votre réponse originale (oui, j'ai lu trop vite). +1 pour la réponse elle-même en utilisant la belle astuce pour boucler jusqu'à NF-1, puis en imprimant le dernier élément pour éviter les espaces supplémentaires. Et encore désolé! (supprimera mon commentaire dans un jour ou deux, pour éviter les malentendus des futurs lecteurs).
fedorqui 'SO arrêtez de nuire'
1
J'utiliserais une sorte d'en-têtes: <votre réponse> puis une règle horizontale suivie d'un grand titre "comparaison des autres réponses". Sinon, déplacez cette comparaison vers une autre réponse, car apparemment les gens ont tendance à préférer les réponses courtes dans une vision "donne-moi mon code"
:)
75
awk '{for(i=1;i<4;i++) $i="";print}' file
jiju
la source
4
Cela laisserait le début OFScar vous ne traitez pas avec l' NFespace de début dans les enregistrements.
Chris Seymour
70

utiliser couper

$ cut -f4-13 file

ou si vous insistez sur awk et que $ 13 est le dernier champ

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

autre

$ awk '{for(i=4;i<=13;i++)printf "%s ",$i;printf "\n"}' file
ghostdog74
la source
14
il vaut probablement mieux utiliser "NF" que "13" dans le dernier exemple.
glenn jackman
2
2 scénario qui revient à OP de décider. si 13 est le dernier champ, utiliser NF est correct. Sinon, l'utilisation de 13 est appropriée.
ghostdog74
3
Le deuxième doit supprimer 3 copies d'OFS à partir du début de 0 $. 3 serait mieux avec printf "%s ",$i, car vous ne savez pas si cela $ipourrait contenir %sou autre. Mais cela imprimerait un espace supplémentaire à la fin.
dubiousjim
38

Essaye ça:

awk '{ $1=""; $2=""; $3=""; print $0 }'
lhf
la source
1
C'est bien à cause de sa dynamique. Vous pouvez ajouter des colonnes à la fin et ne pas réécrire vos scripts.
MinceMan
1
Cela démontre le problème exact que la question essaie de résoudre. Faites simplement le contraire. Qu'en est-il d'imprimer le à partir du 100e champ? Notez que vous ne traitez pas avec NFvous, alors vous laissez le premier OFS.
Chris Seymour
24

La bonne façon de procéder est d'utiliser un intervalle RE, car il vous permet d'indiquer simplement le nombre de champs à ignorer et de conserver l'espacement entre les champs pour les champs restants.

par exemple, sauter les 3 premiers champs sans affecter l'espacement entre les champs restants étant donné le format d'entrée dont nous semblons discuter dans cette question est simplement:

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

Si vous souhaitez accueillir les espaces de début et les espaces non vides, mais encore une fois avec le FS par défaut, alors c'est:

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

Si vous avez un FS qui est un RE que vous ne pouvez pas annuler dans un jeu de caractères, vous pouvez d'abord le convertir en un seul caractère (RS est idéal s'il s'agit d'un seul caractère car un RS NE PEUT PAS apparaître dans un champ, sinon considérez SUBSEP), puis appliquer la substitution d'intervalle RE, puis convertir en OFS. par exemple, si des chaînes de "." séparent les champs:

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

Évidemment, si OFS est un seul caractère ET qu'il ne peut pas apparaître dans les champs de saisie, vous pouvez le réduire à:

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

Ensuite, vous rencontrez le même problème qu'avec toutes les solutions basées sur des boucles qui réaffectent les champs - les FS sont convertis en OFS. Si c'est un problème, vous devez vous pencher sur la fonction patsplit () de GNU awks.

Ed Morton
la source
Cela n'a pas fonctionné pour moi (bash 4.1.2 (1) -release, GNU Awk 3.1.7 ou bash 3.2.25 (1) -release, GNU Awk 3.1.5) mais trouvé ici une autre façon:echo ' This is a test' | awk '{print substr($0, index($0,$3))}'
elysch
2
Non, cela échouera si $ 1 ou $ 2 contiennent la chaîne sur laquelle $ 3 est défini. Essayez, par exemple, echo ' That is a test' | awk '{print substr($0, index($0,$3))}'et vous constaterez que le aqui est de 3 $ correspond à l' aintérieur Thatde $ 1. Dans une très ancienne version de gawk comme celle que vous avez, vous devez activer les intervalles RE avec le drapeau --re-interval.
Ed Morton
2
Vous avez raison, vous n'avez pas remarqué. Au fait, appréciez vraiment votre commentaire. Plusieurs fois ont voulu utiliser une regex avec "{}" pour spécifier le nombre d'éléments et n'ont jamais vu "--re-intervalle" dans l'homme. +1 pour vous.
elysch
1
1est une condition vraie et invoque donc l'action awk par défaut d'impression de l'enregistrement courant.
Ed Morton
1
idk comme c'est canonique mais j'ai ajouté une réponse maintenant.
Ed Morton
10

Presque toutes les réponses ajoutent actuellement des espaces de début, des espaces de fin ou un autre problème de séparateur. Pour sélectionner dans le quatrième champ où le séparateur est un espace et le séparateur de sortie est un espace unique en utilisant awkserait:

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

Pour paramétrer le champ de départ, vous pouvez faire:

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

Et aussi le champ de fin:

awk '{for(i=n;i<=m=(m>NF?NF:m);i++)printf "%s",$i (i==m?ORS:OFS)}' n=4 m=10 file
Chris Seymour
la source
6
awk '{$1=$2=$3="";$0=$0;$1=$1}1'

Contribution

1 2 3 4 5 6 7

Production

4 5 6 7

la source
4
echo 1 2 3 4 5| awk '{ for (i=3; i<=NF; i++) print $i }'
Vetsin
la source
3
Ou pour les mettre sur la même ligne, assignez 3 $ à 1 $, etc., puis changez NF pour le bon nombre de champs. echo 1 2 3 4 5| awk '{ for (i=3; i<=NF; i++) $(i-2)=$i; NF=NF-2; print $0 }'
larsr
Salut @larsr. Votre ligne de commande proposée est la seule bonne réponse. Toutes les autres réponses ajoutent des espaces supplémentaires (au début ou à la fin). S'il vous plaît postez votre ligne de commande dans une nouvelle réponse, je vais voter pour ;-)
olibre
1
Salut @sudo_O, je parlais à @larsr, de la ligne de commande qu'il proposait dans son commentaire. J'ai passé environ cinq minutes avant de comprendre le quiproco (malentendu). Je suis d'accord, la réponse @Vetsin insère de nouvelles lignes ( ORS) entre les champs. Bravo pour ton initiative (j'aime ta réponse). Cheers
olibre
3

Une autre façon d'éviter d'utiliser l'instruction print:

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

Dans awk lorsqu'une condition est vraie, l'impression est l'action par défaut.

Juan Diego Godoy Robles
la source
Cela a tous les problèmes rencontrés par @lhf answer ... c'est juste plus court.
Chris Seymour
Très bonne idée;) Mieux que ma réponse! (J'ai déjà voté pour votre réponse l'année dernière) Cheers
olibre
Cela devrait être: awk '{$1=$2=$3=""}sub("^"OFS"+","")' filecomme l'OFS ce qui reste après la modification du contenu $ 1, $ 2 et $ 3.
3

Je ne peux pas croire que personne n'ait offert une coque simple:

while read -r a b c d; do echo "$d"; done < file
Glenn Jackman
la source
+1 pour la solution similaire ... Mais cela peut avoir des problèmes de performances si elle fileest grande (> 10-30KiB). Pour les fichiers volumineux, la awksolution fonctionne mieux.
TrueY du
3

Les options 1 à 3 ont des problèmes avec plusieurs espaces (mais sont simples). C'est la raison pour laquelle nous avons développé les options 4 et 5, qui traitent sans problème plusieurs espaces blancs. Bien sûr, si les options 4 ou 5 sont utilisées avec les n=0deux, tout espace blanc de début sera conservé commen=0 signifie qu'il n'y a pas de division.

Option 1

Une solution de coupe simple (fonctionne avec des délimiteurs simples):

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

Option 2

Forcer un recalcul awk résout parfois le problème (fonctionne avec certaines versions de awk) des espaces de début ajoutés:

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

Option 3

L'impression de chaque champ formaté avec printfdonnera plus de contrôle:

$ 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

Cependant, toutes les réponses précédentes changent tous les FS entre les champs en OFS. Construisons quelques solutions à cela.

Option 4

Une boucle avec sous pour supprimer les champs et les délimiteurs est plus portable et ne déclenche pas de changement de FS en 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

REMARQUE: Le "^ [" FS "] *" est d'accepter une entrée avec des espaces de début.

Option 5

Il est tout à fait possible de construire une solution qui n'ajoute pas d'espaces blancs supplémentaires de début ou de fin, et de conserver les espaces blancs existants en utilisant la fonction gensubde GNU awk, comme ceci:

$ 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 

Il peut également être utilisé pour échanger une liste de champs en fonction d'un nombre 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     !

Bien entendu, dans ce cas, l'OFS est utilisé pour séparer les deux parties de la ligne, et l'espace blanc de fin des champs est toujours imprimé.

Note1: ["FS"]* est utilisé pour autoriser les espaces de début dans la ligne d'entrée.


la source
Salut BZ Votre réponse est gentille. Mais l'option 3 ne fonctionne pas sur une chaîne commençant par un espace (par exemple " 1 2 3 4 5 6 7 8 "). L'option 4 est agréable mais laissez un espace de début en utilisant une chaîne commençant par un espace. Pensez-vous que ce soit réparable? Vous pouvez utiliser la commande echo " 1 2 3 4 5 6 7 8 " | your awk script | sed 's/ /./g;s/\t/->/g;s/^/"/;s/$/"/'afin de vérifier les espaces de
début
Salut @olibre. Le fait que l'option 3 échoue avec un espace blanc est la raison pour laquelle les options 4 et 5. L'option 4 ne laisse un espace au début que si l'entrée l'a et n est mis à 0 (n = 0). Je crois que c'est la bonne réponse quand il n'y a pas de sélection de champs (rien à corriger IMO). À votre santé.
D'accord. Merci pour les informations supplémentaires :-) Veuillez améliorer votre réponse en fournissant ces informations supplémentaires :-) Cheers
olibre
Parfait :-) Quel dommage que votre utilisateur soit désactivé :-(
olibre
1

Cut a un indicateur --complement qui facilite (et rapidement) la suppression des colonnes. La syntaxe qui en résulte est analogue à ce que vous voulez faire - rendant la solution plus facile à lire / comprendre. Le complément fonctionne également dans le cas où vous souhaitez supprimer des colonnes non contiguës.

$ foo='1 2 3 %s 5 6 7'
$ echo "$foo" | cut --complement -d' ' -f1-3
%s 5 6 7
$
Michael Back
la source
Pouvez-vous expliquer davantage votre réponse s'il vous plaît?
Zulu
La modification ci-dessus aide-t-elle à comprendre? Le but est d'utiliser le drapeau de complément de coupe. La solution doit être une implémentation plus rapide et plus concise que les solutions AWK ou perl. En outre, des colonnes arbitraires peuvent être coupées.
Michael Back
1

Solution Perl qui n'ajoute pas d'espaces de début ou de fin:

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

Le @Ftableau d'autosplit perl commence à l'index 0tandis que les champs awk commencent par$1


Solution Perl pour les données délimitées par des virgules:

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

Solution Python:

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

Chris Koknat
la source
0

Pour moi, la solution la plus compacte et conforme à la demande est

$ 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}'

Et si vous avez plus de lignes à traiter comme par exemple le fichier foo.txt , n'oubliez pas de remettre i à 0:

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

Merci votre forum.

user8008888
la source
0

Comme j'étais ennuyé par la première réponse hautement votée mais fausse, j'ai trouvé assez pour y écrire une réponse, et ici les mauvaises réponses sont marquées comme telles, voici ma part. Je n'aime pas les solutions proposées car je ne vois aucune raison de rendre la réponse si complexe.

J'ai un journal où après 5 $ avec une adresse IP peut être plus de texte ou pas de texte. J'ai besoin de tout, de l'adresse IP à la fin de la ligne, s'il y a quelque chose après 5 $. Dans mon cas, c'est en fait avec un programme awk, pas un awk oneliner donc awk doit résoudre le problème. Lorsque j'essaie de supprimer les 4 premiers champs en utilisant l'ancienne réponse agréable et la plus votée mais complètement fausse:

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}'

il crache une réponse fausse et inutile (j'ai ajouté [] pour démontrer):

[    37.244.182.218 one two three]

Au lieu de cela, si les colonnes ont une largeur fixe jusqu'à ce que le point de coupe et awk soient nécessaires, la réponse correcte et assez simple est:

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

qui produit la sortie souhaitée:

[37.244.182.218 one two three]
Pila
la source
0

J'ai trouvé cette autre possibilité, peut-être qu'elle pourrait être utile aussi ...

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

Remarque: 1. Pour les données tabulaires et de la colonne 1 $ à 14 $

jgarces
la source
0

Utilisez la coupe:

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

par exemple: Si vous avez file1contenant:car.is.nice.equal.bmw

Exécuter: cut -d . -f1,3 file1 imprimeracar.is.nice

zayed
la source
On dirait que votre solution pourrait être en arrière. Veuillez consulter le titre de la question Imprimer tout * sauf * les trois premières colonnes
Stefan Crain
-1

Ce n'est pas très loin de certaines des réponses précédentes, mais résout quelques problèmes:

cols.sh:

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

Que vous pouvez maintenant appeler avec un argument qui sera la colonne de départ:

$ 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

Ou:

$ 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

C'est indexé 1; si vous préférez zéro indexé, utilisez à la i=s + 1place.

De plus, si vous souhaitez avoir des arguments pour l'index de départ et l' index de fin, changez le fichier en:

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

Par exemple:

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

Le %-5saligne le résultat sous forme de colonnes de 5 caractères; si cela ne suffit pas, augmentez le nombre ou utilisez %splutôt (avec un espace) si vous ne vous souciez pas de l'alignement.

user2141650
la source
-1

Solution basée sur AWK printf qui évite% problème, et est unique en ce sens qu'elle ne renvoie rien (pas de caractère de retour) s'il y a moins de 4 colonnes à imprimer:

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

Essai:

$ 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) }'
$
Michael Back
la source