web-dev-qa-db-fra.com

Bash: scinder la chaîne de caractères dans un tableau de caractères

J'ai une chaîne dans un script Shell Bash que je souhaite scinder en un tableau de caractères, non basé sur un délimiteur, mais juste un caractère par index de tableau. Comment puis-je faire ceci? Idéalement, aucun programme externe ne serait utilisé. Permettez-moi de reformuler cela. Mon objectif étant la portabilité, les éléments tels que sed susceptibles de figurer sur n’importe quel système compatible POSIX conviennent parfaitement.

50
extropic-engine

Essayer

echo "abcdefg" | fold -w1

Edit: Ajout d'une solution plus élégante suggérée dans les commentaires.

echo "abcdefg" | grep -o .
85
xdazz

Vous pouvez accéder à chaque lettre individuellement déjà sans conversion de tableau:

$ foo="bar"
$ echo ${foo:0:1}
b
$ echo ${foo:1:1}
a
$ echo ${foo:2:1}
r

Si cela ne suffit pas, vous pouvez utiliser quelque chose comme ceci:

$ bar=($(echo $foo|sed  's/\(.\)/\1 /g'))
$ echo ${bar[1]}
a

Si vous ne pouvez même pas utiliser sed ou quelque chose comme ça, vous pouvez utiliser la première technique ci-dessus combinée avec une boucle while utilisant la longueur de la chaîne d'origine (${#foo}) pour construire le tableau.

Attention: le code ci-dessous ne fonctionne pas si la chaîne contient des espaces. Je pense que la réponse de Vaughn Cato a plus de chances de survivre avec des personnages spéciaux.

