Tableau bash avec des espaces dans les éléments


150

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 voir, 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 comme le délimiteur d'élément.

Comment puis-je capturer correctement les noms de fichiers avec un espace à l'intérieur du nom?


Avez-vous essayé d'ajouter les fichiers à l'ancienne? Comme FILES[0] = ...? (Edit: je viens de le faire; ne fonctionne pas. Intéressant).
Dan Fego


Toutes les réponses ici se décomposent pour moi en utilisant Cygwin. Cela fait des choses étranges s'il y a des espaces dans les noms de fichiers, point. Je contourne ce problème en créant un "tableau" dans un fichier texte répertoriant tous les éléments avec lesquels je souhaite travailler, et en effectuant une itération sur les lignes du fichier: Le formatage est dégoûtant avec les rétroprojecteurs voulus entourant la commande entre parenthèses: IFS = ""; tableau = ( find . -maxdepth 1 -type f -iname \*.$1 -printf '%f\n'); pour l'élément dans $ {array [@]}; faire echo $ element; done
Alex Hall

Réponses:


122

Je pense que le problème tient peut-être en partie à la manière dont vous accédez aux éléments. Si je fais un simple for elem in $FILES, je rencontre le même problème que vous. Cependant, si j'accède au tableau via ses indices, comme ça, cela fonctionne si j'ajoute les éléments soit numériquement ou avec des échappements:

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

Chacune de ces déclarations de $FILESdevrait fonctionner:

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"

6
Notez que vous devez utiliser des guillemets doubles lorsque vous utilisez les éléments du tableau (par exemple echo "${FILES[$i]}"). Cela n'a pas d'importance echo, mais ce sera pour tout ce qui l'utilise comme nom de fichier.
Gordon Davisson

26
Il n'est pas nécessaire de boucler sur les index lorsque vous pouvez boucler sur les éléments avec for f in "${FILES[@]}".
Mark Edgar

10
@MarkEdgar J'ai des problèmes avec for f dans $ {FILES [@]} lorsque les membres du tableau ont des espaces. Il semble que l'ensemble du tableau soit réinterprété à nouveau, les espaces crachant vos membres existants en deux ou plusieurs éléments. Il semble que les "" sont très importants
Michael Shaw

1
Que fait le #symbole sharp ( ) dans l' for ((i = 0; i < ${#FILES[@]}; i++))instruction?
Michal Vician

4
J'ai répondu il y a six ans , mais je crois qu'il est d'obtenir le nombre du nombre d'éléments dans les fichiers du tableau.
Dan Fego

91

Il doit y avoir un problème avec la façon dont vous accédez aux éléments du tableau. Voici comment procéder:

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

Depuis la page de manuel bash :

Tout élément d'un tableau peut être référencé en utilisant $ {name [indice]}. ... Si l'indice est @ ou *, le mot s'étend à tous les membres de nom. Ces indices ne diffèrent que lorsque le mot apparaît entre guillemets. Si le mot est 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 de nom à un mot séparé .

Bien sûr, vous devez également utiliser des guillemets doubles lors de l'accès à un seul membre

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

3
La solution la plus propre et la plus élégante de ce groupe, mais devrait réitérer que chaque élément défini dans le tableau devrait être cité.
maverick

Bien que la réponse de Dan Fego soit efficace, c'est la manière la plus idiomatique de gérer les espaces dans les éléments.
Daniel Zhang

3
Venant d'autres langages de programmation, la terminologie de cet extrait est vraiment difficile à comprendre. De plus, la syntaxe est déconcertante. Je vous serais très reconnaissant si vous pouviez y entrer un peu plus? En particulierexpands to a single word with the value of each array member separated by the first character of the IFS special variable
CL22

1
Oui, d'accord, les guillemets le résolvent et c'est mieux que d'autres solutions. Pour expliquer plus en détail - la plupart des autres manquent simplement de guillemets doubles. Vous avez le bon:, for elem in "${files[@]}"alors qu'ils ont for elem in ${files[@]}- donc les espaces confondent l'expansion et pour les essais en cours d'exécution sur les mots individuels.
arntg

Cela ne fonctionne pas pour moi dans macOS 10.14.4, qui utilise "GNU bash, version 3.2.57 (1) -release (x86_64-apple-darwin18)". Peut-être un bogue dans l'ancienne version de bash?
Mark Ribau

