Cron Linuxis: ajalugu, kasutamine ja seade

Cron Linuxis: ajalugu, kasutamine ja seade

Klassik kirjutas, et õnnelikke tunde ei vaata. Tol metsikul ajal polnud programmeerijaid ega Unixit, kuid tänapäeval teavad programmeerijad kindlalt: nende asemel jälgib aega cron.

Käsurea utiliidid on minu jaoks nii nõrkus kui ka töö. sed, awk, wc, cut ja muid vanu programme käitavad meie serverites iga päev skriptid. Paljud neist on loodud ülesanneteks 70ndatest pärit ajakavale cronile.

Pikka aega kasutasin cronit pealiskaudselt, detailidesse laskumata, kuid ühel päeval, kui skripti käivitamisel tekkis viga, otsustasin seda põhjalikult uurida. Nii see artikkel ilmus, selle kirjutamise ajal sain tuttavaks POSIX crontabiga, populaarsete Linuxi distributsioonide peamiste cron-valikutega ja mõnede nende struktuuriga.

Kas kasutate Linuxit ja kasutate cron-i ülesandeid? Kas olete huvitatud Unixi süsteemirakenduste arhitektuurist? Siis oleme teel!

Sisu

Liigi päritolu

Kasutaja- või süsteemiprogrammide perioodiline täitmine on kõigis operatsioonisüsteemides ilmselge vajadus. Seetõttu mõistsid programmeerijad juba ammu vajadust teenuste järele, mis võimaldavad neil ülesandeid tsentraalselt planeerida ja täita.

Unixilaadsed operatsioonisüsteemid pärinevad Unixi versioonist 7, mis töötati välja eelmise sajandi 70. aastatel ettevõttes Bell Labs, sealhulgas kuulsa Ken Thompsoni poolt. Versioon 7 Unix sisaldas ka cronit, teenust superkasutaja ülesannete regulaarseks käitamiseks.

Tüüpiline kaasaegne cron on lihtne programm, kuid algversiooni tööalgoritm oli veelgi lihtsam: teenus ärkas kord minutis, luges ühest failist (/etc/lib/crontab) ülesannetega tabelit ja teostas superkasutaja neid ülesandeid, mida oleks tulnud praegusel hetkel täita.

Seejärel tarniti kõigi Unixi-laadsete operatsioonisüsteemidega lihtsa ja kasuliku teenuse täiustatud versioonid.

Crontab-vormingu üldistatud kirjeldused ja utiliidi tööpõhimõtted lisati 1992. aastal Unixi-laadsete operatsioonisüsteemide põhistandardisse - POSIX - ning seega sai de facto standardist pärit cron de jure standardiks.

1987. aastal andis Paul Vixie, küsitledes Unixi kasutajaid nende cron-i soovide kohta, välja teise versiooni deemonist, mis parandas mõned traditsioonilise croni probleemid ja laiendas tabelifailide süntaksit.

Kolmandaks Vixie cron versiooniks hakkas vastama POSIX-i nõuetele, lisaks oli programmil liberaalne litsents või õigemini polnud litsentsi üldse, välja arvatud README-s olevad soovid: autor ei anna garantiisid, autori nimi ei saa kustutada ja programmi saab müüa ainult koos lähtekoodiga. Need nõuded osutusid ühilduvaks neil aastatel populaarsust kogunud vaba tarkvara põhimõtetega, nii et mõned 90ndate alguses ilmunud peamised Linuxi distributsioonid võtsid Vixie croni oma süsteemiks ja arendavad seda siiani.

Eelkõige arendavad Red Hat ja SUSE Vixie cron - cronie kahvlit ning Debian ja Ubuntu kasutavad Vixie croni originaalväljaannet koos paljude paikadega.

Tutvume esmalt POSIXis kirjeldatud kasutajautiliidi crontabiga, misjärel vaatame Vixie cron pakutavaid süntaksilaiendeid ja Vixie cron variatsioonide kasutamist populaarsetes Linuxi distributsioonides. Ja lõpuks, kirss tordil on cron deemoni seadme analüüs.

POSIX crontab

Kui algne cron töötas alati superkasutaja jaoks, tegelevad tänapäevased planeerijad sageli tavakasutajate ülesannetega, mis on turvalisem ja mugavam.

