Comment puis-je utiliser les commandes if test et find de bash ensemble?

25

J'ai un répertoire avec des journaux de plantage et j'aimerais utiliser une instruction conditionnelle dans un script bash basé sur une commande find.

Les fichiers journaux sont stockés dans ce format:

/var/log/crashes/app-2012-08-28.log
/var/log/crashes/otherapp-2012-08-28.log

Je souhaite que l'instruction if ne renvoie true que s'il existe un journal des plantages pour une application spécifique qui a été modifiée au cours des 5 dernières minutes. La findcommande que j'utiliserais est:

find /var/log/crashes -name app-\*\.log -mmin -5

Je ne sais pas comment intégrer cela ifcorrectement dans une déclaration. Je pense que cela pourrait fonctionner:

if [ test `find /var/log/crashes -name app-\*\.log -mmin -5` ] then
 service myapp restart
fi

Il y a quelques domaines où je ne suis pas clair:

  • J'ai regardé les drapeaux if mais je ne sais pas lequel, le cas échéant, je devrais utiliser.
  • Ai-je besoin de la testdirective ou dois-je simplement traiter les résultats de la commande find directement, ou peut-être utiliser find... | wc -lpour obtenir un nombre de lignes à la place?
  • Pas 100% nécessaire pour répondre à cette question, mais testest-ce pour tester les codes de retour que les commandes retournent? Et ils sont en quelque sorte invisibles - en dehors de stdout/ stderr? J'ai lu la manpage mais je ne sais toujours pas quand l'utiliser testet comment la déboguer.
cwd
la source
La vraie réponse au cas général est d'utiliser find ... -exec. Voir également les exemples de commandes sous Pourquoi le bouclage sur la sortie de find est-il une mauvaise pratique?
Wildcard
@Wildcard - malheureusement, cela ne résout pas le cas général: cela ne fonctionne pas s'il y a plus d'une correspondance et l'action ne doit s'exécuter qu'une seule fois, et cela ne fonctionne pas si vous avez besoin d'une action à exécuter lorsqu'il y en a Pas de correspondance. Le premier peut être résolu en utilisant ... -exec command ';' -quit, mais je ne crois pas qu'il existe de solution pour le second autre que l'analyse du résultat. De plus, dans les deux cas, le principal problème avec l'analyse du résultat find(c'est-à-dire l'incapacité à distinguer les délimiteurs des caractères dans les noms de fichiers) ne s'applique pas, car vous n'avez pas besoin de trouver de délimiteurs dans ces cas.
Jules

Réponses:

27

