Este o idee bună să folosiți „while true” pentru a menține un script în viață? (Unix, Shell, Perl)

Abhinav Gujjar a intrebat.
a intrebat.

Tocmai am sărit în unix dintr-o altă lume și am vrut să știu dacă

while true
do
  /someperlscript.pl
done

Scriptul perl în sine are în mod intern un observator de dosare/fișiere care se execută atunci când fișierele sunt modificate în locația țintă.

Este aceasta (while true) o idee bună? Dacă nu, care este o abordare robustă preferată?

TIA

EDIT : Deoarece acest lucru pare să fi generat un interes destul de mare, iată scenariul complet. Scriptul perl propriu-zis urmărește un director folosind un file watcher. La primirea de fișiere noi (acestea sosesc prin rsync), îl preia pe cel nou și îl procesează. Acum, fișierele primite pot fi corupte (nu întrebați… provenind de la un raspberry pi) și, uneori, procesul poate să nu fie capabil să le gestioneze. Nu știu exact de ce, pentru că nu cunoaștem încă toate scenariile.

DAR – dacă procesul eșuează din anumite motive, vrem ca acesta să fie funcțional și să se ocupe de următorul fișier, deoarece următorul fișier nu are nicio legătură cu cel anterior care ar fi putut cauza eroarea.

În mod normal, aș fi folosit un fel de catch all și aș fi înfășurat întregul cod în jurul acestuia, astfel încât să nu se blocheze NICIODATĂ. Dar nu eram sigur pentru perl.

Din câte am înțeles, folosirea a ceva de genul supervisord este o abordare bună pentru acest lucru.

Comentarii

  • Este vorba de Linux sau UNIX? Dacă este Linux, ați putea dori să verificați inotify API, astfel încât să puteți evita o buclă ocupată care așteaptă ca fișierele să se schimbe în directorul țintă. –  > Por roaima.
  • Este linux, ubuntu –  > Por Abhinav Gujjar.
  • Dacă știți că fișierul este bun înainte de a părăsi pi, atunci ați putea rula o sumă de control, cum ar fi md5 sau sha1, și să o trimiteți împreună cu fișierul. Apoi, destinatarul va ști dacă a primit un fișier rău înainte de a încerca să îl proceseze. Dacă nu știți acest lucru, atunci puteți construi un fel de sume de control parțiale sau pe blocuri sau ceva similar în fișierul de date care să asigure integritatea datelor, astfel încât să puteți verifica lucrurile pe parcurs înainte de a vă angaja într-un proces care poate eșua. Un gram de prevenire… –  > Por Joe.
9 răspunsuri
jlliagre

Asta depinde de cât de repede se întoarce scriptul perl. Dacă se întoarce rapid, poate doriți să introduceți o mică pauză între execuții pentru a evita încărcarea CPU, de ex:

while true
do
  /someperlscript.pl
  sleep 1
done

Acest lucru va preveni, de asemenea, o acaparare a CPU în cazul în care scriptul nu este găsit sau se blochează imediat.

De asemenea, ar fi mai bine ca bucla să fie implementată în scriptul perl în sine pentru a evita aceste probleme.

Editați:

După cum ați scris, singurul scop al buclei este de a reporni scriptul perl în cazul în care acesta se blochează, o abordare mai bună ar fi să o implementați ca serviciu monitorizat, dar modul exact de a face acest lucru depinde de sistemul de operare. De exemplu: Solaris smf, Linux systemd sau un restarter bazat pe cron.

Comentarii

  • Ați putea face următoarele while sleep 1; do ... pentru a salva apelul adevărat. –  > Por Raphael Ahrens.
  • @RaphaelAhrens Într-adevăr, deși acest lucru ar schimba ușor comportamentul inițial. Alternativa until ! sleep 1; do ...; done va salva în continuare apelul construit, în timp ce va porni imediat scriptul. –  > Por jlliagre.
  • Total, total de acord că o buclă fierbinte trebuie evitată cu un apel sleep în interiorul buclei, dacă aveți de gând să faceți acest lucru. De asemenea, sunteți de acord că un script bazat pe evenimente (inotify, et al) ar fi o soluție mai bună. Dar folosirea unei bucle while loop nu este intrinsec rea, neapărat, decât dacă este infinită și fierbinte. Cred că problema mai importantă este probabil să se ocupe de motivul pentru care scriptul perl eșuează și trebuie repornit, totuși. –  > Por Craig.
  • Mai bine: sleep 1& /someperlscript.pl; wait. –  > Por user23013.
  • @jlliagre until ! sleep 1; do ...; done de asemenea, nu va porni imediat scriptul. Este ca și cum while sleep 1; do ...; done. Ca while, until testează înainte de fiecare iterație. (until este rar folosit, așa că în cazul în care o implementare ar fi neconformă – deși mă îndoiesc că vreun shell de tip Bourne ar trata until într-un mod neconform – am testat until ! sleep 5; do echo foo; done în bash, dash, busybox sh, ksh93, mksh, lksh, posh, zsh, și yash, toate din depozitele Ubuntu 16.04). –  > Por Eliah Kagan.