Crons tarnitakse kahe programmi komplektina: pidevalt töötav cron deemon ja kasutajatele kättesaadav utiliit crontab. Viimane võimaldab redigeerida igale süsteemi kasutajale omaseid ülesannete tabeleid, samas kui deemon käivitab ülesanded kasutaja- ja süsteemitabelitest.

В POSIX standard deemoni käitumist ei kirjeldata kuidagi ja vormistatakse ainult kasutajaprogramm crontab. Kasutajaülesannete käivitamise mehhanismide olemasolu on loomulikult kaudne, kuid seda ei kirjeldata üksikasjalikult.

Utiliidi crontab kutsudes saate teha nelja asja: redigeerida redaktoris kasutaja ülesannete tabelit, laadida tabel failist, kuvada praegune ülesannete tabel ja tühjendada ülesannete tabel. Näited utiliidi crontab toimimisest:

crontab -e # редактировать таблицу задач
crontab -l # показать таблицу задач
crontab -r # удалить таблицу задач
crontab path/to/file.crontab # загрузить таблицу задач из файла

Helistades crontab -e kasutatakse standardses keskkonnamuutujas määratud redaktorit EDITOR.

Ülesanded ise on kirjeldatud järgmises vormingus:

# строки-комментарии игнорируются
#
# задача, выполняемая ежеминутно
* * * * * /path/to/exec -a -b -c
# задача, выполняемая на 10-й минуте каждого часа
10 * * * * /path/to/exec -a -b -c
# задача, выполняемая на 10-й минуте второго часа каждого дня и использующая перенаправление стандартного потока вывода
10 2 * * * /path/to/exec -a -b -c > /tmp/cron-job-output.log

Kirjete esimesed viis välja: minutid [1..60], tunnid [0..23], kuupäevad [1..31], kuud [1..12], nädalapäevad [0. .6], kus 0 on pühapäev. Viimane, kuues väli on rida, mille käivitab tavaline käsutõlk.

Esimesel viiel väljal saab loetleda väärtused komadega eraldatuna:

# задача, выполняемая в первую и десятую минуты каждого часа
1,10 * * * * /path/to/exec -a -b -c

Või sidekriipsuga:

# задача, выполняемая в каждую из первых десяти минут каждого часа
0-9 * * * * /path/to/exec -a -b -c

Kasutajate juurdepääsu ülesannete ajastamisele reguleerivad POSIX-is failid cron.allow ja cron.deny, mis loetlevad vastavalt kasutajad, kellel on juurdepääs crontabile, ja kasutajad, kellel puudub juurdepääs programmile. Standard ei reguleeri nende failide asukohta kuidagi.

Standardi kohaselt tuleb käivitatud programmidele edastada vähemalt neli keskkonnamuutujat:

  1. KODU – kasutaja kodukataloog.
  2. LOGNAME — kasutaja sisselogimine.
  3. PATH on tee, kust leiate standardsed süsteemiutiliidid.
  4. SHELL — tee kasutatud käsutõlgi juurde.

Eelkõige ei ütle POSIX midagi selle kohta, kust nende muutujate väärtused pärinevad.

Enim müüdud – Vixie cron 3.0pl1

Populaarsete cron-variantide ühine esivanem on Vixie cron 3.0pl1, mis võeti kasutusele comp.sources.unixi meililistis 1992. aastal. Vaatleme üksikasjalikumalt selle versiooni põhifunktsioone.

Vixie cron on saadaval kahes programmis (cron ja crontab). Nagu tavaliselt, vastutab deemon ülesannete lugemise ja käitamise eest süsteemiülesannete tabelist ja üksikute kasutajate ülesannete tabelitest ning utiliit crontab vastutab kasutajatabelite redigeerimise eest.

Ülesande tabel ja konfiguratsioonifailid

Superkasutaja ülesannete tabel asub failis /etc/crontab. Süsteemitabeli süntaks vastab Vixie cron süntaksile, välja arvatud see, et selle kuues veerg näitab selle kasutaja nime, kelle nimel ülesanne käivitatakse:

# Запускается ежеминутно от пользователя vlad
* * * * * vlad /path/to/exec

Tavakasutajate ülesannete tabelid asuvad kaustas /var/cron/tabs/username ja kasutavad sama süntaksit. Kui käivitate utiliidi crontab kasutajana, redigeeritakse neid faile.

