Comment trouver le dernier champ en utilisant 'cut'

310

Sans utiliser sedou awk, seulement cut , comment obtenir le dernier champ lorsque le nombre de champs est inconnu ou change à chaque ligne?

noobcoder
la source
8
Êtes-vous amoureux de la cutcommande :)? pourquoi pas d'autres commandes Linux?
Jayesh Bhoi
7
Sans sedou awk: perl -pe 's/^.+\s+([^\s]+)$/$1/'.
jordanm
4
@MestreLion Plusieurs fois, les gens lisent une question pour trouver une solution à une variation d'un problème. Celui-ci commence avec la fausse prémisse qui cutsoutient quelque chose qu'il ne fait pas. Mais j'ai pensé que c'était utile, car cela oblige le lecteur à considérer un code plus facile à suivre. Je voulais un moyen rapide et simple à utiliser cutsans avoir besoin d'utiliser plusieurs syntaxes pour awk, grep, sed, etc. La revchose a fait l'affaire; très élégant, et quelque chose que je n'ai jamais envisagé (même s'il est maladroit pour d'autres situations). J'ai aussi aimé lire les autres approches des autres réponses.
Beejor
3
Est venu ici un problème réel: je veux trouver toutes les différentes extensions de fichier dans une arborescence source, pour mettre à jour un fichier .gitattributes avec. Il en find | cut -d. -f<last>va de même pour l'inclinaison naturelle
studog

Réponses:

680

Vous pouvez essayer quelque chose comme ça:

echo 'maps.google.com' | rev | cut -d'.' -f 1 | rev

Explication

  • rev inverse "maps.google.com" pour être moc.elgoog.spam
  • cut utilise le point (c'est-à-dire «.») comme délimiteur et choisit le premier champ, qui est moc
  • enfin, nous l'inversons à nouveau pour obtenir com
zedfoxus
la source
6
Il n'utilise pas seulement cutmais c'est sans sedou awk.Alors qu'en pense OP?
Jayesh Bhoi
7
@tom OP a posé plus de questions que cela au cours des dernières heures. Sur la base de nos interactions avec l'OP, nous savons que awk / sed / etc. ne sont pas autorisés dans ses devoirs, mais aucune référence à rev n'a été faite. Donc ça valait le coup
zedfoxus
4
@zfus je vois. Pourrait vouloir en coller un autre revaprès.
Tom
17
double revgrand idéal!
Ford Guo
6
Génial, simple, parfait, merci aussi pour l'explication - pas assez de gens expliquant chaque étape dans de longues chaînes de commandes piped
Pete
128

Utilisez une extension de paramètre. C'est beaucoup plus efficace que n'importe quel type de commande externe, cut(ou grep) incluse.

data=foo,bar,baz,qux
last=${data##*,}

Voir BashFAQ # 100 pour une introduction à la manipulation de chaînes natives dans bash.

Charles Duffy
la source
3
@ErwinWessels: Parce que bash est vraiment lent. Utilisez bash pour exécuter des pipelines, et non pour traiter des données en bloc. Je veux dire, c'est génial si vous avez déjà une ligne de texte dans une variable shell, ou si vous voulez faire while IFS= read -ra array_var; do :;done <(cmd)pour traiter quelques lignes. Mais pour un gros fichier, rev | cut | rev est probablement plus rapide! (Et bien sûr, awk sera plus rapide que ça.)
Peter Cordes
2
@PeterCordes, awk sera plus rapide pour un gros fichier, bien sûr, mais il faut pas mal d'entrée pour surmonter les coûts de démarrage à facteur constant. (Il existe également des shells - comme ksh93 - avec des performances plus proches de awk, où la syntaxe donnée dans cette réponse reste valide; bash est exceptionnellement lent, mais il n'est même pas proche de la seule option disponible).
Charles Duffy
1
Merci @PeterCordes; comme d'habitude, je suppose que chaque outil a ses cas d'utilisation.
Erwin Wessels
1
C'est de loin le moyen le plus rapide et le plus concis de réduire une seule variable dans un bashscript (en supposant que vous utilisez déjà un bashscript). Pas besoin d'appeler quoi que ce soit d'extérieur.
Ken Sharp
1
@Balmipour, ... cependant, rev est spécifique au système d'exploitation que vous utilisez qui le fournit - il n'est pas standardisé sur tous les systèmes UNIX. Voir la liste des chapitres pour la section POSIX sur les commandes et les utilitaires - elle n'est pas là. Et ${var##prefix_pattern}n'est pas en fait spécifique à bash; il est dans la norme POSIX sh , voir la fin de la section 2.6.2 (lié), donc contrairement à rev, il est toujours disponible sur n'importe quel shell compatible.
Charles Duffy
89

Il n'est pas possible d'utiliser simplement cut. Voici un moyen d'utiliser grep:

grep -o '[^,]*$'

Remplacez la virgule pour les autres délimiteurs.

à M
la source
3
Pour faire l'inverse, et trouver tout sauf le dernier champ faire:grep -o '^.*,'
Ariel
2
C'était particulièrement utile, car revajoutez un problème de caractères unicode multioctets dans mon cas.
Brice
3
J'essayais de le faire sur MinGW mais ma version grep ne prend pas en charge -o, j'ai donc utilisé sed 's/^.*,//'ce qui remplace tous les caractères jusqu'à et y compris la dernière virgule par une chaîne vide.
TamaMcGlinn
46

Sans awk? ... Mais c'est si simple avec awk:

echo 'maps.google.com' | awk -F. '{print $NF}'

AWK est un outil beaucoup plus puissant à avoir dans votre poche. -F si pour le séparateur de champs NF est le nombre de champs (représente également l'index du dernier)

Amir Mehler
la source
2
C'est universel et cela fonctionne exactement comme prévu à chaque fois. Dans ce scénario, utiliser cutpour obtenir la sortie finale de l'OP revient à utiliser une cuillère pour "couper" le steak (jeu de mots voulu :)). awkest le couteau à steak.
Hickory420
3
Évitez une utilisation inutile de ce echoqui peut ralentir le script pour les longs fichiers awk -F. '{print $NF}' <<< 'maps.google.com'.
Anil_M
14

Il y a plusieurs façons. Vous pouvez également l'utiliser.

echo "Your string here"| tr ' ' '\n' | tail -n1
> here

De toute évidence, l'entrée d'espace vide pour la commande tr doit être remplacée par le délimiteur dont vous avez besoin.

rjni
la source
Je vous remercie! quelque chose qui fonctionne dans busybox sh 1.0.0 :)
kevinf
1
Cela ressemble à la réponse la plus simple pour moi, moins de tuyaux et une signification plus claire
joeButler
1
Cela ne fonctionnera pas pour un fichier entier, ce que l'OP signifiait probablement.
Amir
7