Jacob Krall

Celelalte răspunsuri, despre utilizarea inotify, sunt corecte, dar nu reprezintă un răspuns la această întrebare.

Un supraveghetor de proces, cum ar fi supervisord, upstart, sau runit, este conceput exact pentru problema supravegherii și repornirii unui serviciu în cazul în care acesta se blochează.

Probabil că distribuția dvs. vine cu un supervizor de proces încorporat.

Stuart Caie

while true este bun ca o construcție „loop forever” de uz general. Așa cum spun și alte răspunsuri, corpul buclei nu ar trebui să fie gol sau să devină gol în virtutea faptului că comanda din interiorul buclei nu funcționează.

Dacă utilizați Linux, este posibil să doriți să utilizați o comandă de tipul inotifywait, care face ca while bucla mult mai simplă:

while inotifywait -qqe modify "$DIRECTORY"
do
    process_the_directory "$DIRECTORY"
done

Aici, comanda inotifywait va sta și va aștepta să se producă un eveniment în sistemul de fișiere (în acest exemplu, atunci când se scriu fișierele din director). În acel moment, aceasta iese cu succes și se execută corpul buclei. Apoi se întoarce din nou la așteptare. Deoarece inotifywait așteaptă să se întâmple ceva în director, este mult mai eficientă decât o interogare continuă a directorului.

Comentarii

  • ` (apt-get sau yum) install inotify-tools` +1 cool! –  > Por JJoao.
JJoao

Mutați while 1 în scriptul perl (În urma sugestiei lui @roaima)

#!/usr/bin/perl

 use Linux::Inotify2;

 my $inotify = new Linux::Inotify2 or die "unable to inotify: $!";

 $inotify->watch ("Dir", IN_MODIFY, ## or in_{acess,create,open, etc...}
   sub { my $e = shift;
     my $name = $e->fullname;
     ## whatever 
     print "$name was modified
" if $e->IN_MODIFY;
  });

 1 while $inotify->poll;

