Imagine à tort que la règle a réussi à cause du file de taille 0 généré par la redirection de sortie

Dans un makefile, j'ai plusieurs règles qui ressemblent à ceci:

out.txt: foo.sh input.txt ./foo.sh -i input.txt > out.txt 

Si foo.sh échoue, alors out.txt sera créé en tant que file de taille 0. Si j'exécute make encore, il supposera à tort que le file out.txt été créé avec succès et qu'il ne réexécutera pas la règle.

Quelle est la bonne façon de gérer ce genre de scénario?

Vous pouvez requestr que faire supprimer le file cible si la règle échoue, en définissant une cible spéciale nommée .DELETE_ON_ERROR . Il n'a pas besoin de faire quoi que ce soit ou d'avoir des dependencies, alors ajoutez simplement ceci à votre makefile:

 .DELETE_ON_ERROR: 

Ensuite, vous obtenez ce qui suit:

 $ cat Makefile .DELETE_ON_ERROR: foo: false > foo $ make false > foo make: *** [foo] Error 1 make: *** Deleting file `foo' zsh: exit 2 make $ stat foo stat: cannot stat `foo': No such file or directory 

Des erreurs dans les recettes :

Habituellement, lorsqu'une ligne de recette échoue, si le file cible a changé, le file est corrompu et ne peut pas être utilisé – ou du less, il n'est pas complètement mis à jour. Pourtant, l'horodatage du file indique qu'il est à jour, de sorte que la prochaine fois, il ne tentera pas de mettre à jour ce file. La situation est la même que lorsque le shell est tué par un signal; voir Interruptions . Donc, généralement, la bonne chose à faire est de supprimer le file cible si la recette échoue après avoir commencé à changer le file. make fera si .DELETE_ON_ERROR apparaît comme cible. C'est presque toujours ce que vous voulez make , mais ce n'est pas une pratique historique; donc pour la compatibilité, vous devez le requestr explicitement.

Dans la mesure du possible, les règles makefile doivent d'abord créer leur cible sous un nom temporaire, puis le déplacer en place. De cette façon, si le process de construction est interrompu pour une raison quelconque, il n'y aura pas de file cible à moitié écrit qui ne peut pas être distingué d'un file entièrement écrit.

 out.txt: foo.sh input.txt ./foo.sh -i input.txt >[email protected] mv -f [email protected] $@ 

mv -f [email protected] $@ est un idiome de makefile commun.

La réponse de Juliano montre une variante où le nom du file temporaire est généré dynamicment. La génération de nom dynamic est nécessaire s'il peut y avoir plus d'un process générant la même cible ou si le directory peut être écrit par d'autres users. C'est très rarement le cas pour un tree de construction (si ce sont des problèmes, beaucoup de ce qui se passe dans un makefile typique casserait), donc la complexité supplémentaire n'est généralement pas nécessaire.

Comme il semble que foo.sh est quelque chose que vous avez écrit (ou est généré par quelque chose que vous avez écrit), je voudrais envisager d'append un drapeau -o , qui prend le nom du file de sortie. Vous n'avez alors pas besoin de dépendre du comportement de redirection d'E / S par défaut. Votre script peut nettoyer les files de sortie partielle, ou peut-être même éviter de créer le file en premier lieu si vous pouvez détecter l'erreur assez tôt.

Je suppose que foo.sh renvoie correctement non-zéro en cas d'erreur. Ensuite, vous devriez faire un temporaire, et seulement écraser la sortie en cas de succès.

 tmp=$$(mktemp) && ./foo.sh input.txt > $$tmp && mv $$tmp $@ || rm -f $$tmp && false