web-dev-qa-db-fra.com

Comment diviser une chaîne en plusieurs chaînes séparées par au moins un espace dans le shell bash?

J'ai une chaîne contenant beaucoup de mots avec au moins un espace entre chaque deux. Comment puis-je diviser la chaîne en mots individuels pour pouvoir les parcourir en boucle?

La chaîne est passée en argument. Par exemple. ${2} == "cat cat file". Comment puis-je faire une boucle?

Aussi, comment puis-je vérifier si une chaîne contient des espaces?

199
derrdji

Avez-vous essayé de passer simplement la variable chaîne à une boucle for? Bash, pour sa part, se scinde automatiquement sur les espaces blancs.

sentence="This is   a sentence."
for Word in $sentence
do
    echo $Word
done

This
is
a
sentence.
254
mob

J'aime la conversion en tableau pour pouvoir accéder à des éléments individuels:

sentence="this is a story"
stringarray=($sentence)

vous pouvez maintenant accéder directement à des éléments individuels (cela commence par 0):

echo ${stringarray[0]}

ou reconvertir en chaîne afin de boucler:

for i in "${stringarray[@]}"
do
  :
  # do whatever on $i
done

Bien sûr, la réponse directe en boucle à travers la chaîne a déjà été répondue, mais cette réponse présentait l’inconvénient de ne pas suivre les éléments individuels pour une utilisation ultérieure:

for i in $sentence
do
  :
  # do whatever on $i
done

Voir aussi Référence du tableau de Bash .

267
Highwind

Il suffit d’utiliser les "coquilles" intégrées. Par exemple,

 set $ text 

Après cela, les mots individuels dans $ text seront dans $ 1, $ 2, $ 3, etc. Pour la robustesse, on fait habituellement

 set - junk $ text 
 shift 

pour gérer le cas où $ text est vide ou commencer par un tiret. Par exemple:

 text = "Ceci est un test" 
 set - junk $ text 
 shift 
 pour Word; Est-ce que 
 fait écho "[$ Word]" 
 fait 

Cela imprime

[C'est un test]
81
Idelic

Le moyen probablement le plus facile et le plus sûr dans BASH 3 et supérieur est:

var="string    to  split"
read -ra arr <<<"$var"

(où arr est le tableau qui prend les parties fractionnées de la chaîne) ou, s'il peut y avoir des retours à la ligne dans l'entrée et que vous voulez plus que la première ligne:

var="string    to  split"
read -ra arr -d '' <<<"$var"

(notez s'il vous plaît l'espace dans -d '', il ne peut pas être laissé), mais cela pourrait vous donner une nouvelle ligne inattendue de <<<"$var" (car ceci ajoute implicitement un LF à la fin) .

Exemple:

touch NOPE
var="* a  *"
read -ra arr <<<"$var"
for a in "${arr[@]}"; do echo "[$a]"; done

Sortie attendue

[*]
[a]
[*]

car cette solution (contrairement à toutes les solutions précédentes ici) n’est pas sujette à un éblouissement inattendu et souvent incontrôlable de Shell.

En outre, cela vous donne toute la puissance d'IFS que vous souhaitez probablement:

Exemple:

IFS=: read -ra arr < <(grep "^$USER:" /etc/passwd)
for a in "${arr[@]}"; do echo "[$a]"; done

Sortie quelque chose comme:

[tino]
[x]
[1000]
[1000]
[Valentin Hilbig]
[/home/tino]
[/bin/bash]

Comme vous pouvez le constater, les espaces peuvent également être préservés:

IFS=: read -ra arr <<<' split  :   this    '
for a in "${arr[@]}"; do echo "[$a]"; done

les sorties

[ split  ]
[   this    ]

Veuillez noter que le traitement de IFS dans BASH est un sujet en soi, alors faites vos tests, quelques sujets intéressants à ce sujet:

  • unset IFS: Ignore les exécutions de SPC, TAB, NL et commence et se termine en ligne
  • IFS='': Pas de séparation de champs, lit tout
  • IFS=' ': Exécution de SPC (et SPC uniquement)

Un dernier exemple

var=$'\n\nthis is\n\n\na test\n\n'
IFS=$'\n' read -ra arr -d '' <<<"$var"
i=0; for a in "${arr[@]}"; do let i++; echo "$i [$a]"; done

les sorties

1 [this is]
2 [a test]

tandis que

unset IFS
var=$'\n\nthis is\n\n\na test\n\n'
read -ra arr -d '' <<<"$var"
i=0; for a in "${arr[@]}"; do let i++; echo "$i [$a]"; done

les sorties

1 [this]
2 [is]
3 [a]
4 [test]

BTW:

  • Si vous n'êtes pas habitué à $'ANSI-ESCAPED-STRING' vous y habituer, vous gagnerez du temps.

  • Si vous n'incluez pas -r (comme dans read -a arr <<<"$var"), la lecture est supprimée. Ceci est laissé comme exercice pour le lecteur.


Pour la deuxième question:

Pour tester quelque chose dans une chaîne, je m'en tiens généralement à case, car cela permet de vérifier plusieurs observations à la fois (remarque: la casse n'exécute que la première correspondance, si vous avez besoin d'une instruction complémentaire, multiplce case,) et ce besoin est assez souvent le cas (jeu de mots voulu):

case "$var" in
'')                empty_var;;                # variable is empty
*' '*)             have_space "$var";;        # have SPC
*[[:space:]]*)     have_whitespace "$var";;   # have whitespaces like TAB
*[^-+.,A-Za-z0-9]*) have_nonalnum "$var";;    # non-alphanum-chars found
*[-+.,]*)          have_punctuation "$var";;  # some punctuation chars found
*)                 default_case "$var";;      # if all above does not match
esac