Kasutajate loendeid, kellel on juurdepääs crontabile, hallatakse failides /var/cron/allow ja /var/cron/deny, kus peate lihtsalt sisestama kasutajanime eraldi reale.

Laiendatud süntaks

Võrreldes POSIX crontabiga sisaldab Paul Vixey lahendus mitmeid väga kasulikke muudatusi utiliidi ülesannete tabelite süntaksis.

Saadaval on uus tabeli süntaks: näiteks saate nime järgi määrata nädalapäevad või kuud (esmaspäev, teisipäev jne):

# Запускается ежеминутно по понедельникам и вторникам в январе
* * * Jan Mon,Tue /path/to/exec

Saate määrata sammu, mille kaudu ülesanded käivitatakse:

# Запускается с шагом в две минуты
*/2 * * * Mon,Tue /path/to/exec

Samme ja intervalle saab segada:

# Запускается с шагом в две минуты в первых десять минут каждого часа
0-10/2 * * * * /path/to/exec

Toetatakse intuitiivseid alternatiive tavapärasele süntaksile (taaskäivitamine, kord aastas, kord aastas, iga kuu, iga nädal, iga päev, kesköö, tunni tagant):

# Запускается после перезагрузки системы
@reboot /exec/on/reboot
# Запускается раз в день
@daily /exec/daily
# Запускается раз в час
@hourly /exec/daily

Ülesande täitmise keskkond

Vixie cron võimaldab muuta töötavate rakenduste keskkonda.

Keskkonnamuutujaid USER, LOGNAME ja HOME ei paku lihtsalt deemon, vaid need võetakse failist passwd. PATH muutuja on seatud väärtusele "/usr/bin:/bin" ja SHELL muutuja väärtuseks "/bin/sh". Kõikide muutujate, välja arvatud LOGNAME, väärtusi saab muuta kasutajatabelites.

Mõnda keskkonnamuutujat (eelkõige SHELL ja HOME) kasutab cron ise ülesande käitamiseks. Bashi kasutamine kohandatud ülesannete käitamiseks standardse sh asemel võib välja näha järgmine:

SHELL=/bin/bash
HOME=/tmp/
# exec будет запущен bash-ем в /tmp/
* * * * * /path/to/exec

Lõppkokkuvõttes edastatakse kõik tabelis määratletud keskkonnamuutujad (mida kasutab cron või mida protsess vajab) töötavale toimingule.

Failide redigeerimiseks kasutab crontab keskkonnamuutujas VISUAL või EDITOR määratud redaktorit. Kui keskkonnas, kus crontab käitati, pole neid muutujaid defineeritud, kasutatakse faili "/usr/ucb/vi" (UCb on tõenäoliselt California Ülikool, Berkeley).

cron Debianis ja Ubuntus

Debiani ja tuletatud distributsioonide arendajad on välja andnud tugevalt modifitseeritud versioon Vixie cron versioon 3.0pl1. Tabelifailide süntaksis pole erinevusi, kasutajate jaoks on see sama Vixie cron. Suurim uus funktsioon: tugi syslog, SELinux и PAM.

Vähem märgatavad, kuid käegakatsutavad muudatused hõlmavad konfiguratsioonifailide ja ülesannete tabelite asukohta.

Debiani kasutajatabelid asuvad kataloogis /var/spool/cron/crontabs, süsteemitabel on endiselt alles - /etc/crontab. Debiani paketispetsiifilised ülesannete tabelid paigutatakse kausta /etc/cron.d, kust cron deemon neid automaatselt loeb. Kasutajate juurdepääsu kontrolli juhivad failid /etc/cron.allow ja /etc/cron.deny.

Vaikimisi kest on endiselt /bin/sh, mis Debianis on väike POSIX-ühilduv kest kriips, käivitati konfiguratsiooni lugemata (mitteinteraktiivses režiimis).

Cron ise käivitatakse Debiani uusimates versioonides systemd kaudu ja käivituskonfiguratsiooni saab vaadata /lib/systemd/system/cron.service. Teenuse konfiguratsioonis pole midagi erilist, peenemat ülesannete haldamist saab teha keskkonnamuutujate kaudu, mis on deklareeritud otse iga kasutaja crontabis.

cronie RedHatis, Fedoras ja CentOSis

kaaslane - Vixie cron versiooni 4.1 kahvel. Nagu Debianis, pole süntaks muutunud, kuid lisatud on PAM-i ja SELinuxi tugi, klastris töötamine, failide jälgimine inotify abil ja muud võimalused.