43

Vous devez utiliser IFS pour arrêter l'espace comme 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 souhaitez vous séparer sur la base de. alors faites simplement IFS = "." J'espère que cela vous aide :)


3
J'ai dû déplacer IFS = "" avant l'affectation du tableau mais c'est la bonne réponse.
rob

J'utilise plusieurs tableaux pour analyser les informations et j'aurai l'effet de IFS = "" travaillant dans un seul d'entre eux. Une fois que j'utilise IFS = "", tous les autres tableaux arrêtent l'analyse en conséquence. Des indices à ce sujet?
Paulo Pedroso

Paulo, voyez ici une autre réponse qui pourrait être meilleure pour votre cas: stackoverflow.com/a/9089186/1041319 . Je n'ai pas essayé IFS = "", et semble le résoudre avec élégance - mais votre exemple montre pourquoi on peut rencontrer des problèmes dans certains cas. Il peut être possible de définir IFS = "" sur une seule ligne, mais cela peut être encore plus déroutant que l'autre solution.
arntg

Cela a également fonctionné pour moi sur bash. Merci @Khushneet Je le cherchais pendant une demi-heure ...
csonuryilmaz

Super, seule réponse sur cette page qui a fonctionné. Mais j'ai également dû déplacer l' IFS="" avant la construction du tableau .
pkamb

13

Je suis d'accord avec les autres pour dire que c'est probablement la façon dont vous accédez aux éléments qui pose problème. La citation des noms de fichier dans l'affectation de tableau est correcte:

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 doubles autour de n'importe quel tableau du formulaire "${FILES[@]}"divise le tableau en un mot par élément de tableau. Cela ne divise pas les mots au-delà de cela.

L'utilisation a "${FILES[*]}"également une signification particulière, mais elle joint les éléments du tableau avec le premier caractère de $ IFS, ce qui donne un mot, ce qui n'est probablement pas ce que vous voulez.

En utilisant un nu ${array[@]}ou ${array[*]}soumet le résultat de cette expansion à un fractionnement supplémentaire des mots, vous vous retrouverez donc avec des mots divisés sur des espaces (et tout ce qui s'y trouve $IFS) au lieu d'un mot par élément du tableau.

Utiliser une boucle for de style C est également très bien et évite de s'inquiéter de la division des mots si vous n'êtes pas clair:

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

3

Des œuvres qui s'échappent.

#!/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]}

Production:

$ ./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

Le fait de citer les chaînes produit également la même sortie.


3

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 obtiendrez:

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 obtiendrez:

Debian
Red Hat
Ubuntu
Suse

2

Pas exactement une réponse au problème de citation / échappement de la question originale 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[@]}"

Où bien sûr l'expression devrait être adaptée à l'exigence spécifique (par exemple *.jpgpour tous ou 2001-09-11*.jpgpour seulement les images d'un certain jour).


0

Une autre solution consiste à utiliser une boucle «while» à la place d'une boucle «for»:

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

0

Si vous n'êtes pas coincé dans l'utilisation bash, la gestion différente des espaces dans les noms de fichiers est l'un des avantages de la coquille de poisson . Prenons un répertoire contenant deux fichiers: "a b.txt" et "b c.txt". Voici une estimation raisonnable du traitement d'une liste de fichiers générés à partir d'une autre commande avec bash, mais cela échoue en raison 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 le résultat est ce à quoi vous vous attendez:

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

Cela fonctionne différemment parce que le poisson divise la sortie des commandes sur les nouvelles lignes, pas sur les espaces.

Si vous avez un cas où vous souhaitez fractionner sur des espaces au lieu de nouvelles lignes, fisha une syntaxe très lisible pour cela:

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

0

J'avais l'habitude de réinitialiser la valeur IFS et de revenir en arrière une fois terminé.

# backup IFS value
O_IFS=$IFS

# reset IFS value
IFS=""

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 file in ${FILES[@]}; do
    echo ${file}
done

# rollback IFS value
IFS=${O_IFS}

Sortie possible de la boucle:

04/09/2011 21.43.02.jpg

05/09/2011 10.23.14.jpg

09/09/2011 12.31.16.jpg

11/09/2011 08.43.12.jpg

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.