Bash globbing et argument passant

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 transmets des arguments (noms de files) en tant que parameters, ce script imprimera les noms de files appropriés. D'un autre côté, si je ne passe pas d'arguments, il imprimera

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

Pourquoi les noms de files ne sont-ils pas développés dans ce cas et comment puis-je le résoudre? Notez que j'utilise les constructions files=("$@") et "${files[@]}" parce que j'ai lu qu'elle doit être préférée aux habituelles "files = $ *".

Vous affectez des files tant que variable scalaire au lieu d'une variable de tableau .

Dans

  files=$HOME/print/*.pdf 

Vous affectez une string comme /home/highsciguy/print/*.pdf à la variable $files scalar (aka ssortingng).

Utilisation:

 files=(~/print/*.pdf) 

ou

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

au lieu. Le shell étendra ce motif de globulation dans une list de paths de file et affectera chacun d'entre eux aux éléments du tableau $files .

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

Vous n'avez pas besoin d'utiliser des fonctionnalités sh non standard et vous pouvez utiliser le sh votre système plutôt que bash en l'écrivant:

 #!/bin/sh - [ "$#" -gt 0 ] || set -- ~/print/*.pdf for file do ls -d -- "$file" done 

set est d'assigner le tableau "$@" des parameters 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 variable $files est développée.

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

Ici, parce $files sont pas cités (ce que vous ne devriez pas faire habituellement), son expansion est sujette à la division des mots (que nous avons désactivé ici) et à la génération de nom de file.

Ainsi, le *.pdf sera étendu à la list des files correspondants. Cependant, si $HOME contenait des caractères generics, ils pourraient aussi être développés, c'est pourquoi il est préférable d'utiliser une variable tableau.

Vous avez peut-être vu des files=$* comme des files=$* et des files=~/print/*.pdf dans des anciens shell sans arrays, puis ls $files .

Une substitution de variable qui n'est pas dans des guillemets doubles interprète la valeur de la variable comme une list de motifs generics de shell séparés par des espaces blancs qui sont remplacés par des noms de file correspondants s'il y en a. Par exemple, après files=~/print/*.pdf , les ls $files développent en quelque chose comme ls avec les arguments /home/highsciguy/print/bar.pdf , /home/highsciguy/print/foo.pdf , etc. les files=$* cas files=$* , cette assignation concatène les arguments passés au script avec des espaces entre eux, et les ls $files séparent.

Tout cela se décompose si vous avez des noms de files contenant des espaces ou des caractères globuleux, c'est pourquoi vous ne devriez pas faire les choses de cette façon. Utilisez les arrays à la place.

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

Notez que

  • Toutes les affectations de tableau requièrent des parenthèses autour des valeurs du tableau: var=(…) .
  • Pour vérifier si un tableau est vide, vérifiez sa longueur. "$files" est vide lorsque les files sont un tableau dont l'élément de l'index 0 est désactivé ou une string vide. Aussi [ "X$foo" = "X" ] est une façon obsolète de tester si $foo est vide: tous les shells modernes implémentent correctement [ -n "$foo" ] . En bash, vous pouvez utiliser [[ -n $foo ]] .

Dans les shells qui ne supportent pas les arrays, il y a en fait un tableau: les parameters positionnels de la coquille ou de la fonction courante. Ici, vous n'avez pas vraiment besoin du tableau des files , en fait il serait plus facile d'utiliser les parameters de position.

 #!/bin/sh if [ "$#" -eq 0 ]; then set -- ~/print/*.pdf fi for file do …