2017-05-09 2 views
6

J'ai un post-achat et githook post-fusion avec ce contenu:Git crochet ne réussit pas Silencieusement

#!/bin/bash 
# MIT © Sindre Sorhus - sindresorhus.com 
set -eux 

changed_files="$(git diff-tree -r --name-only --no-commit-id [email protected]{1} HEAD)" 

check_run() { 
    echo "$changed_files" | grep --quiet "$1" && eval "$2" 
} 

echo '' 
echo 'running git submodule update --init --recursive if .gitmodules has changed' 
check_run .gitmodules "git submodule update --init --recursive" 

echo '' 
echo 'running npm install if package.json has changed' 
check_run package.json "npm prune && npm install" 

echo '' 
echo 'running npm build:localhost' 
npm run build:localhost 

Étrangement, s'il n'y a aucun changement à .gitmodules, le script se termine plutôt que des progrès pour vérifier l'emballage. Json. (il n'exécute même pas les lignes d'écho après la ligne 12)

En supprimant les invocations check_run et en mettant juste les commandes droites à la place semble fonctionner correctement. La suppression de check_run .gitmodules "git submodule update --init --recursive" fonctionne également. Cependant, le même comportement est affiché dans la ligne suivante: check_run package.json "npm prune && npm install" si package.json n'a pas été modifié

Y a-t-il quelque chose qui me manque qui provoque la fin du script lorsque le premier changement de fichier n'est pas trouvé?

Preuve visuelle: enter image description here

+0

peut-être spécifier le chemin d'accès complet au binaire 'npm'? – t0mm13b

+0

@ t0mm13b Si vous regardez l'image, vous pouvez voir qu'elle n'arrive même pas à la ligne avec npm mais échoue plutôt pendant/après grep. C'est pourquoi j'ai inclus les lignes d'écho – jth41

+0

même avec grep, inclure un chemin complet à n'importe quel binaire, pourquoi ne pas renvoyer le code d'erreur '$?' De la dernière commande exécutée. – t0mm13b

Répondre

7

Le set -e est le problème ici: -e signifie « sortie si quelque chose échoue », où « échoue » est défini comme « sort différent de zéro ».

Nous voyons dans la sortie, les dernières lignes:

+ check_run .gitmodules 'git submodule update --init --recursive' 
+ echo '' 
+ grep --quiet .gitmodules 

(et rien d'autre). Le script est sorti après grep --quiet.

est ici la définition réelle de check_run:

check_run() { 
    echo "$changed_files" | grep --quiet "$1" && eval "$2" 
} 

Ce parse comme left && rightgauche est le echo ... | grep ... et droit est le eval "$2".

Nous voyons que la partie gauche a couru et la partie droite n'a pas fonctionné. Voici où nous devons savoir quelque chose sur les shells: même avec -e ensemble, ils ne immédiatement quitter quitter si quelque chose échoue, tant que ce quelque chose fait partie d'un test. Donc, ce n'est pas immédiatement le problème.

Mais c'est toujours le problème, car left && right a comme statut de sortie l'état de sortie de la dernière chose qu'il exécute. La dernière chose qu'il a couru est le pipeline gauche, qui était echo ... | grep .... Le statut de sortie du pipeline est le statut de sortie de son dernier composant, , c'est-à-dire grep. Le grep sort zéro si elle trouve la chaîne (et avec --quiet, supprime sa sortie aussi bien), 1 sinon, et 2 sur les erreurs génériques, de sorte que l'état de sortie est 1.

Par conséquent, l'état de sortie de left && right était aussi 1.

Par conséquent, puisque -e était en vigueur, le shell est sorti! La solution est d'éviter -e (mais cela signifie que si quelque chose d'autre échoue de façon inattendue, l'interpréteur de commandes s'enfonce, donc cela peut être un peu dangereux), ou de s'assurer que le left && right ne fait pas sortir le shell.

Il y a une façon simple de faire de celui-ci: remplacer left && right avec if left; then right; fi:

if echo "$changed_files" | grep --quiet "$1"; then 
    eval "$2" 
fi 

Notez que si eval "$2" échoue, le shell sortie encore.

Il existe une manière différente et légèrement compliquée de faire cela avec un effet différent: remplacer left && right par left && right || true. Le "et" l'expression est plus prioritaire, de sorte que cela signifie:

  • Évaluer gauche
  • Si cela réussit (sort zéro), évaluer droit
  • Si l'&& échoue (soit gauche sorties non nulles, ou à droite est exécuté et à droite quitte non zéro), évaluez la partie || true, qui quitte 0.

Par conséquent:

echo "$changed_files" | grep --quiet "$1" && eval "$2" || true 

sort toujours 0, eval -ment "$2" si et seulement si le côté gauche échoue (ne trouve pas la pour l'expression rassemblés de).

Si vous voulez que check_run s'accroche même si eval "$2" échoue, utilisez la seconde version (|| true). Si vous souhaitez que check_run s'arrête (et que la sortie du shell complète) sous -e échoue, utilisez la première version (if ...; then).


très ancienne 4BSD /bin/sh, bien avant qu'il ne se open source, avait un bug: -eaurait faire la sortie de la coquille dans ces cas. (Je pense que je fixe moi-même à un moment donné, mais quand 4BSD est allé à un nouveau sh, et non à partir du code original de Steve Bourne, le bug n'était pas là du tout.)

En bash, vous pouvez contrôler cela plus en détail. En particulier, vous pouvez obtenir le statut chaque composant d'un pipeline dans la variable de tableau $PIPESTATUS.

+0

Merci beaucoup! – jth41