C'est la seule solution possible pour n'utiliser que du cut:

écho "chaîne" | couper -d '.' -f2- [repeat_following_part_forever_or_until_out_of_memory:] | couper -d '.' -f2-

En utilisant cette solution, le nombre de champs peut en effet être inconnu et varier de temps en temps. Cependant, comme la longueur de ligne ne doit pas dépasser les caractères ou champs LINE_MAX, y compris le caractère de nouvelle ligne, un nombre arbitraire de champs ne peut jamais faire partie comme condition réelle de cette solution.

Oui, une solution très idiote mais la seule qui répond aux critères je pense.

Un ami
la source
2
Agréable. Prenez le dernier '.' hors de "chaîne" et cela fonctionne.
Matt
2
J'adore quand tout le monde dit que quelque chose est impossible et que quelqu'un intervient avec une réponse qui fonctionne. Même si c'est en effet très idiot.
Beejor
On pourrait parcourir cut -f2-une boucle jusqu'à ce que la sortie ne change plus.
loa_in_
4

Si votre chaîne d'entrée ne contient pas de barres obliques, vous pouvez utiliser basenameet un sous-shell:

$ basename "$(echo 'maps.google.com' | tr '.' '/')"

Cela n'utilise pas sedou awkmais il n'utilise pas non cutplus, donc je ne suis pas sûr si cela peut être considéré comme une réponse à la question telle qu'elle est formulée.

Cela ne fonctionne pas bien si vous traitez des chaînes d'entrée pouvant contenir des barres obliques. Une solution de contournement pour cette situation serait de remplacer la barre oblique par un autre caractère dont vous savez qu'il ne fait pas partie d'une chaîne d'entrée valide. Par exemple, le caractère pipe ( |) n'est pas non plus autorisé dans les noms de fichiers, donc cela fonctionnerait:

$ basename "$(echo 'maps.google.com/some/url/things' | tr '/' '|' | tr '.' '/')" | tr '|' '/'
jstine
la source
2

ce qui suit met en œuvre la suggestion d'un ami

#!/bin/bash
rcut(){

  nu="$( echo $1 | cut -d"$DELIM" -f 2-  )"
  if [ "$nu" != "$1" ]
  then
    rcut "$nu"
  else
    echo "$nu"
  fi
}

$ export DELIM=.
$ rcut a.b.c.d
d
user2166700
la source
2
Vous avez besoin de guillemets doubles autour des arguments echopour que cela fonctionne de manière fiable et robuste. Voir stackoverflow.com/questions/10067266/…
tripleee
0

Si vous avez un fichier nommé filelist.txt qui est une liste de chemins tels que les suivants: c: /dir1/dir2/file1.h c: /dir1/dir2/dir3/file2.h

alors vous pouvez le faire: rev filelist.txt | cut -d "/" -f1 | tour

aperson1961
la source
0

Ajout d'une approche à cette vieille question juste pour le plaisir:

$ cat input.file # file containing input that needs to be processed
a;b;c;d;e
1;2;3;4;5
no delimiter here
124;adsf;15454
foo;bar;is;null;info

$ cat tmp.sh # showing off the script to do the job
#!/bin/bash
delim=';'
while read -r line; do  
    while [[ "$line" =~ "$delim" ]]; do
        line=$(cut -d"$delim" -f 2- <<<"$line")
    done
    echo "$line"
done < input.file

$ ./tmp.sh # output of above script/processed input file
e
5
no delimiter here
15454
info

Outre bash, seule la coupe est utilisée. Eh bien, et l'écho, je suppose.

Kaffe Myers
la source
Meh, pourquoi ne pas supprimer complètement la coupe et utiliser uniquement bash ... x] while read -r line; do echo ${line/*;}; done <input.filedonne le même résultat.
Kaffe Myers
-1

J'ai réalisé que si nous nous contentions de nous assurer qu'un délimiteur de fin existe, cela fonctionne. Donc, dans mon cas, j'ai des délimiteurs de virgule et d'espaces. J'ajoute un espace à la fin;

$ ans="a, b"
$ ans+=" "; echo ${ans} | tr ',' ' ' | tr -s ' ' | cut -d' ' -f2
b
AnneTheAgile
la source
Et ans="a, b, c"produit b, qui ne répond pas aux exigences de "nombre de champs sont inconnus ou changent à chaque ligne" .
jww