Comment diviser une chaîne en shell et obtenir le dernier champ

293

Supposons que j'ai la chaîne 1:2:3:4:5et que je veuille obtenir son dernier champ ( 5dans ce cas). Comment faire cela en utilisant Bash? J'ai essayé cut, mais je ne sais pas comment spécifier le dernier champ avec -f.

cd1
la source

Réponses:

430

Vous pouvez utiliser des opérateurs de chaîne :

$ foo=1:2:3:4:5
$ echo ${foo##*:}
5

Cela coupe tout de l'avant jusqu'à un ':', avec avidité.

${foo  <-- from variable foo
  ##   <-- greedy front trim
  *    <-- matches anything
  :    <-- until the last ':'
 }
Stephen
la source
7
Bien que cela fonctionne pour le problème donné, la réponse de William ci-dessous ( stackoverflow.com/a/3163857/520162 ) renvoie également 5si la chaîne l'est 1:2:3:4:5:(tout en utilisant les opérateurs de chaîne donne un résultat vide). Ceci est particulièrement pratique lors de l'analyse de chemins pouvant contenir (ou non) un /caractère de fin .
vérifie le
9
Comment feriez-vous alors le contraire? faire écho «1: 2: 3: 4:»?
Dobz
12
Et comment garder la pièce avant le dernier séparateur? Apparemment en utilisant ${foo%:*}. #- Depuis le début; %- de fin. #, %- correspondance la plus courte; ##, %%- match le plus long.
Mihai Danila
1
Si je veux obtenir le dernier élément du chemin, comment dois-je l'utiliser? echo ${pwd##*/}ne marche pas.
Putnik
2
@Putnik que la commande voit pwdcomme une variable. Essayez dir=$(pwd); echo ${dir##*/}. Travaille pour moi!
Stan Strum
344

Une autre façon consiste à inverser avant et après cut:

$ echo ab:cd:ef | rev | cut -d: -f1 | rev
ef

Cela permet d'obtenir très facilement le dernier mais un seul champ, ou toute plage de champs numérotés à partir de la fin.

a3nm
la source
17
Cette réponse est agréable car elle utilise «cut», que l'auteur est (vraisemblablement) déjà familier. De plus, j'aime cette réponse parce que j'utilise «couper» et que j'avais cette question exacte, trouvant ainsi ce fil via la recherche.
Dannid
6
echo "1 2 3 4" | rev | cut -d " " -f1 | rev
Du
2
le rev | cut -d -f1 | rev est tellement intelligent! Merci! M'a aidé un tas (mon cas d'utilisation était rev | -d '' -f 2- | rev
EdgeCaseBerg
1
J'oublie toujours rev, c'était juste ce dont j'avais besoin! cut -b20- | rev | cut -b10- | rev
shearn89
Je me suis retrouvé avec cette solution, ma tentative de couper les chemins de fichiers avec "awk -F" / "'{print $ NF}'" s'est quelque peu cassée pour moi, car les noms de fichiers comprenant des espaces blancs ont également été coupés
THX
76

Il est difficile d'obtenir le dernier champ en utilisant cut, mais voici quelques solutions dans awk et perl

echo 1:2:3:4:5 | awk -F: '{print $NF}'
echo 1:2:3:4:5 | perl -F: -wane 'print $F[-1]'
William Pursell
la source
5
grand avantage de cette solution par rapport à la réponse acceptée: elle correspond également aux chemins qui contiennent ou ne contiennent pas de /caractère de finition : /a/b/c/det /a/b/c/d/donnent le même résultat ( d) lors du traitement pwd | awk -F/ '{print $NF}'. La réponse acceptée entraîne un résultat vide dans le cas de/a/b/c/d/
eckes
@eckes Dans le cas d'une solution AWK, sur GNU bash, version 4.3.48 (1) -release ce n'est pas vrai, car cela importe chaque fois que vous avez une barre oblique de fin ou non. Mettez simplement AWK utilisé /comme délimiteur, et si votre chemin est, /my/path/dir/il utilisera la valeur après le dernier délimiteur, qui est simplement une chaîne vide. Il est donc préférable d'éviter la barre oblique si vous devez faire une chose comme moi.
stamster
Comment puis-je obtenir la sous-chaîne JUSQU'AU dernier champ?
blackjacx
1
@blackjacx Il y a quelques bizarreries, mais quelque chose comme ça awk '{$NF=""; print $0}' FS=: OFS=:fonctionne souvent assez bien.
William Pursell
29

En supposant une utilisation assez simple (aucun échappement du délimiteur par exemple), vous pouvez utiliser grep:

$ echo "1:2:3:4:5" | grep -oE "[^:]+$"
5

Ventilation - recherchez tous les caractères et non le délimiteur ([^:]) à la fin de la ligne ($). -o imprime uniquement la pièce correspondante.

Nicholas MT Elliott
la source
-E signifie utiliser une syntaxe étendue; [^ ...] signifie autre chose que les caractères répertoriés; + un ou plusieurs hits de ce type (prendra la longueur maximale possible pour le motif; cet élément est une extension GNU) - par exemple, les caractères de séparation sont les deux points.
Alexander Stohr
18

Une manière:

var1="1:2:3:4:5"
var2=${var1##*:}

Un autre, en utilisant un tableau:

var1="1:2:3:4:5"
saveIFS=$IFS
IFS=":"
var2=($var1)
IFS=$saveIFS
var2=${var2[@]: -1}

Encore un autre avec un tableau:

var1="1:2:3:4:5"
saveIFS=$IFS
IFS=":"
var2=($var1)
IFS=$saveIFS
count=${#var2[@]}
var2=${var2[$count-1]}

Utilisation d'expressions régulières Bash (version> = 3.2):

var1="1:2:3:4:5"
[[ $var1 =~ :([^:]*)$ ]]
var2=${BASH_REMATCH[1]}
En pause jusqu'à nouvel ordre.
la source
11
$ echo "a b c d e" | tr ' ' '\n' | tail -1
e

Traduisez simplement le délimiteur en une nouvelle ligne et choisissez la dernière entrée avec tail -1.

user3133260
la source
Il échouera si le dernier élément contient un \n, mais dans la plupart des cas, c'est la solution la plus lisible.
Yajo
7

En utilisant sed:

$ echo '1:2:3:4:5' | sed 's/.*://' # => 5

$ echo '' | sed 's/.*://' # => (empty)

$ echo ':' | sed 's/.*://' # => (empty)
$ echo ':b' | sed 's/.*://' # => b
$ echo '::c' | sed 's/.*://' # => c

$ echo 'a' | sed 's/.*://' # => a
$ echo 'a:' | sed 's/.*://' # => (empty)
$ echo 'a:b' | sed 's/.*://' # => b
$ echo 'a::c' | sed 's/.*://' # => c
Rafael
la source
4

Si votre dernier champ est un seul caractère, vous pouvez le faire:

a="1:2:3:4:5"

echo ${a: -1}
echo ${a:(-1)}

Vérifiez la manipulation des chaînes dans bash .

Ab Irato
la source
Cela ne fonctionne pas: cela donne le dernier caractère de a, pas le dernier champ .
gniourf_gniourf
1
C'est vrai, c'est l'idée, si vous connaissez la longueur du dernier champ, c'est bon. Sinon, vous devez utiliser autre chose ...
Ab Irato
4

Il y a beaucoup de bonnes réponses ici, mais je veux quand même partager celle-ci en utilisant le nom de base :

 basename $(echo "a:b:c:d:e" | tr ':' '/')

Cependant, il échouera s'il y a déjà un '/' dans votre chaîne . Si slash / est votre délimiteur, il vous suffit (et devrait) utiliser le nom de base.

Ce n'est pas la meilleure réponse, mais cela montre simplement comment vous pouvez être créatif en utilisant les commandes bash.

021
la source
2

Utilisation de Bash.

$ var1="1:2:3:4:0"
$ IFS=":"
$ set -- $var1
$ eval echo  \$${#}
0
ghostdog74
la source
Aurait pu utiliser echo ${!#}au lieu de eval echo \$${#}.
Rafa
2
echo "a:b:c:d:e"|xargs -d : -n1|tail -1

Utilisez d'abord xargs pour le diviser en utilisant ":", - n1 signifie que chaque ligne n'a qu'une seule partie. Ensuite, tapez la dernière partie.

Crytis
la source
1
for x in `echo $str | tr ";" "\n"`; do echo $x; done
Nahid Akbar
la source
2
Cela pose des problèmes s'il y a des espaces dans l'un des champs. En outre, il ne traite pas directement la question de la récupération du dernier champ.
chepner
1

Pour ceux qui sont à l'aise avec Python, https://github.com/Russell91/pythonpy est un bon choix pour résoudre ce problème.

$ echo "a:b:c:d:e" | py -x 'x.split(":")[-1]'

De l'aide pythonpy: -x treat each row of stdin as x.

Avec cet outil, il est facile d'écrire du code python qui est appliqué à l'entrée.

Christoph Böddeker
la source
1

Peut-être un peu tard avec la réponse, bien qu'une solution simple soit d'inverser l'ordre de la chaîne d'entrée. Cela vous permettrait de toujours gagner le dernier élément, quelle que soit sa longueur.

[chris@desktop bin]$ echo 1:2:3:4:5 | rev | cut -d: -f1
5

Il est important de noter cependant que si vous utilisez cette méthode et que les nombres sont supérieurs à 1 chiffre (ou supérieur à un caractère en toute circonstance), vous devrez exécuter une autre commande 'rev' sur la sortie canalisée.

[chris@desktop bin]$ echo 1:2:3:4:5:8:24 | rev | cut -d: -f1
42
[chris@desktop bin]$ echo 1:2:3:4:5:8:24 | rev | cut -d: -f1 | rev
24

J'espère que je peux vous aider

Chris
la source
1

Une solution utilisant la lecture intégrée:

IFS=':' read -a fields <<< "1:2:3:4:5"
echo "${fields[4]}"

Ou, pour le rendre plus générique:

echo "${fields[-1]}" # prints the last item
baz
la source
0

Si vous aimez python et avez une option pour installer un paquet, vous pouvez utiliser cet utilitaire python .

# install pythonp
pythonp -m pip install pythonp

echo "1:2:3:4:5" | pythonp "l.split(':')[-1]"
5
bombes
la source
python peut le faire directement: echo "1:2:3:4:5" | python -c "import sys; print(list(sys.stdin)[0].split(':')[-1])"
MortenB
@MortenB Vous vous trompez. Le but du pythonppackage est de vous faire faire les mêmes choses python -cqu'avec moins de saisies de caractères. Veuillez consulter le fichier README dans le référentiel.
bombes
0

L'appariement des expressions rationnelles sedest gourmand (va toujours à la dernière occurrence), que vous pouvez utiliser à votre avantage ici:

$ foo=1:2:3:4:5
$ echo ${foo} | sed "s/.*://"
5
détrempé
la source