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.

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

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.


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

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.


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

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.


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

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 .


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.


2

Utilisation de Bash.

$ var1="1:2:3:4:0"
$ IFS=":"
$ set -- $var1
$ eval echo  \$${#}
0

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.


1
for x in `echo $str | tr ";" "\n"`; do echo $x; done

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.


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


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

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

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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.