Donc, vous pouvez définir la valeur de retour pour vérifier SPC comme ceci:

case "$var" in (*' '*) true;; (*) false;; esac

Pourquoi case? Comme il est généralement un peu plus lisible que les séquences regex, et grâce aux métacaractères de Shell, il gère très bien 99% des besoins.

58
Tino
$ echo "This is   a sentence." | tr -s " " "\012"
This
is
a
sentence.

Pour vérifier les espaces, utilisez grep:

$ echo "This is   a sentence." | grep " " > /dev/null
$ echo $?
0
$ echo "Thisisasentence." | grep " " > /dev/null     
$ echo $?
1
41
DVK

(A) Pour diviser une phrase en mots (séparés par des espaces), vous pouvez simplement utiliser l'IFS par défaut en utilisant

array=( $string )


Exemple exécutant l'extrait de code suivant

#!/bin/bash

sentence="this is the \"sentence\"   'you' want to split"
words=( $sentence )

len="${#words[@]}"
echo "words counted: $len"

printf "%s\n" "${words[@]}" ## print array

va sortir

words counted: 8
this
is
the
"sentence"
'you'
want
to
split

Comme vous pouvez le constater, vous pouvez aussi utiliser des guillemets simples ou doubles sans problème.

Notes:
- Ceci est fondamentalement la même chose que la réponse de mob , mais de cette manière, vous stockez le tableau pour tout besoin ultérieur. Si vous n'avez besoin que d'une seule boucle, vous pouvez utiliser sa réponse, qui est une ligne plus courte :)
- veuillez vous référer à cette question pour connaître d'autres méthodes permettant de fractionner une chaîne en fonction d'un délimiteur.


(B) Pour rechercher un caractère dans une chaîne, vous pouvez également utiliser une correspondance d'expression régulière.
Exemple pour vérifier la présence d'un caractère d'espacement que vous pouvez utiliser:

regex='\s{1,}'
if [[ "$sentence" =~ $regex ]]
    then
        echo "Space here!";
fi
16
Luca Borrione

Pour vérifier les espaces juste avec bash:

[[ "$str" = "${str% *}" ]] && echo "no spaces" || echo "has spaces"
6
glenn jackman