Vaikekonfiguratsioon on tavalistes kohtades: süsteemitabel asub kaustas /etc/crontab, paketid panevad oma tabelid kausta /etc/cron.d, kasutajatabelid lähevad kausta /var/spool/cron/crontabs.

Deemon töötab systemd juhtimise all, teenuse konfiguratsioon on /lib/systemd/system/crond.service.

Red Hati sarnastel distributsioonidel kasutatakse käivitamisel vaikimisi /bin/sh, mis on standardne bash. Tuleb märkida, et cron-tööde käivitamisel /bin/sh kaudu käivitub bash-shell POSIX-iga ühilduvas režiimis ega loe täiendavaid seadistusi, töötades mitteinteraktiivses režiimis.

cronie SLES-is ja openSUSE-s

Saksa distributsioon SLES ja selle tuletis openSUSE kasutavad sama sõpra. Siinne deemon käivitatakse ka systemd all, teenuse konfiguratsioon asub /usr/lib/systemd/system/cron.service. Konfiguratsioon: /etc/crontab, /etc/cron.d, /var/spool/cron/tabs. /bin/sh on sama bash, mis töötab POSIX-iga ühilduvas mitteinteraktiivses režiimis.

Vixie cron seade

Kaasaegsed croni järeltulijad pole Vixie croniga võrreldes radikaalselt muutunud, kuid siiski omandanud uusi funktsioone, mida programmi põhimõtete mõistmiseks pole vaja. Paljud neist laiendustest on halvasti kujundatud ja ajavad koodi segadusse. Paul Vixey algset cron lähtekoodi on rõõm lugeda.

Seetõttu otsustasin analüüsida cron seadet mõlema arendusharu ühise cron programmi näitel - Vixie cron 3.0pl1. Lihtsustan näiteid, eemaldades lugemist raskendavad ifdefid ja jättes välja väiksemad üksikasjad.

Deemoni töö võib jagada mitmeks etapiks:

  1. Programmi lähtestamine.
  2. Täidetavate ülesannete loendi kogumine ja värskendamine.
  3. Peamine cron silmus töötab.
  4. Alustage ülesannet.

Vaatame neid järjekorras.

Initsialiseerimine

Käivitamisel installib cron pärast protsessiargumentide kontrollimist signaalitöötlejad SIGCHLD ja SIGHUP. Esimene teeb logikirje alamprotsessi lõpetamise kohta, teine ​​sulgeb logifaili failideskriptori:

signal(SIGCHLD, sigchld_handler);
signal(SIGHUP, sighup_handler);

Cron deemon töötab alati süsteemis üksi, ainult superkasutajana ja croni peakataloogist. Järgmised kõned loovad deemoniprotsessi PID-ga lukufaili, veenduge, et kasutaja on õige, ja muutke praegune kataloog põhikataloogiks:

acquire_daemonlock(0);
set_cron_uid();
set_cron_cwd();

Määratakse vaiketee, mida kasutatakse protsesside käivitamisel:

setenv("PATH", _PATH_DEFPATH, 1);

Seejärel protsess "deemoniseeritakse": see loob protsessist alamkoopia, kutsudes forki ja uue seansi alamprotsessis (kutsates setsid). Vanemprotsessi pole enam vaja ja see väljub:

switch (fork()) {
case -1:
    /* критическая ошибка и завершение работы */
    exit(0);
break;
case 0:
    /* дочерний процесс */
    (void) setsid();
break;
default:
    /* родительский процесс завершает работу */
    _exit(0);
}

Vanemprotsessi lõpetamine vabastab lukustusfaili lukustuse. Lisaks on vajalik failis olev PID uuendamine lapse jaoks. Pärast seda täidetakse ülesannete andmebaas:

/* повторный захват лока */
acquire_daemonlock(0);

/* Заполнение БД  */
database.head = NULL;
database.tail = NULL;
database.mtime = (time_t) 0;
load_database(&database);

Seejärel liigub cron põhitöötsükli juurde. Enne aga tasub pilk peale visata ülesannete nimekirja laadimisele.

Ülesannete nimekirja kogumine ja uuendamine

