web-dev-qa-db-fra.com

Tableau de Bash avec des espaces dans les éléments

J'essaie de construire un tableau en bash des noms de fichiers de ma caméra:

FILES=(2011-09-04 21.43.02.jpg
2011-09-05 10.23.14.jpg
2011-09-09 12.31.16.jpg
2011-09-11 08.43.12.jpg)

Comme vous pouvez le constater, il y a un espace au milieu de chaque nom de fichier.

J'ai essayé de mettre chaque nom entre guillemets et d'échapper à l'espace avec une barre oblique inverse, ce qui ne fonctionne pas.

Lorsque j'essaie d'accéder aux éléments du tableau, il continue de traiter l'espace en tant qu'élément-élément.

Comment puis-je capturer correctement les noms de fichiers avec un espace dans le nom?

128
abelenky

Je pense que le problème pourrait être en partie lié à la façon dont vous accédez aux éléments. Si je fais un simple for elem in $FILES, Je ressens le même problème que vous. Cependant, si j'accède au tableau par ses index, comme cela, cela fonctionne si j'ajoute les éléments numériquement ou avec des échappements:

for ((i = 0; i < ${#FILES[@]}; i++))
do
    echo "${FILES[$i]}"
done

N'importe laquelle de ces déclarations de $FILES devrait marcher:

FILES=(2011-09-04\ 21.43.02.jpg
2011-09-05\ 10.23.14.jpg
2011-09-09\ 12.31.16.jpg
2011-09-11\ 08.43.12.jpg)

ou

FILES=("2011-09-04 21.43.02.jpg"
"2011-09-05 10.23.14.jpg"
"2011-09-09 12.31.16.jpg"
"2011-09-11 08.43.12.jpg")

ou

FILES[0]="2011-09-04 21.43.02.jpg"
FILES[1]="2011-09-05 10.23.14.jpg"
FILES[2]="2011-09-09 12.31.16.jpg"
FILES[3]="2011-09-11 08.43.12.jpg"
105
Dan Fego

Il doit y avoir quelque chose qui cloche dans la façon dont vous accédez aux éléments du tableau. Voici comment c'est fait:

for elem in "${files[@]}"
...

De la page de manuel de bash :

Tout élément d'un tableau peut être référencé avec $ {name [subscript]}. ... Si l'indice est @ ou *, le mot s'étend à tous les membres du nom. Ces indices ne diffèrent que lorsque le mot apparaît entre guillemets doubles. Si le mot est mis entre guillemets, $ {name [*]} se développe en un seul mot avec la valeur de chaque membre du tableau séparée par le premier caractère de la variable spéciale IFS, et $ {name [@]} développe chaque élément du nom en un mot distinct .

Bien sûr, vous devez également utiliser des guillemets lorsque vous accédez à un seul membre

cp "${files[0]}" /tmp
83
user123444555621

Vous devez utiliser IFS pour arrêter l’espace en tant que délimiteur d’élément.

FILES=("2011-09-04 21.43.02.jpg"
       "2011-09-05 10.23.14.jpg"
       "2011-09-09 12.31.16.jpg"
       "2011-09-11 08.43.12.jpg")
IFS=""
for jpg in ${FILES[*]}
do
    echo "${jpg}"
done

Si vous voulez séparer sur la base de. alors il suffit de faire IFS = "." J'espère que ça vous aide :)

37
Khushneet

Je conviens avec d’autres qu’il est probable que le problème réside dans la manière dont vous accédez aux éléments. Citer les noms de fichier dans l'assignation de tableau est correct:

FILES=(
  "2011-09-04 21.43.02.jpg"
  "2011-09-05 10.23.14.jpg"
  "2011-09-09 12.31.16.jpg"
  "2011-09-11 08.43.12.jpg"
)

for f in "${FILES[@]}"
do
  echo "$f"
done

L'utilisation de guillemets autour d'un tableau de la forme "${FILES[@]}" divise le tableau en un élément Word par tableau. Il ne fait aucune division de Word au-delà de cela.

Utiliser "${FILES[*]}" a également une signification spéciale, mais joint les éléments du tableau avec le premier caractère de $ IFS, ce qui entraîne n Word, qui est probablement pas ce que tu veux.

L'utilisation de ${array[@]} ou ${array[*]} dépouillé soumet le résultat de cette expansion à une division supplémentaire de Word. Vous obtiendrez ainsi des mots séparés sur des espaces (et toute autre chose dans le code $IFS) au lieu d'un mot par élément de tableau.

L'utilisation d'une boucle de style C est également une bonne chose et évite de se soucier de la division de Word si vous n'êtes pas clair:

for (( i = 0; i < ${#FILES[@]}; i++ ))
do
  echo "${FILES[$i]}"
done
11
Dean Hall

Pas exactement une réponse au problème de citation/d'échappement de la question initiale, mais probablement quelque chose qui aurait en fait été plus utile pour l'opération:

unset FILES
for f in 2011-*.jpg; do FILES+=("$f"); done
echo "${FILES[@]}"

Bien entendu, l’expression devrait être adaptée à l’exigence spécifique (par exemple, *.jpg pour tous ou 2001-09-11*.jpg pour seulement les images d’un certain jour).

2
TNT

Échapper à des œuvres.

#!/bin/bash

FILES=(2011-09-04\ 21.43.02.jpg
2011-09-05\ 10.23.14.jpg
2011-09-09\ 12.31.16.jpg
2011-09-11\ 08.43.12.jpg)

echo ${FILES[0]}
echo ${FILES[1]}
echo ${FILES[2]}
echo ${FILES[3]}

Sortie:

$ ./test.sh
2011-09-04 21.43.02.jpg
2011-09-05 10.23.14.jpg
2011-09-09 12.31.16.jpg
2011-09-11 08.43.12.jpg

La citation des chaînes produit également le même résultat.

2
Chris Seymour

Si vous aviez votre tableau comme ceci: #!/Bin/bash

Unix[0]='Debian'
Unix[1]="Red Hat"
Unix[2]='Ubuntu'
Unix[3]='Suse'

for i in $(echo ${Unix[@]});
    do echo $i;
done

Vous auriez:

Debian
Red
Hat
Ubuntu
Suse

Je ne sais pas pourquoi mais la boucle décompose les espaces et les place comme un élément individuel, même si vous l'entourez de guillemets.

Pour contourner ce problème, au lieu d'appeler les éléments du tableau, vous appelez les index, qui prennent la chaîne complète entourée de guillemets. Il doit être entouré de guillemets!

#!/bin/bash

Unix[0]='Debian'
Unix[1]='Red Hat'
Unix[2]='Ubuntu'
Unix[3]='Suse'

for i in $(echo ${!Unix[@]});
    do echo ${Unix[$i]};
done

Ensuite, vous aurez:

Debian
Red Hat
Ubuntu
Suse
1
Jonni2016aa

Si vous n'êtes pas coincé dans l'utilisation de bash, le traitement différent des espaces dans les noms de fichiers est l'un des avantages de fish shell . Prenons un répertoire contenant deux fichiers: "a b.txt" et "b c.txt". Voici une hypothèse raisonnable pour traiter une liste de fichiers générés à partir d'une autre commande avec bash, mais cela échoue à cause d'espaces dans les noms de fichiers que vous avez rencontrés:

# bash
$ for f in $(ls *.txt); { echo $f; }
a
b.txt
b
c.txt

Avec fish, la syntaxe est presque identique, mais vous obtenez le résultat souhaité:

# fish
for f in (ls *.txt); echo $f; end
a b.txt
b c.txt

Cela fonctionne différemment car fish divise la sortie des commandes sur les nouvelles lignes, pas les espaces.

Si vous souhaitez fractionner des espaces au lieu de nouvelles lignes, la syntaxe de fish est très lisible:

for f in (ls *.txt | string split " "); echo $f; end
0
Mark Stosberg

Une autre solution consiste à utiliser une boucle "while" à la place d'une boucle "pour":

index=0
while [ ${index} -lt ${#Array[@]} ]
  do
     echo ${Array[${index}]}
     index=$(( $index + 1 ))
  done
0
Javier Salas