Globes Bash et passage d'arguments

8

J'ai le script bash simplifié suivant

#!/bin/bash

files=("$@")

if [ "X$files" = "X" ]; then
  files=$HOME/print/*.pdf;
fi

for file in "${files[@]}"; do
  ls "$file";
done

Si je passe des arguments (noms de fichiers) comme paramètres, ce script affichera les noms de fichiers appropriés. D'un autre côté, si je ne passe pas d'arguments, il affichera

/home/user/print/*.pdf: No such file or directory

Pourquoi les noms de fichiers ne sont-ils pas développés dans ce cas, et comment puis-je le corriger? Notez que j'utilise les constructions files=("$@")et "${files[@]}"parce que j'ai lu qu'il est préférable aux "fichiers = $ *" habituels.

highsciguy
la source
Où est-ce files=$*que c'est habituel ? C'est tout à fait faux .
Stéphane Chazelas
Habituellement, c'est relatif, non. Je voulais dire une méthode qui n'utilise pas de tableaux. Que feriez-vous alors?
highsciguy

Réponses:

10

Vous attribuez filesune variable scalaire au lieu d'une variable de tableau .

Dans

 files=$HOME/print/*.pdf

Vous assignez une chaîne comme /home/highsciguy/print/*.pdfà la $filesvariable scalaire (ou chaîne).

Utilisation:

files=(~/print/*.pdf)

ou

files=("$HOME"/print/*.pdf)

au lieu. Le shell étendra ce modèle de globalisation dans une liste de chemins de fichiers et affectera chacun d'eux aux éléments du $files tableau .

L'expansion du glob se fait au moment de l'affectation.

Vous n'avez pas besoin d'utiliser des fonctionnalités sh non standard, et vous pouvez utiliser celles de votre système à la shplace d' bashici en l'écrivant:

#!/bin/sh -

[ "$#" -gt 0 ] || set -- ~/print/*.pdf

for file do
  ls -d -- "$file"
done

setconsiste à affecter le "$@"tableau de paramètres de position.

Une autre approche aurait pu être de stocker le motif de globulation dans une variable scalaire:

files=$HOME/print/*.pdf

Et que le shell développe le glob au moment où la $files variable est développée.

IFS= # disable word splitting
for file in $files; do ...

Ici, parce qu'il $filesn'est pas cité (ce que vous ne devriez généralement pas faire), son expansion est sujette au fractionnement de mots (que nous avons désactivé ici) et à la génération de globbing / nom de fichier.

Ainsi, le *.pdf sera étendu à la liste des fichiers correspondants. Cependant, s'ils $HOMEcontiennent des caractères génériques, ils peuvent également être développés, c'est pourquoi il est toujours préférable d'utiliser une variable de tableau.

Stéphane Chazelas
la source
4

Vous avez peut-être vu des choses comme files=$*et files=~/print/*.pdfdans des coquilles plus anciennes sans tableaux, puis ls $files.

Une substitution de variable qui n'est pas entre guillemets interprète la valeur de la variable comme une liste séparée par des espaces de motifs génériques de shell qui sont remplacés par des noms de fichiers correspondants s'il y en a. Par exemple, après files=~/print/*.pdf, se ls $filesdéveloppe en quelque chose comme lsavec les arguments /home/highsciguy/print/bar.pdf, /home/highsciguy/print/foo.pdfetc. Dans le cas files=$*, cette affectation concatène les arguments passés au script avec des espaces entre les deux et les ls $filesdivise en arrière.

Tout cela tombe en panne si vous avez des noms de fichiers contenant des espaces ou des caractères globbing, c'est pourquoi vous ne devriez pas faire les choses de cette façon. Utilisez plutôt des tableaux.

files=("$@")
if ((${#files[@]} == 0)); then
  files=("$HOME"/print/*.pdf)
fi

Notez que

  • Toutes les missions de tableau exigent des parenthèses autour des valeurs de tableau: var=(…).
  • Pour tester si un tableau est vide, vérifiez sa longueur. "$files"est vide quand filesest un tableau dont l'élément d'index 0 est non défini ou une chaîne vide. Il [ "X$foo" = "X" ]existe également un moyen obsolète de tester s'il $fooest vide: tous les shells modernes sont [ -n "$foo" ]correctement mis en œuvre . En bash, vous pouvez utiliser [[ -n $foo ]].

Dans les shells qui ne prennent pas en charge les tableaux, il existe en fait un tableau: les paramètres positionnels du shell ou la fonction courante. Ici, vous n'avez pas vraiment besoin du filestableau, en fait, il serait plus facile d'utiliser les paramètres positionnels.

#!/bin/sh
if [ "$#" -eq 0 ]; then
  set -- ~/print/*.pdf
fi
for file do 
Gilles 'SO- arrête d'être méchant'
la source