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.
Essayer
echo "abcdefg" | fold -w1
Edit: Ajout d'une solution plus élégante suggérée dans les commentaires.
echo "abcdefg" | grep -o .
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))
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
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
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.
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 ]
.
Si le texte peut contenir des espaces:
eval a=( $(echo "this is a test" | sed "s/\(.\)/'\1' /g") )
$ echo hello | awk NF=NF FS=
h e l l o
Ou
$ echo hello | awk '$0=RT' RS=[[:alnum:]]
h
e
l
l
o
AWK est assez pratique:
a='123'; echo $a | awk 'BEGIN{FS="";OFS=" "} {print $1,$2,$3}'
où FS
et OFS
est le séparateur pour la lecture et l’impression
zsh solution: pour placer la variable scalaire string
dans arr
, qui sera un tableau:
arr=(${(ps::)string})
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
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"
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