Comentarii

  • Acest lucru nu rezolvă cerința de repornire în cazul în care scriptul se blochează sau este omorât din anumite motive. –  > Por jlliagre.
  • @jlliagre, mulțumesc pentru comentariu. În răspuns, nu văd o specificație clară a comportamentului dorit după un accident sau o ucidere. Aceasta este în mod clar o întrebare interesantă și relevantă. Deocamdată o voi păstra simplă (dacă scriptul a murit sau a fost ucis, rămâne mort 🙂 –  > Por JJoao.
  • @jlliagre Pentru asta sunt excepțiile. Dar Perl s-ar putea să nu fie cea mai potrivită alegere într-un astfel de caz, deoarece nu suportă mecanismul de excepție. E doar o presupunere. Cel mai bine ar fi să portezi scriptul într-un limbaj care suportă excepțiile. Totul depinde de cât de mare este munca de portare, bineînțeles. – utilizator86969
  • @Nasha, În Perl, cu gestionari de semnale, variabile de eroare, Tyr::Tiny, etc., o putem face! … dar — urăsc gestionarea excepțiilor și recuperarea erorilor: nu sunt niciodată complete… –  > Por JJoao.
  • @JJoao Sunt de acord cu tine în ceea ce privește faptele că recuperarea erorilor este rareori completă. Bineînțeles că este la latitudinea dezvoltatorului să acopere toate cazurile posibile. La fel se întâmplă și cu PLC-urile din lumea industrială, prin urmare, trebuie să fie destul de posibil până la urmă 😉 . – utilizator86969
Walter A

Atunci când scriptul perl este destinat să ruleze tot timpul, de ce să folosiți construcția while ? Atunci când perl-ul cedează din cauza unei probleme grave, noul script perl inițiat de while s-ar putea prăbuși la fel de tare. Din nou și din nou și așa mai departe.
dacă doriți cu adevărat ca perl-ul dvs. să pornească din nou, luați în considerare crontab și un script care verifică mai întâi dacă există instanțe în execuție. În acest fel, scriptul dvs. va fi pornit chiar și după o repornire.

Lambert

În general, nu există nicio problemă în a utiliza while true deoarece este un test micuț care este executat doar după ce scriptul perl este terminat. Rețineți că, în funcție de varianta Linux/Unix pe care o utilizați, scriptul ar putea fi terminat la deconectare. În acest caz, luați în considerare utilizarea buclei într-un script și apelați-o cu nohup și puneți-l în fundal, adică. nohup myscript &

În cazul în care scriptul perl se termină prea des și provoacă o încărcare a CPU, atunci această încărcare este imputabilă scriptului perl și nu while true.

A se vedea și man nohup pentru detalii.

Comentarii

  • Singura problemă este potențialul unei bucle fierbinți care duce la creșterea utilizării CPU. Deci, sunt total de acord cu introducerea unui apel sleep în interiorul buclei pentru a o reduce. –  > Por Craig.
Andrew Aylett

Dacă doriți să gestionați un proces, ar fi bine să căutați un manager de proces pentru a face acest lucru.

În cazul multor sisteme recente, puteți utiliza systemd pentru a vă monitoriza procesele (acesta este unul dintre avantajele lui systemd față de scripturile init clasice). Dacă distribuția pe care o alegeți nu utilizează systemd, puteți utiliza daemontools sau monit.

Comentarii

  • mon este o alternativă simplă, dacă imensitatea greoaie a lui monit și systemd vă deranjează la fel de mult ca pe mine. –  > Por Anko.
mikeserv

while loop nu este o idee bună. Nu există nici o scăpare acolo – se execută la nesfârșit – static. Orice număr de lucruri s-ar putea schimba în mediul înconjurător și nu va fi afectat – iar acest lucru ar putea fi rău.

De exemplu, dacă executabilul de shell responsabil pentru acea while buclă este actualizat, kernelul nu va putea elibera spațiul pe disc pentru vechea versiune până când scriptul respectiv nu se închide, deoarece trebuie să mențină descriptorul atâta timp cât rulează. Și acest lucru este valabil pentru orice fișiere pe care shell-ul le-ar putea avea deschise din orice motiv pentru a rula bucla – toate vor fi menținute deschise de acest while buclă atâta timp cât aceasta rulează – pentru totdeauna.

Și dacă, Doamne ferește, există o scurgere de memorie oriunde în shell-ul care rulează această buclă – chiar și cea mai mică – pur și simplu va continua să se scurgă – static. Se va construi fără a fi verificată și singurul recurs este de a o opri cu forța, apoi de a o porni din nou doar pentru a face același lucru mai târziu.

Acesta nu este într-adevăr modul în care ar trebui să configurați un proces în fundal – cel puțin, nu în opinia mea. În schimb, așa cum cred eu, ar trebui să existe un punct de resetare – o reîmprospătare a scriptului și a stării sale. Cel mai simplu mod de a face acest lucru este cu exec. Puteți înlocui procesul curent cu unul nou – păstrând același PID – dar rulând în continuare un proces nou proces.

De exemplu, dacă perl ar trebui să returneze true după ce a gestionat cu succes o modificare de fișier într-un director monitorizat:

#!/bin/sh
trap 'rm -rf -- "${ldir%%*.}"' 0 INT
_exec() case    $#      in
        (0)     exec env -  "PID=$$" "ldir=${TMPDIR:-/tmp}/." 
                            "$0" "[email protected]";;
        (*)     export "[email protected]" "boff=0" "lmt=30"
                exec        "$0" "[email protected]";;
        esac
[ "$PID" = "$$" ] || _exec
[ -w "$ldir" ]  &&
case    $ldir   in
(*.)    until   mkdir -- "$ldir"
        do      :& ldir=$ldir$$$!
        done    2>/dev/null
;;
(*)     until   /someperlscript.pl ||
                [ "$((boff+=1))"  -ge "$lmt" ]
        do      [ -d "$ldir" ]     &&
                sleep "$boff"      || ! break
        done
;;esac  &&      _exec ldir PID

…sau ceva de genul acesta. Ceva care să permită mecanismului de bază din spatele buclei să se actualizeze din când în când.

Craig

Am votat în sus câteva dintre aceste răspunsuri, dar instinctiv am cele mai calde sentimente pentru răspunsul lui @WalterA. Ei bine, am făcut-o până când mi-am creat propriul răspuns…

Personal, aș tinde să actualizez scriptul Perl astfel încât să scrie o intrare de jurnal descriptivă despre eșec și să trimită o alertă unui administrator.

Dacă scriptul Perl este menit să continue să ruleze, așteptând evenimente de modificare din partea sistemului de fișiere, de ce eșuează?

Dacă nu eșuează, de ce vă preocupați să îl includeți într-un script pentru a-l reporni la infinit?

Dacă există o problemă de configurare sau o dependență stricată care face ca scriptul Perl să eșueze, nu este probabil ca repornirea repetată a acestuia să îl facă să înceapă să funcționeze brusc.

Cunoașteți definiția nebuniei, nu-i așa? (a face același lucru din nou și din nou, așteptând un rezultat diferit). Spuneam și eu așa. 😉

Comentarii

  • haha- Știu, știu, știu. dar știi – lumea reală e nașpa. Oricum – motivul este că procesează fișiere, iar unele fișiere s-ar putea să nu fie transmise corect. Nu știm încă varietatea de motive pentru care poate da greș. Vă înțeleg punctul de vedere cu privire la înregistrarea erorii, dar tot voi avea nevoie de o copie de rezervă pentru a procesa următorul fișier prin ceva de genul supervisord –  > Por Abhinav Gujjar.
  • Dacă puteți prinde excepțiile, puteți să le înregistrați, apoi să continuați să procesați și chiar să vă întoarceți și să încercați din când în când din nou fișierele eșuate? Poate că fișierul eșuat a fost blocat de un alt utilizator sau proces atunci când ați încercat să îl procesați etc. –  > Por Craig.

Tags:,