web-dev-qa-db-fra.com

Fractionner la chaîne par délimiteur et obtenir le N-ième élément

J'ai une chaîne:

one_two_three_four_five

J'ai besoin de sauvegarder dans une variable A valeur two et dans la variable B valeur fourde la chaîne ci-dessus

96
Alex

Utilisez cut avec _ comme délimiteur de champ et obtenez les champs souhaités:

A="$(cut -d'_' -f2 <<<'one_two_three_four_five')"
B="$(cut -d'_' -f4 <<<'one_two_three_four_five')"

Vous pouvez également utiliser echo et pipe au lieu de la chaîne Here:

A="$(echo 'one_two_three_four_five' | cut -d'_' -f2)"
B="$(echo 'one_two_three_four_five' | cut -d'_' -f4)"

Exemple:

$ s='one_two_three_four_five'

$ A="$(cut -d'_' -f2 <<<"$s")"
$ echo "$A"
two

$ B="$(cut -d'_' -f4 <<<"$s")"
$ echo "$B"
four
144
heemayl

En utilisant uniquement des constructions sh POSIX, vous pouvez utiliser constructions de substitution de paramètres pour analyser un délimiteur à la fois. Notez que ce code suppose qu'il existe le nombre requis de champs, sinon le dernier champ est répété.

string='one_two_three_four_five'
remainder="$string"
first="${remainder%%_*}"; remainder="${remainder#*_}"
second="${remainder%%_*}"; remainder="${remainder#*_}"
third="${remainder%%_*}"; remainder="${remainder#*_}"
fourth="${remainder%%_*}"; remainder="${remainder#*_}"

Alternativement, vous pouvez utiliser une substitution de paramètre sans guillemets avec expansion générique désactivé et IFS défini sur le caractère délimiteur (cela ne fonctionne que si le délimiteur est un seul non - caractère d'espacement ou si une séquence d'espaces est un délimiteur).

string='one_two_three_four_five'
set -f; IFS='_'
set -- $string
second=$2; fourth=$4
set +f; unset IFS

Cela détruit les paramètres de position. Si vous effectuez cette opération dans une fonction, seuls les paramètres positionnels de la fonction sont affectés.

Une autre approche consiste à utiliser la fonction intégrée read.

IFS=_ read -r first second third fourth trail <<'EOF'
one_two_three_four_five
EOF

Je voulais voir une réponse awk, alors en voici une:

A=$(awk -F_ '{print $2}' <<< 'one_two_three_four_five')
B=$(awk -F_ '{print $4}' <<< 'one_two_three_four_five')
21
Paul Evans

La manière la plus simple (pour les shells avec <<<) est:

 IFS='_' read -r a second a fourth a <<<"$string"

Utilisation d'une variable temporelle $a au lieu de $_ parce qu'un Shell se plaint.

Dans un script complet:

 string='one_two_three_four_five'
 IFS='_' read -r a second a fourth a <<<"$string"
 echo "$second $fourth"

Aucun changement IFS, pas de problèmes avec set -f (Extension du nom de chemin) Aucune modification des paramètres de position ("$ @").


Pour une solution portable vers tous shells (oui, tous POSIX inclus) sans changer IFS ou set -f, utilisez l'équivalent hérédoc (un peu plus complexe):

string='one_two_three_four_five'

IFS='_' read -r a second a fourth a <<-_EOF_
$string
_EOF_

echo "$second $fourth"

Comprenez que ces solutions (à la fois ici-doc et l'utilisation de <<< supprimera toutes les nouvelles lignes de fin.
Et que cela est conçu pour un contenu variable "à une ligne".
Des solutions pour les multilignes sont possibles mais nécessitent des constructions plus complexes.


Une solution très simple est possible dans la version 4.4 de bash

readarray -d _ -t arr <<<"$string"

echo "array ${arr[1]} ${arr[3]}"   # array numbers are zero based.

Il n'y a pas d'équivalent pour les shells POSIX, car de nombreux shells POSIX n'ont pas de tableaux.

Pour les shells qui ont des tableaux peuvent être aussi simples que:
(testé en attsh, lksh, mksh, ksh et bash)

set -f; IFS=_; arr=($string)

Mais avec beaucoup de plomberie supplémentaire pour conserver et réinitialiser les variables et les options:

string='one_* *_three_four_five'

case $- in
    *f*) noglobset=true; ;;
    *) noglobset=false;;
esac

oldIFS="$IFS"

set -f; IFS=_; arr=($string)

if $noglobset; then set -f; else set +f; fi

echo "two=${arr[1]} four=${arr[3]}"

Dans zsh, les tableaux commencent par 1 et ne divisent pas la chaîne par défaut.
Certains changements doivent donc être effectués pour que cela fonctionne dans zsh.

11
Isaac

Avec zsh, vous pouvez diviser la chaîne (sur _) dans un tableau:

elements=(${(s:_:)string})

puis accéder à chaque élément/tout via un index de tableau:

print -r ${elements[4]}

Gardez à l'esprit que dans zsh (contrairement à ksh/bash) les indices de tableau commencent à 1 .

3
don_crissti

Une solution python est-elle autorisée?

# python -c "import sys; print sys.argv[1].split('_')[1]" one_two_three_four_five
two

# python -c "import sys; print sys.argv[1].split('_')[3]" one_two_three_four_five
four
2
fhgd

n autre exemple awk; plus simple à comprendre.

A=\`echo one_two_three_four_five | awk -F_ '{print $1}'\`  
B=\`echo one_two_three_four_five | awk -F_ '{print $2}'\`  
C=\`echo one_two_three_four_five | awk -F_ '{print $3}'\`  
... and so on...  

Peut également être utilisé avec des variables.
Supposons:
this_str = "one_two_three_four_five"
Ensuite, les travaux suivants:
A = `echo $ {this_str} | awk -F_ '{print $ 1}' '
B = `echo $ {this_str} | awk -F_ '{print $ 2}' '
C = `echo $ {this_str} | awk -F_ '{print $ 3}' '
... etc...

1
user274900