Vérifiez si le script est démarré par cron, plutôt qu'appelé manuellement

23

Existe-t-il une variable définie par cron lors de l'exécution d'un programme? Si le script est exécuté par cron, je voudrais ignorer certaines parties; sinon invoquer ces parties.

Comment savoir si le script Bash est démarré par cron?

Marguerite
la source
Pourquoi tu ne viens pas de nous ps?
terdon
@terdon: probablement parce qu'il psest assez mal documenté (en particulier la version de Linux qui prend en charge plusieurs styles de syntaxe différents) et la page de manuel est encore plus dense et cryptée que la plupart des outils. Je soupçonne que la plupart des gens ne réalisent même pas à quel point un outil pspeut être utile et polyvalent .
cas

Réponses:

31

Je ne suis pas conscient que cela cronfasse quoi que ce soit par défaut à son environnement qui puisse être utile ici, mais il y a quelques choses que vous pourriez faire pour obtenir l'effet souhaité.

1) Créez un lien physique ou logiciel vers le fichier de script, de sorte que, par exemple, myscriptet myscript_via_cronpointez vers le même fichier. Vous pouvez ensuite tester la valeur de l' $0intérieur du script lorsque vous souhaitez exécuter conditionnellement ou omettre certaines parties du code. Mettez le nom approprié dans votre crontab, et vous êtes prêt.

2) Ajoutez une option au script et définissez cette option dans l'invocation crontab. Par exemple, ajoutez une option -c, qui indique au script d'exécuter ou d'omettre les parties appropriées du code, et ajoutez -cau nom de la commande dans votre crontab.

Et bien sûr, cron peut définir des variables d'environnement arbitraires, vous pouvez donc simplement mettre une ligne comme RUN_BY_CRON="TRUE"dans votre crontab, et vérifier sa valeur dans votre script.

D_Bye
la source
7
+1 pour RUN_BY_CRON = true
cas
la réponse de cas fonctionne très bien et peut être utilisée pour autre chose aussi
Deian
19

Les scripts exécutés à partir de cron ne sont pas exécutés dans des shells interactifs. Les scripts de démarrage ne le sont pas non plus. La différenciation est que les shells interactifs ont STDIN et STDOUT attachés à un tty.

Méthode 1: vérifiez si $-inclut le idrapeau. iest défini pour les shells interactifs.

case "$-" in
    *i*)
        interactive=1
        ;;
    *)
        not_interactive=1
        ;;
esac

Méthode 2: le chèque $PS1est vide.

if [ -z "$PS1" ]; then
    not_interactive=1 
else
    interactive=1
fi

référence: http://techdoc.kvindesland.no/linux/gnubooks/bash/bashref_54.html

Méthode 3: testez votre tty. ce n'est pas aussi fiable, mais pour les tâches cron simples, vous devriez être d'accord, car cron n'alloue pas de tty par défaut à un script.

if [ -t 0 ]; then
    interactive=1
else
    non_interactive=1
fi

Gardez à l'esprit que vous pouvez cependant forcer l'utilisation d'un shell interactif -i, mais vous seriez probablement au courant si vous faisiez cela ...

Tim Kennedy
la source
1
Notez que la commande $ PS1 ne fonctionne pas lors de la vérification si le script est démarré par systemd ou non. the $ - one does
mveroone
1
Votre lien avec l'Université de Winnipeg est rompu.
WinEunuuchs2Unix
1
@TimKennedy Vous êtes les bienvenus .... d'Edmonton :)
WinEunuuchs2Unix
'case "$ -" in' ne semble pas fonctionner dans les scripts bash.
Hobadee
@Hobadee - tout ce à quoi bashj'ai accès a $ -, tout comme dashet ksh. même les obus restreints de Solaris l'ont. Quelle plateforme essayez-vous de l'utiliser là où cela ne fonctionne pas? Qu'est-ce que ça case "$-" in *i*) echo true ;; *) echo false ;; esacvous montre?
Tim Kennedy
7

Tout d'abord, obtenez le PID de cron, puis obtenez le PID parent du processus en cours (PPID) et comparez-les:

CRONPID=$(ps ho %p -C cron)
PPID=$(ps ho %P -p $$)
if [ $CRONPID -eq $PPID ] ; then echo Cron is our parent. ; fi

Si votre script est démarré par un autre processus qui pourrait avoir été démarré par cron, alors vous pouvez remonter les PID parents jusqu'à ce que vous atteigniez $ CRONPID ou 1 (PID d'init).

quelque chose comme ça, peut-être (Untested-But-It-Might-Work <TM>):