thing=($(i=0; while [ $i -lt ${#foo} ] ; do echo ${foo:$i:1} ; i=$((i+1)) ; done))
28
Mat

Si votre chaîne est stockée dans la variable x, cela produit un tableau y avec les caractères individuels:

i=0
while [ $i -lt ${#x} ]; do y[$i]=${x:$i:1};  i=$((i+1));done
9
Vaughn Cato

En guise d'alternative à l'itération sur 0 .. ${#string}-1 avec une boucle for/while, il existe deux autres façons de procéder avec only bash: en utilisant =~ et en utilisant printf. (Il existe une troisième possibilité utilisant eval et une expression de séquence {..}, mais cela manque de clarté.) 

Avec le bon environnement et le mode NLS activé dans bash, ceux-ci fonctionneront avec le non-ASCII comme prévu, éliminant les sources potentielles de défaillance avec les anciens outils système tels que sed, si cela pose un problème. Ceux-ci fonctionneront à partir de bash-3.0 (publié en 2005).

À l'aide de =~ et d'expressions régulières, convertissez une chaîne en tableau en une seule expression:

string="wonkabars"
[[ "$string" =~ ${string//?/(.)} ]]       # splits into array
printf "%s\n" "${BASH_REMATCH[@]:1}"      # loop free: reuse fmtstr
declare -a arr=( "${BASH_REMATCH[@]:1}" ) # copy array for later

La façon dont cela fonctionne consiste à effectuer une expansion de string qui substitue chaque caractère à (.), puis associe cette expression régulière générée à un groupement pour capturer chaque caractère individuel dans BASH_REMATCH[]. L'index 0 est défini sur la chaîne entière, car ce tableau spécial est en lecture seule, vous ne pouvez pas le supprimer. Notez le :1 lorsque le tableau est développé pour ignorer l'index 0, si nécessaire . Quelques tests rapides pour des chaînes non triviales (> 64 caractères) indique que cette méthode est pratiquement plus rapide qu'une méthode utilisant des opérations de chaîne et de tableau bash.

Ce qui précède fonctionnera avec des chaînes contenant des nouvelles lignes, =~ supporte POSIX ERE où . correspond à tout sauf NUL par défaut, c’est-à-dire que l’expression rationnelle est compilée sans REG_NEWLINE. (Le comportement du traitement de texte POSIX utilities est autorisé à être différent par défaut à cet égard, et l'est généralement.)

Deuxième option, en utilisant printf:

string="wonkabars"
ii=0
while printf "%s%n" "${string:ii++:1}" xx; do 
  ((xx)) && printf "\n" || break
done 

Cette boucle incrémente index ii pour imprimer un caractère à la fois, et se déclenche lorsqu'il ne reste plus de caractères. Cela serait encore plus simple si bash printf renvoyait le nombre de caractères imprimés (comme en C) plutôt qu'un statut d'erreur, mais le nombre de caractères imprimés est capturé dans xx à l'aide de %n. (Cela fonctionne au moins aussi loin que bash-2.05b.)

Avec bash-3.1 et printf -v var, vous avez un peu plus de flexibilité et évitez de tomber en fin de chaîne si vous ne faites pas qu'imprimer les caractères, par exemple. pour créer un tableau:

declare -a arr
ii=0
while printf -v cc "%s%n" "${string:(ii++):1}" xx; do 
    ((xx)) && arr+=("$cc") || break
done
7
mr.spuratic

La solution la plus simple, complète et élégante: 

$ read -a ARRAY <<< $(echo "abcdefg" | sed 's/./& /g')  

et test 

$ echo ${ARRAY[0]}
  a

$ echo ${ARRAY[1]}
  b

Explication: read -a lit le stdin en tant que tableau et l'assigne à la variable ARRAY traitant les espaces comme délimiteur pour chaque élément du tableau. 

L'évaluation de l'écho de la chaîne à ajouter ne fait qu'ajouter des espaces nécessaires entre chaque caractère. 

Nous utilisons Here String (<<<) pour alimenter le stdin de la commande de lecture. 

2
string=hello123

for i in $(seq 0 ${#string})
    do array[$i]=${string:$i:1}
done

echo "zero element of array is [${array[0]}]"
echo "entire array is [${array[@]}]"

L'élément zéro du tableau est [h]. Le tableau entier est [h e l l o 1 2 3 ].

2
0x00

Si le texte peut contenir des espaces:

eval a=( $(echo "this is a test" | sed "s/\(.\)/'\1' /g") )
1
Karoly Horvath
$ echo hello | awk NF=NF FS=
h e l l o

Ou

$ echo hello | awk '$0=RT' RS=[[:alnum:]]
h
e
l
l
o
1
Steven Penny

AWK est assez pratique:

a='123'; echo $a | awk 'BEGIN{FS="";OFS=" "} {print $1,$2,$3}'

FS et OFS est le séparateur pour la lecture et l’impression

0
Tony Xu

zsh solution: pour placer la variable scalaire string dans arr, qui sera un tableau:

arr=(${(ps::)string})
0
Ed Grimm

Pour ceux qui ont atterri ici en cherchant comment faire ceci dans fish :

Nous pouvons utiliser la commande string intégrée (depuis la v2.3.0) pour la manipulation de chaînes.

↪ string split '' abc
a
b
c

La sortie est une liste, donc les opérations sur les tableaux vont fonctionner.

↪ for c in (string split '' abc)
      echo char is $c
  end
char is a
char is b
char is c

Voici un exemple plus complexe parcourant la chaîne avec un index.

↪ set --local chars (string split '' abc)
  for i in (seq (count $chars))
      echo $i: $chars[$i]
  end
1: a
2: b
3: c
0
Dennis

En réponse à Alexandro de Oliveira, je pense que ce qui suit est plus élégant ou du moins plus intuitif:

while read -r -n1 c ; do arr+=("$c") ; done <<<"hejsan"
0
methuselah-0

Si vous voulez stocker ceci dans un tableau, vous pouvez faire ceci:

string=foo
unset chars
declare -a chars
while read -N 1
do
    chars[${#chars[@]}]="$REPLY"
done <<<"$string"x
unset chars[$((${#chars[@]} - 1))]
unset chars[$((${#chars[@]} - 1))]

echo "Array: ${chars[@]}"
Array: f o o
echo "Array length: ${#chars[@]}"
Array length: 3

La version finale x est nécessaire pour gérer le fait qu'une nouvelle ligne est ajoutée après le $string si elle n'en contient pas.

Si vous voulez utiliser des caractères séparés par NUL, essayez ceci:

echo -n "$string" | while read -N 1
do
    printf %s "$REPLY"
    printf '\0'
done
0
l0b0