[et testsont des synonymes (sauf [requis ]), donc vous ne voulez pas utiliser [ test:

[ -x /bin/cat ] && echo 'cat is executable'
test -x /bin/cat && echo 'cat is executable'

testrenvoie un état de sortie nul si la condition est vraie, sinon différente de zéro. Cela peut en fait être remplacé par n'importe quel programme pour vérifier son état de sortie, où 0 indique le succès et non nul indique l'échec:

# echoes "command succeeded" because echo rarely fails
if /bin/echo hi; then echo 'command succeeded'; else echo 'command failed'; fi

# echoes "command failed" because rmdir requires an argument
if /bin/rmdir; then echo 'command succeeded'; else echo 'command failed'; fi

Cependant, tous les exemples ci-dessus ne testent que l'état de sortie du programme et ignorent la sortie du programme.

Pour find, vous devrez tester si une sortie a été générée. -nteste une chaîne non vide:

if [[ -n $(find /var/log/crashes -name "app-*.log" -mmin -5) ]]
then
    service myapp restart
fi

Une liste complète des arguments de test est disponible en appelant help testsur la bashligne de commande.

Si vous utilisez bash(et non sh), vous pouvez utiliser [[ condition ]], qui se comporte de manière plus prévisible lorsqu'il y a des espaces ou d'autres cas spéciaux dans votre condition. Sinon, c'est généralement la même chose que l'utilisation [ condition ]. J'ai utilisé [[ condition ]]dans cet exemple, comme je le fais chaque fois que possible.

J'ai également changé `command`pour $(command), qui se comporte généralement de manière similaire, mais est plus agréable avec les commandes imbriquées.

mrb
la source
echopeut échouer: essayez echo 'oops' > /dev/full.
derobert
Cette réponse bat tout autour de la racine du problème mais évite gracieusement de mentionner exactement ce que c'est.
bahamat
9

findse fermera correctement s'il n'y a pas eu d'erreurs, vous ne pouvez donc pas compter sur son état de sortie pour savoir s'il a trouvé un fichier. Mais, comme vous l'avez dit, vous pouvez compter le nombre de fichiers trouvés et tester ce nombre.

Ce serait quelque chose comme ceci:

if [ $(find /var/log/crashes -name 'app-*.log' -mmin -5 | wc -l) -gt 0 ]; then
    ...
fi

test(aka [) ne vérifie pas les codes d'erreur des commandes, il a une syntaxe spéciale pour effectuer des tests, puis se termine avec un code d'erreur de 0 si le test a réussi, ou 1 sinon. C'est ifcelui qui vérifie le code d'erreur de la commande que vous lui passez et exécute son corps en fonction de celui-ci.

Voir man test(ou help test, si vous utilisez bash), et help if(idem).

Dans ce cas, wc -laffichera un nombre. Nous utilisons testl'option de -gtpour tester si ce nombre est supérieur à 0. Si c'est le cas, test(ou [) retournera avec le code de sortie 0. ifinterprétera ce code de sortie comme un succès et exécutera le code dans son corps.

angus
la source
1

Ce serait

if [ -n "$(find /var/log/crashes -name app-\*\.log -mmin -5)" ]; then

ou

if test -n "$(find /var/log/crashes -name app-\*\.log -mmin -5)"; then

Les commandes testet [ … ]sont également synonymes. La seule différence est leur nom, et le fait qu'il [nécessite une fermeture ]comme dernier argument. Comme toujours, utilisez des guillemets doubles autour de la substitution de commande, sinon la sortie de la findcommande sera divisée en mots, et ici vous obtiendrez une erreur de syntaxe s'il y a plus d'un fichier correspondant (et lorsqu'il n'y a pas d'arguments, [ -n ]c'est vrai , alors que vous voulez [ -n "" ]ce qui est faux).

Dans ksh, bash et zsh mais pas dans ash, vous pouvez également utiliser [[ … ]]qui a des règles d'analyse différentes: [est une commande ordinaire, alors que [[ … ]]c'est une construction d'analyse différente. Vous n'avez pas besoin de guillemets doubles à l'intérieur [[ … ]](bien qu'ils ne fassent pas de mal). Vous avez toujours besoin de la ;suite de la commande.

if [[ -n $(find /var/log/crashes -name app-\*\.log -mmin -5) ]]; then

Cela peut être potentiellement inefficace: s'il y a beaucoup de fichiers /var/log/crashes, find les explorera tous. Vous devez arrêter la recherche dès qu'elle trouve une correspondance ou peu de temps après. Avec GNU find (Linux non embarqué, Cygwin), utilisez le -quitprimaire.

if [ -n "$(find /var/log/crashes -name app-\*\.log -mmin -5 -print -quit)" ]; then

Avec d' autres systèmes, des tuyaux finddans headau moins quitter peu après le premier match (FIND va mourir d'un tuyau cassé).

if [ -n "$(find /var/log/crashes -name app-\*\.log -mmin -5 -print | head -n 1)" ]; then

(Vous pouvez utiliser head -c 1si votre headcommande le prend en charge.)


Vous pouvez également utiliser zsh.

crash_files=(/var/log/crashes/**/app-*.log(mm-5[1]))
if (($#crash_files)); then
Gilles 'SO- arrête d'être méchant'
la source
1

Cela devrait fonctionner

if find /var/log/crashes -name 'app-\*\.log' -mmin -5 | read
then
  service myapp restart
fi
Steven Penny
la source
0
find /var/log/crashes -name app-\*\.log -mmin -5 -exec service myapp restart ';' -quit

est une solution appropriée ici.

-exec service myapp restart ';'fait findinvoquer la commande que vous souhaitez exécuter directement, plutôt que d'avoir besoin du shell pour interpréter quoi que ce soit.

-quitprovoque la fermeture findaprès le traitement de la commande, empêchant ainsi la commande d'être exécutée à nouveau s'il se trouve que plusieurs fichiers correspondent aux critères.

Jules
la source