web-dev-qa-db-fra.com

rm ne parvient pas à supprimer des fichiers à l'aide d'un caractère générique à partir d'un script, mais fonctionne à partir d'une invite de shell

J'ai rencontré un problème vraiment idiot avec un script Linux Shell. Je veux supprimer tous les fichiers avec l'extension ".bz2" dans un répertoire. Dans le script que j'appelle

rm "$archivedir/*.bz2"

où $ archivedir est un chemin de répertoire. Devrait être assez simple, ne devrait pas? En quelque sorte, il réussit à échouer avec cette erreur:

rm: cannot remove `/var/archives/monthly/April/*.bz2': No such file or directory

Mais il y a est un fichier dans ce répertoire appelé test.bz2 et si je change mon script en

echo rm "$archivedir/*.bz2"

et copier/coller le résultat de cette ligne dans une fenêtre de terminal, le fichier est supprimé avec succès. Qu'est-ce que je fais mal?

53
EMP

TL; DR

Ne citez que la variable, et non l'ensemble du chemin attendu avec le caractère générique

rm "$archivedir"/*.bz2

Explication

  • Sous Unix, les programmes n'interprètent généralement pas les caractères génériques eux-mêmes. Le shell interprète les caractères génériques non entre guillemets et remplace chaque argument générique par une liste de noms de fichiers correspondants . Si $ archivedir peut contenir des espaces, alors rm $archivedir/*.bz2 risque de ne pas faire ce que vous voulez. 

  • Vous pouvez désactiver ce processus en citant le caractère générique, en utilisant des guillemets simples ou doubles ou une barre oblique inverse avant celui-ci. Cependant, ce n'est pas ce que vous voulez ici - vous voulez que le caractère générique soit étendu à la liste des fichiers auxquels il correspond.

  • Faites attention à écrire rm $archivedir/*.bz2 (sans guillemets). La scission de Word (c'est-à-dire la fragmentation de la ligne de commande en arguments) survient après que $ archivedir soit substitué. Donc, si $ archivedir contient des espaces, vous obtiendrez des arguments supplémentaires que vous n'aviez pas l'intention de faire. Dites archivedir est /var/archives/monthly/April to June. Vous obtiendrez alors l’équivalent d’écrire rm /var/archives/monthly/April to June/*.bz2, qui tente de supprimer les fichiers "/ var/archives/mensuel/April", "to", ainsi que tous les fichiers correspondant à "June/*. Bz2", ce qui n’est pas ce que vous voulez. vouloir.

La bonne solution est d'écrire:

rm "$ archivedir"/*. bz2
99
Doug

Votre ligne d'origine

rm "$archivedir/*.bz2"

Peut être ré-écrit comme

rm "$archivedir"/*.bz2

pour obtenir le même effet. L'expansion de caractère générique n'a pas lieu correctement dans votre configuration existante. En déplaçant le guillemet double vers le "devant" du chemin du fichier (ce qui est légitime), vous évitez cela.

10
Avery Payne

Pour en dire un peu plus sur le sujet, bash a des règles assez compliquées pour traiter les métacaractères entre guillemets. En général

  • presque rien n'est interprété entre guillemets simples:

     echo '$foo/*.c'                  => $foo/*.c
     echo '\\*'                       => \\*
    
  • La substitution de shell s'effectue entre guillemets, mais les métacaractères du fichier ne sont pas développés:

     FOO=hello; echo "$foo/*.c"       => hello/*.c
    
  • tout ce qui est entre les guillemets est transmis au sous-shell qui les interprète. Une variable Shell non exportée n'est pas définie dans le sous-shell. Ainsi, la première commande fait écho, mais les deuxième et troisième font écho "bye":

    BAR=bye echo `echo $BAR`
    BAR=bye; echo `echo $BAR`
    export BAR=bye; echo `echo $BAR`
    

(Et obtenir ceci pour imprimer comme vous le souhaitez dans SO prend plusieurs essais est apparemment impossible ...)

9
Charlie Martin

Les guillemets entraînent une interprétation de la chaîne en tant que littéral. Essayez de les supprimer. 

4
Brian Gianforcaro

Pourquoi ne pas simplement rm -rf */*.bz2? Fonctionne pour moi sur OSX.

0
holms

J'ai vu des erreurs similaires lors de l'appel d'un script Shell tel que ./Shell_script.sh à partir d'un autre script shell. Cela peut être corrigé en l'invoquant sous la forme sh Shell_script.sh

0
Christo