2011-07-31 1 views
1


Je suis intéressé par l'automatisation des tâches quotidiennes. Jusqu'à récemment, chaque partie de l'un de mes scripts fonctionnait correctement. Mais maintenant, j'essayais de mettre en œuvre la zénité et tout s'est effondré. Maintenant, j'espère que vous pourrez voir ce que je fais de mal.fonctions bash complexes: utilisation correcte?

Soo, venant à un point: je vais avoir un script bash cruel, qui ressemble à ceci:

#!/bin/bash 

dosomething() { 
    # code 
    echo $1 >> ~/test.txt # For debugging 
    $1 & # ← Important line 
    # more code 
} 

main() { 
    # other code 
    dosomething "/usr/bin/rsync $rsync_options" # ← Call it "do_1" 
    dosomething "/usr/bin/find $findme_path -iname \"*.gpx\" -print0 | xargs -0 $other_command" # ← Call it "do_2" 
    # another code 
} 

(main | (zenity --progress $zenity_options || $still_another_command) & 

Quoi (devrait) se produire: La sortie principale est canalisée à zenity (barre de progression). main n'exécute aucune commande, mais appelle dosomething avec un paramètre qui contient la commande à exécuter. dosomething exécute la commande.

Ce qui se passe vraiment: La partie "echo" de dosomething fonctionne comme prévu. Une fois le script exécuté, les commandes do_1 et do_2 apparaissent correctement dans ~/test.txt. (Si je copie & collé le contenu de ~/test.txt dans mon terminal, chaque commande a été exécutée avec les résultats attendus.)
La "ligne importante" de "do_1" est exécutée avec les résultats attendus. Mais la "ligne importante" de "do_2" semble n'avoir aucun effet. Au moins, je ne peux voir aucun effet de $ other_command après l'exécution du script. J'espère que vous comprendrez au moins ce que je veux dire. Ce serait très gentil de votre part si vous me donniez un indice sur ce qui ne va pas ici.

+0

changez le shebang en '#!/Bin/bash -x' et réexécutez-le, regardez la sortie! –

Répondre

1

Réponse courte: voir BashFAQ #50. Réponse longue: Lorsque bash analyse une ligne, il analyse les guillemets et les délimiteurs de commande (| etc.) avant d'effectuer une substitution de variable; Par conséquent, il exécute $1 & dans la fonction, les guillemets et pipe dans la valeur de $ 1 ne sont jamais analysés, ils sont simplement transmis à la commande (usr/bin/find dans ce cas) comme partie d'un argument . Résultat net: il fonctionne en réalité l'équivalent de /usr/bin/find $findme_path -iname '"*.gpx"' -print0 '|' xargs -0 $other_command". Normalement, dans des cas comme celui-ci, je recommanderais de passer la commande comme une série de mots (c.-à-d. Que la fonction exécute "[email protected]" &, et l'appelle dosomething /usr/bin/find $findme_path -iname "*.gpx" -print0 mais même cela ne gérera pas le tuyau dans la commande - il sera encore traité comme un argument de plus

Une possibilité est d'utiliser eval Cela devrait être évité si possible, car eval est un bon moyen de créer des bogues de script - bugs énormes, bugs subtils, incompréhensible bugs, bugs de sécurité ... Il ajoute une couche supplémentaire d'analyse à tout dans la ligne de commande, ce qui signifie que, par exemple, si vous essayez d'opérer sur un fichier nommé Fred's file.txt il prendra cette apostrophe comme une citation et deviendra très confus. Opérer sur des fichiers qui contiennent des back-quotes est trop horrible à envisager. Fondamentalement, c'est une mauvaise nouvelle. Après un rapide coup d'œil sur le script, je recommande un mélange de stratégies: masquer autant que possible la complexité de la commande dans les fonctions du shell, donc vous n'essayez pas de passer des tuyaux et des redirections dans le dosomething, puis utilisez l'approche de la série de mots que j'ai mentionnée plus tôt.Pour le script dans la question, je ferais:

#!/bin/bash 

dosomething() { 
    # code 
    printf "%q " "[email protected]" >> ~/test.txt # this gives a much better idea what's being done than echo $1 would 
    "[email protected]" & 
    # more code 
} 

# hide the pipeline in a shell function 
find_and_do_something() { 
    /usr/bin/find "$1" -iname "*.gpx" -print0 | xargs -0 $other_command 
} 

main() { 
    # other code 
    dosomething /usr/bin/rsync $rsync_options 
    dosomething find_and_do_something "$findme_path" 
    # another code 
} 

(main | (zenity --progress $zenity_options || $still_another_command) & 

Cela signifie que votre fichier journal sera pas les détails de la conduite qui est en cours d'exécution, juste find_and_do_something /whatevers/in/findme_path, mais au moins ça va marcher.

+0

Réponse courte: Merci. Réponse longue: Wow, je pense que tu l'as fait! Pour un amateur, celui-ci est vraiment bien caché. Bon travail! Merci aussi pour le BashFAQ, j'ai beaucoup appris! – Noob

0

Où définissez-vous $other_command? Notez également qu'il vous manque une parenthèse de fin à la fin (main ouvre une parenthèse qui n'est pas fermée).

+0

Merci pour votre réponse rapide. Je n'ai pas copié et collé mon vrai code ici - c'est trop long. Désolé d'oublier la parenthèse ici. Si j'ai copié et collé la partie entre la pièce dans do_2 (après do_something) dans le terminal, tout va bien. – Noob

+0

Le vrai script: http://nopaste.info/b77f763cdd.html – Noob

+0

activer le débogage du shell, c'est-à-dire 'set -vx'. Alors vous pouvez voir que les vars ou toute autre syntaxe ne fonctionnent pas comme vous le pensez. Aussi certains auteurs disent qu'il est préférable d'utiliser '{...; } 'pour les processus pipelinés au lieu de' (...) '. (Notez qu'une terminaison ';' avant chaque fermeture '}' est critique) Bonne chance. – shellter