Fractionner une chaîne par un délimiteur et obtenir le n-ième élément

77

J'ai une ficelle:

one_two_three_four_five

Je dois enregistrer dans une Avaleur de variable twoet dans la Bvaleur fourde variable de la chaîne ci-dessus

Alex
la source

Réponses:

109

Utilisez cutavec _comme délimiteur de champ et obtenez les champs désirés:

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

Vous pouvez également utiliser echoet 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
heemayl
la source
Y a-t-il une alternative? J'utilise ksh (pas bsh) et il retourne ksh: erreur de syntaxe: `<'inattendu
Alex
@ Alex Vérifiez mes modifications.
Heemayl
Bonne réponse, j'ai une petite question: que se passera-t-il si votre variable "$ s" est un dossier de chemin. Lorsque j'essaie de couper un dossier de chemin, j'aime bien ce qui suit: `$ FILE = mon_utilisateur / mon_folder / [fichier] *` $ echo $FILE my_user/my_folder/file.csv $ A="$(cut -d'/' -f2 <<<"$FILE")" $ echo $A [file]* Savez-vous ce qui se passe ici?
Henry Navarro
1
Et si vous voulez juste le dernier champ, en utilisant uniquement les commandes intégrées du shell - sans avoir besoin de spécifier sa position, ou si vous ne connaissez pas le nombre de champs:echo "${s##*_}"
Amit Naidu
19

En utilisant uniquement les constructions POSIX sh, vous pouvez utiliser des 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#*_}"

Vous pouvez également utiliser une substitution de paramètre non entre guillemets avec le développement de caractères génériques désactivé et IFSdéfini sur le caractère de délimitation (cela ne fonctionne que si le délimiteur est un seul caractère non blanc ou si une séquence d'espaces blancs est un délimiteur).

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

Cela écrase les paramètres de position. Si vous faites cela dans une fonction, seuls les paramètres de position de la fonction sont affectés.

Une autre approche consiste à utiliser le mode readintégré.

IFS=_ read -r first second third fourth trail <<'EOF'
one_two_three_four_five
EOF
Gilles, arrête de faire le mal
la source
L'utilisation de unset IFSne retourne pas IFSaux valeurs par défaut. Si après cela, quelqu'un OldIFS="$IFS"aura une valeur nulle dans OldIFS. En outre, il est supposé que la valeur précédente de IFS est la valeur par défaut, ce qui est très possible (et utile) de ne pas l'être. La seule solution correcte est de stocker dans old="$IFS"et de restaurer ultérieurement avec IFS = "$ old". Ou ... utilisez un sous-shell (...). Ou, mieux encore, lisez ma réponse.
sorontar
@sorontar unset IFSne rétablit IFSpas la valeur par défaut, mais renvoie la division du champ à l'effet par défaut. Oui, c'est une limitation, mais généralement acceptable dans la pratique. Le problème avec un sous-shell est que nous devons en extraire des données. Je montre une solution qui ne change pas l'état à la fin, avec read. (Cela fonctionne dans les shells POSIX, mais pas IIRC dans le shell Bourne, car il s'exécuterait readdans un sous-shell à cause du document here.) Utiliser <<<as dans vous répondez est une variante qui fonctionne uniquement dans ksh / bash / zsh.
Gilles 'SO- arrête d'être méchant'
Je ne vois pas de problème même avec att ou un héritage à propos d'un sous-shell. Toutes les coquilles testées (y compris l'ancienne bourne) fournissent la valeur correcte dans la coquille principale.
sorontar
Que se passe-t-il si mon chemin ressemble à quelque chose user/my_folder/[this_is_my_file]*? Ce que j'obtiens quand je suis ces étapes est[this_is_my_file]*
Henry Navarro
@HenryNavarro Cette sortie ne correspond à aucun extrait de code dans ma réponse. Aucun d'entre eux ne fait quelque chose de spécial sur /.
Gilles, arrête de faire le mal '
17

Je voulais voir une awkréponse, alors en voici une:

A=$(awk -F_ '{print $2}' <<< 'one_two_three_four_five')
B=$(awk -F_ '{print $4}' <<< 'one_two_three_four_five')
Paul Evans
la source
1
Et si vous voulez la dernière pièce - sans avoir besoin de spécifier sa position ou quand vous ne connaissez pas le nombre de champs:awk -F_ '{print $NF}' <<< 'one_two_3_4_five'
Amit Naidu
8

Le moyen le plus simple (pour les shells avec <<<) est:

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

Utiliser une variable temporelle $aau 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 IFS ne change, pas de problème avec set -f(extension de nom de chemin) Aucune modification des paramètres de position ("$ @").


Pour une solution portable à tous les shells (oui, tous les POSIX inclus) sans changer d’IFS ou set -f, utilisez l’équivalent heredoc (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 cette solution (à la fois ici-doc et l'utilisation de <<<, supprimera toutes les nouvelles lignes qui suivent.
Et cela est conçu pour un contenu variable "one liner". Les
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é avec attsh, lksh, mksh, ksh et bash)

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

Mais avec beaucoup de tuyauterie 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.
Il faut donc faire quelques changements pour que cela fonctionne dans zsh.

sorontar
la source
les solutions qui l'utilisent read sont simples tant que OP ne veut pas extraire les 76ème et 127ème éléments d'une longue chaîne ...
don_crissti
@don_crissti Eh bien, oui, bien sûr, mais un concept similaire readarraypourrait être plus facile à utiliser dans cette situation.
sorontar
@don_crissti J'ai également ajouté une solution de tableau pour les shells dotés de tableaux. Pour les shells POSIX, eh bien, n'ayant pas de tableaux, les paramètres de position jusqu'à 127 éléments ne constituent pas une solution "simple".
sorontar
2

Avec zshvous pouvez diviser la chaîne (sur _) en un tableau:

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

puis accédez à chacun des éléments via un index de tableau:

print -r ${elements[4]}

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

don_crissti
la source
N'oubliez pas d'ajouter l' set -favertissement à la première solution. ... des astérisques *peut-être?
sorontar
@sorontar - pourquoi pensez-vous que j'ai besoin set -f? Je n'utilise pas read/ IFS. Essayez mes solutions avec une ficelle *_*_*ou quelque chose comme ça ...
don_crissti
Pas pour zsh, mais l'utilisateur a demandé une solution ksh. Il peut donc essayer de l'utiliser dans ce shell. Un avertissement l'aidera à éviter le problème.
sorontar
1

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
fhgd
la source
Mauvaise mauvaise réponse
Raj Kumar
0

Un 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 être utilisé avec des variables aussi.
Supposons:
this_str = "one_two_three_four_five"
Alors les travaux suivants fonctionnent:
A = `echo $ {this_str} | awk -F_ '{print $ 1}' '
B = `echo $ {this_str} | awk -F_ '{print $ 2}' '
C = `echo $ {this_str} | awk -F_ '{print $ 3}' `
... et ainsi de suite ...

utilisateur274900
la source