Funktsioon load_database vastutab ülesannete loendi laadimise eest. See kontrollib põhisüsteemi crontabi ja kasutajafailidega kataloogi. Kui failid ja kataloog pole muutunud, ei loeta ülesannete loendit uuesti. Vastasel juhul hakkab tekkima uus ülesannete loend.

Spetsiaalsete faili- ja tabelinimedega süsteemifaili laadimine:

/* если файл системной таблицы изменился, перечитываем */
if (syscron_stat.st_mtime) {
    process_crontab("root", "*system*",
    SYSCRONTAB, &syscron_stat,
    &new_db, old_db);
}

Kasutajatabelite tsüklina laadimine:

while (NULL != (dp = readdir(dir))) {
    char    fname[MAXNAMLEN+1],
            tabname[MAXNAMLEN+1];
    /* читать файлы с точкой не надо*/
    if (dp->d_name[0] == '.')
            continue;
    (void) strcpy(fname, dp->d_name);
    sprintf(tabname, CRON_TAB(fname));
    process_crontab(fname, fname, tabname,
                    &statbuf, &new_db, old_db);
}

Pärast seda asendatakse vana andmebaas uuega.

Ülaltoodud näidetes kontrollib protsessi_crontab funktsiooni kutse, et tabeli failinimele vastav kasutaja on olemas (välja arvatud juhul, kui see on superkasutaja), ja kutsub seejärel välja load_user. Viimane loeb juba ridade kaupa faili ennast:

while ((status = load_env(envstr, file)) >= OK) {
    switch (status) {
    case ERR:
        free_user(u);
        u = NULL;
        goto done;
    case FALSE:
        e = load_entry(file, NULL, pw, envp);
        if (e) {
            e->next = u->crontab;
            u->crontab = e;
        }
        break;
    case TRUE:
        envp = env_set(envp, envstr);
        break;
    }
}

Siin määratakse kas keskkonnamuutuja (read kujul VAR=value) funktsioonide load_env / env_set abil või loetakse ülesande kirjeldus (* * * * * /path/to/exec) funktsiooni load_entry abil.

Kirje olem, mille load_entry tagastab, on meie ülesanne, mis paigutatakse ülesannete üldloendisse. Funktsioon ise teostab ajavormingu üksikasjalikku sõelumist, kuid meid huvitab rohkem keskkonnamuutujate ja ülesannete käivitamise parameetrite moodustamine:

/* пользователь и группа для запуска задачи берутся из passwd*/
e->uid = pw->pw_uid;
e->gid = pw->pw_gid;

/* шелл по умолчанию (/bin/sh), если пользователь не указал другое */
e->envp = env_copy(envp);
if (!env_get("SHELL", e->envp)) {
    sprintf(envstr, "SHELL=%s", _PATH_BSHELL);
    e->envp = env_set(e->envp, envstr);
}
/* домашняя директория */
if (!env_get("HOME", e->envp)) {
    sprintf(envstr, "HOME=%s", pw->pw_dir);
    e->envp = env_set(e->envp, envstr);
}
/* путь для поиска программ */
if (!env_get("PATH", e->envp)) {
    sprintf(envstr, "PATH=%s", _PATH_DEFPATH);
    e->envp = env_set(e->envp, envstr);
}
/* имя пользовтеля всегда из passwd */
sprintf(envstr, "%s=%s", "LOGNAME", pw->pw_name);
e->envp = env_set(e->envp, envstr);

Põhitsükkel töötab praeguse ülesannete loendiga.

Põhisilmus

Unixi versiooni 7 algne cron töötas üsna lihtsalt: luges konfiguratsiooni tsüklina uuesti, käivitas superkasutajana jooksva minuti ülesanded ja magas järgmise minuti alguseni. See lihtne lähenemine vanematele masinatele nõudis liiga palju ressursse.

SysV-s pakuti välja alternatiivne versioon, milles deemon läks magama kas lähima minutini, mille jaoks ülesanne oli määratletud, või 30 minutiks. Selles režiimis konfiguratsiooni uuesti lugemiseks ja ülesannete kontrollimiseks kulus vähem ressursse, kuid ülesannete loendi kiire värskendamine muutus ebamugavaks.

Vixie cron naasis kord minutis ülesannete loendite kontrollimise juurde, õnneks oli 80ndate lõpuks tavalistes Unixi masinates oluliselt rohkem ressursse:

/* первичная загрузка задач */
load_database(&database);
/* запустить задачи, поставленные к выполнению после перезагрузки системы */
run_reboot_jobs(&database);
/* сделать TargetTime началом ближайшей минуты */
cron_sync();
while (TRUE) {
    /* выполнить задачи, после чего спать до TargetTime с поправкой на время, потраченное на задачи */
    cron_sleep();

    /* перечитать конфигурацию */
    load_database(&database);

    /* собрать задачи для данной минуты */
    cron_tick(&database);

    /* перевести TargetTime на начало следующей минуты */
    TargetTime += 60;
}

Funktsioon cron_sleep on otseselt seotud ülesannete täitmisega, kutsudes välja funktsioone job_runqueue (ülesannete loendamine ja käivitamine) ja do_command (käivita iga üksikut ülesannet). Viimast funktsiooni tasub lähemalt uurida.

Ülesande täitmine

Funktsioon do_command täidetakse heas Unixi stiilis, see tähendab, et see täidab ülesande asünkroonselt. Vanemprotsess jätkab ülesannete käivitamist, alamprotsess valmistab ette ülesandeprotsessi:

switch (fork()) {
case -1:
    /*не смогли выполнить fork */
    break;
case 0:
    /* дочерний процесс: на всякий случай еще раз пробуем захватить главный лок */
    acquire_daemonlock(1);
    /* переходим к формированию процесса задачи */
    child_process(e, u);
    /* по завершению дочерний процесс заканчивает работу */
    _exit(OK_EXIT);
    break;
default:
    /* родительский процесс продолжает работу */
    break;
}

Child_processis on üsna palju loogikat: see võtab standardväljundi ja veavoo enda peale, et need siis meilile saata (kui ülesandetabelis on määratud MAILTO keskkonnamuutuja) ja lõpuks ootab peamist ülesande täitmise protsess.

Ülesande protsessi moodustab teine ​​​​kahvel:

switch (vfork()) {
case -1:
    /* при ошибки сразу завершается работа */
    exit(ERROR_EXIT);
case 0:
    /* процесс-внук формирует новую сессию, терминал и т.д.
     */
    (void) setsid();

    /*
     * дальше многословная настройка вывода процесса, опустим для краткости
     */

    /* смена директории, пользователя и группы пользователя,
     * то есть процесс больше не суперпользовательский
     */
    setgid(e->gid);
    setuid(e->uid);
    chdir(env_get("HOME", e->envp));

    /* запуск самой команды
     */
    {
        /* переменная окружения SHELL указывает на интерпретатор для запуска */
        char    *shell = env_get("SHELL", e->envp);

        /* процесс запускается без передачи окружения родительского процесса,
         * то есть именно так, как описано в таблице задач пользователя  */
        execle(shell, shell, "-c", e->cmd, (char *)0, e->envp);

        /* ошибка — и процесс на запустился? завершение работы */
        perror("execl");
        _exit(ERROR_EXIT);
    }
    break;
default:
    /* сам процесс продолжает работу: ждет завершения работы и вывода */
    break;
}

See on põhimõtteliselt kõik, mis cron on. Jätsin välja mõned huvitavad detailid, näiteks kaugkasutajate arvestus, kuid tõin välja peamise.

järelsõna

Cron on üllatavalt lihtne ja kasulik programm, mis on tehtud Unixi maailma parimate traditsioonide kohaselt. Ta ei tee midagi ekstra, kuid on juba mitukümmend aastat oma tööd suurepäraselt teinud. Ubuntuga kaasas oleva versiooni koodi läbimine ei võtnud rohkem kui tund aega ja mul oli väga lõbus! Loodan, et sain seda teiega jagada.

Ma ei tea, kuidas teil on, aga mul on natuke kurb tõdeda, et kaasaegne programmeerimine, mille kalduvus on liiga keeruline ja abstraktne, pole pikka aega sellist lihtsust soodustanud.

Moodsaid alternatiive cronile on palju: systemd-taimerid võimaldavad organiseerida keerulisi sõltuvustega süsteeme, fcron võimaldab paindlikumalt reguleerida ressursikulu ülesannete kaupa. Aga mulle isiklikult piisas alati kõige lihtsamast crontabist.

Lühidalt, armastage Unixit, kasutage lihtsaid programme ja ärge unustage lugeda oma platvormi manat!

Allikas: www.habr.com

Lisa kommentaar