PPID=$$   # start from current PID
CRON_IS_PARENT=0
CRONPID=$(ps ho %p -C cron)
while [ $CRON_IS_PARENT -ne 1 ] && [ $PPID -ne 1 ] ; do
  PPID=$(ps ho %P -p $PPID)
  [ $CRONPID -eq $PPID ] && CRON_IS_PARENT=1
done

De Deian: Ceci est une version testée sur RedHat Linux

# start from current PID
MYPID=$$
CRON_IS_PARENT=0
# this might return a list of multiple PIDs
CRONPIDS=$(ps ho %p -C crond)

CPID=$MYPID
while [ $CRON_IS_PARENT -ne 1 ] && [ $CPID -ne 1 ] ; do
        CPID_STR=$(ps ho %P -p $CPID)
        # the ParentPID came up as a string with leading spaces
        # this will convert it to int
        CPID=$(($CPID_STR))
        # now loop the CRON PIDs and compare them with the CPID
        for CRONPID in $CRONPIDS ; do
                [ $CRONPID -eq $CPID ] && CRON_IS_PARENT=1
                # we could leave earlier but it's okay like that too
        done
done

# now do whatever you want with the information
if [ "$CRON_IS_PARENT" == "1" ]; then
        CRON_CALL="Y"
else
        CRON_CALL="N"
fi

echo "CRON Call: ${CRON_CALL}"
cas
la source
1
Sous Solaris, cron démarre un shell et le shell exécute le script, qui lui-même démarre un autre shell. Donc, le pid parent dans le script n'est pas le pid de cron.
ceving
4

Si votre fichier de script est invoqué par cronet qu'il contient un shell dans la première ligne, comme #!/bin/bashvous devez trouver le nom parent-parent pour votre objectif.

1) cronest invoqué à un moment donné dans votre crontab, exécutant un shell 2) le shell exécute votre script 3) votre script est en cours d'exécution

Le PID parent est disponible en bash en tant que variable $PPID. La pscommande pour obtenir le PID parent du PID parent est:

PPPID=`ps h -o ppid= $PPID`

mais nous avons besoin du nom de la commande, pas du pid, donc nous appelons

P_COMMAND=`ps h -o %c $PPPID`

il nous suffit maintenant de tester le résultat pour "cron"

if [ "$P_COMMAND" == "cron" ]; then
  RUNNING_FROM_CRON=1
fi

Vous pouvez maintenant tester n'importe où dans votre script

if [ "$RUNNING_FROM_CRON" == "1" ]; then
  ## do something when running from cron
else
  ## do something when running from shell
fi

Bonne chance!

Olray
la source
Cela ne fonctionne que pour Linux ps. Pour MacOS (ainsi que Linux, peut-être aussi * BSD), vous pouvez utiliser le P_COMMAND suivant:P_COMMAND=$(basename -a $(ps h -o comm $PPPID))
mdd
1

Fonctionne sur FreeBSD ou sur Linux:

if [ "Z$(ps o comm="" -p $(ps o ppid="" -p $$))" == "Zcron" -o \
     "Z$(ps o comm="" -p $(ps o ppid="" -p $(ps o ppid="" -p $$)))" == "Zcron" ]
then
    echo "Called from cron"
else
    echo "Not called from cron"
fi

Vous pouvez aller aussi loin que vous le souhaitez dans l'arborescence des processus.

Ted Rypma
la source
1

Une solution générique à la question «ma sortie est-elle un terminal ou suis-je en train d'exécuter à partir d'un script» est:

( : > /dev/tty) && dev_tty_good=y || dev_tty_good=n
Stephen
la source
0

Un simple echo $TERM | mail [email protected]dans cron m'a montré que sous Linux et AIX, cron semble réglé $TERMsur 'muet'.

Maintenant, théoriquement, il peut toujours y avoir de vrais terminaux stupides, mais je soupçonne que pour la plupart des occasions, cela devrait suffire ...

vegivamp
la source
0

Il n'y a pas de réponse faisant autorité, mais les variables prompt ( $PS1) et terminal ( $TERM) sont plutôt correctes ici. Certains systèmes sont définis TERM=dumbtandis que la plupart le laissent vide, nous allons donc simplement vérifier:

if [ "${TERM:-dumb}$PS1" != "dumb" ]; then
  echo "This is not a cron job"
fi

Le code ci-dessus remplace le mot "muet" lorsqu'il n'y a pas de valeur pour $TERM. Par conséquent, le conditionnel se déclenche lorsqu'il n'y a pas $TERMou $TERMest défini sur "muet" ou si la $PS1variable n'est pas vide.

J'ai testé cela sur Debian 9 ( TERM=), CentOS 6.4 & 7.4 ( TERM=dumb) et FreeBSD 7.3 ( TERM=).

Adam Katz
la source