Sprawdź, czy skrypt jest uruchamiany przez crona, a nie wywoływany ręcznie

23

Czy jest jakaś zmienna, którą cron ustawia podczas uruchamiania programu? Jeśli skrypt jest uruchamiany przez crona, chciałbym pominąć niektóre części; w przeciwnym razie przywołaj te części.

Skąd mam wiedzieć, czy skrypt Bash jest uruchamiany przez crona?

stokrotka
źródło
Dlaczego nie tylko my ps?
terdon
@terdon: prawdopodobnie dlatego, że psjest dość źle udokumentowany (szczególnie wersja Linuksa, która obsługuje kilka różnych stylów składni), a strona podręcznika jest jeszcze bardziej gęsta i tajemnicza niż większość narzędzi. Podejrzewam, że większość ludzi nawet nie zdaje sobie sprawy z tego, jak przydatne i wszechstronne psmoże być narzędzie .
cas

Odpowiedzi:

31

Nie wiem, cronczy domyślnie robi coś w swoim środowisku, co może być tutaj przydatne, ale jest kilka rzeczy, które możesz zrobić, aby uzyskać pożądany efekt.

1) Sprawdź, twardej lub miękkiej link do pliku skryptu, tak że, na przykład, myscripti myscript_via_cronwskazują na ten sam plik. Następnie możesz przetestować wartość $0skryptu, jeśli chcesz warunkowo uruchomić lub pominąć niektóre części kodu. Umieść odpowiednią nazwę w swoim crontab i gotowe.

2) Dodaj opcję do skryptu i ustaw tę opcję w wywołaniu crontab. Na przykład dodaj opcję -c, która nakazuje skryptowi uruchomienie lub pominięcie odpowiednich części kodu, i dodaj -cdo nazwy polecenia w twoim crontabie.

I oczywiście cron może ustawiać dowolne zmienne środowiskowe, więc możesz po prostu wstawić linię jak RUN_BY_CRON="TRUE"na twoim crontabie i sprawdzić jej wartość w skrypcie.

D_Bye
źródło
7
+1 dla RUN_BY_CRON = true
cas
odpowiedź cas działa bardzo dobrze i może być wykorzystana do wszystkiego innego
Deian
19

Skrypty uruchamiane z crona nie są uruchamiane w interaktywnych powłokach. Nie są to także skrypty startowe. Różnica polega na tym, że interaktywne powłoki mają STDIN i STDOUT dołączone do tty.

Metoda 1: sprawdź, czy $-zawiera iflagę. ijest ustawiony dla interaktywnych powłok.

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

Metoda 2: sprawdzenie $PS1jest puste.

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

odniesienie: http://techdoc.kvindesland.no/linux/gnubooks/bash/bashref_54.html

Metoda 3: przetestuj swój tty. nie jest tak niezawodny, ale w przypadku prostych zadań cron powinieneś być w porządku, ponieważ cron domyślnie nie przypisuje tty do skryptu.

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

Pamiętaj, że możesz jednak wymusić użycie interaktywnej powłoki -i, ale prawdopodobnie będziesz świadomy, jeśli robisz to ...

Tim Kennedy
źródło
1
Zauważ, że polecenie $ PS1 nie działa podczas sprawdzania, czy skrypt jest uruchamiany przez systemd, czy nie. $ - jeden robi
mveroone
1
Twój link do uniwersytetu Winnipeg jest uszkodzony.
WinEunuuchs2Unix
1
@TimKennedy Nie ma za co .... z Edmonton :)
WinEunuuchs2Unix
„case” $ - „in” nie działa w skryptach bash.
Hobadee
@Hobadee - każdy, do którego bashmam dostęp, ma $ - podobnie jak dashi ksh. mają nawet ograniczone powłoki w Solarisie. Na jakiej platformie próbujesz go używać, gdy nie działa? Co case "$-" in *i*) echo true ;; *) echo false ;; esacci pokazuje
Tim Kennedy
7

Najpierw pobierz PID crona, a następnie pobierz PID bieżącego procesu (PPID) i porównaj je:

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

Jeśli skrypt jest uruchamiany przez inny proces, który mógł zostać uruchomiony przez crona, możesz wrócić do nadrzędnych identyfikatorów PID, aż dojdziesz do $ CRONPID lub 1 (PID init).

coś takiego, może (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

From Deian: To jest wersja testowana na 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
źródło
1
W systemie Solaris cron uruchamia powłokę, a powłoka uruchamia skrypt, który sam uruchamia kolejną powłokę. Zatem pid nadrzędny w skrypcie nie jest pid cron.
Ceving
4

Jeśli plik skryptu jest wywoływany przez croni zawiera powłokę w pierwszym wierszu, tak jak #!/bin/bashmusisz znaleźć nazwę nadrzędny-nadrzędny dla swojego celu.

1) cronjest wywoływany w danym momencie w twoim crontab, wykonanie powłoki 2) powłoka wykonuje twój skrypt 3) twój skrypt działa

Nadrzędny PID jest dostępny w wersji bash jako zmienna $PPID. psPolecenie, aby uzyskać PID rodzica rodzica PID:

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

ale potrzebujemy nazwy polecenia, a nie pid, więc dzwonimy

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

teraz musimy tylko przetestować wynik dla „cron”

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

Teraz możesz przetestować w dowolnym miejscu skryptu

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

Powodzenia!

Olray
źródło
Działa to tylko w przypadku ps PS. W systemie MacOS (a także Linux, może także * BSD) możesz użyć następującego P_COMMAND:P_COMMAND=$(basename -a $(ps h -o comm $PPPID))
mdd
1

Działa na FreeBSD lub 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

Możesz iść tak daleko w górę drzewa procesów, jak chcesz.

Ted Rypma
źródło
1

Ogólne rozwiązanie pytania „czy moje wyjście jest terminalem czy uruchamiam ze skryptu” to:

( : > /dev/tty) && dev_tty_good=y || dev_tty_good=n
Stephen
źródło
0

Prosty echo $TERM | mail [email protected]w cron pokazał mi, że zarówno w systemie Linux, jak i AIX, cron wydaje się ustawiony $TERMna „głupi”.

Teoretycznie mogą być jeszcze głupie terminale, ale podejrzewam, że w większości przypadków to powinno wystarczyć ...

vegivamp
źródło
0

Nie ma autorytatywnej odpowiedzi, ale zmienne prompt ( $PS1) i terminal ( $TERM) są tutaj całkiem przyzwoite. Niektóre systemy są ustawione, TERM=dumbpodczas gdy większość pozostawia to puste, więc sprawdzimy tylko:

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

Powyższy kod zastępuje słowo „głupi”, gdy nie ma wartości dla $TERM. Dlatego warunkowy jest uruchamiany, gdy nie ma $TERMlub $TERMjest ustawiony na „głupi” lub jeśli $PS1zmienna nie jest pusta.

Przetestowałem to na Debianie 9 ( TERM=), CentOS 6.4 i 7.4 ( TERM=dumb) oraz FreeBSD 7.3 ( TERM=).

Adam Katz
źródło