company_banner

Cron в Linux: история, использование и устройство


    Классик писал, что счастливые часов не наблюдают. В те дикие времена ещё не было ни программистов, ни Unix, но в наши дни программисты знают твёрдо: вместо них за временем проследит cron.


    Утилиты командной строки для меня одновременно слабость и рутина. sed, awk, wc, cut и другие старые программы запускаются скриптами на наших серверах ежедневно. Многие из них оформлены в виде задач для cron, планировщика родом из 70-х.


    Я долго пользовался cron поверхностно, не вникая в детали, но однажды, столкнувшись с ошибкой при запуске скрипта, решил разобраться основательно. Так появилась эта статья, при написании которой я ознакомился с POSIX crontab, основными вариантами cron в популярных дистрибутивах Linux и устройством некоторых из них.


    Используете Linux и запускаете задачи в cron? Вам интересна архитектура системных приложений в Unix? Тогда нам по пути!


    Содержание



    Происхождение видов


    Периодическое выполнение пользовательских или системных программ — очевидная необходимость во всех операционных системах. Поэтому потребность в сервисах, позволяющих централизованно планировать и выполнять задачи, программисты осознали очень давно.


    Unix-подобные операционные системы ведут свою родословную от Version 7 Unix, разработанной в 70-х годах прошлого века в Bell Labs в том числе и знаменитым Кеном Томпсоном (англ. Ken Thompson). Вместе c Version 7 Unix поставлялся и cron, сервис для регулярного выполнения задач суперпользователя.


    Типичный современный cron — несложная программа, но алгоритм работы оригинального варианта был ещё проще: сервис просыпался раз в минуту, читал табличку с задачами из единственного файл (/etc/lib/crontab) и выполнял для суперпользователя те задачи, которые следовало выполнить в текущую минуту.


    Впоследствии усовершенствованные варианты простого и полезного сервиса поставлялись со всеми Unix-подобными операционными системами.


    Обобщённые описания формата crontab и базовых принципов работы утилиты в 1992 году были включены в главный стандарт Unix-подобных операционных систем — POSIX — и таким образом cron из стандарта де-факто стал стандартом де-юре.


    В 1987 году Пол Викси (англ. Paul Vixie), опросив пользователей Unix на предмет пожеланий к cron, выпустил ещё одну версию демона, исправляющую некоторые проблемы традиционных cron и расширяющую синтаксис файлов-таблиц.


    К третьей версии Vixie cron стал отвечать требованиям POSIX, к тому же у программы была либеральная лицензия, вернее не было вообще никакой лицензии, если не считать пожеланий в README: гарантий автор не даёт, имя автора удалять нельзя, а продавать программу можно только вместе с исходным кодом. Эти требования оказались совместимы с принципами набиравшего в те годы популярность свободного ПО, поэтому некоторые ключевые из появившихся в начале 90-х дистрибутивов Linux взяли Vixie cron в качестве системного и развивают его до сих пор.


    В частности, Red Hat и SUSE развивают форк Vixie cron — cronie, а Debian и Ubuntu используют оригинальное издание Vixie cron со множеством патчей.


    Давайте для начала познакомимся с описанной в POSIX пользовательской утилитой crontab, после чего разберём расширения синтаксиса, представленные в Vixie cron, и использование вариаций Vixie cron в популярных дистрибутивах Linux. И, наконец, вишенка на торте — разбор устройства демона cron.


    POSIX crontab


    Если оригинальный cron всегда работал для суперпользователя, то современные планировщики чаще имеют дело с задачами обычных пользователей, что более безопасно и удобно.


    Сron-ы поставляются комплектом из двух программ: постоянно работающего демона cron и доступной пользователям утилиты crontab. Последняя позволяет редактировать таблицы задач, специфичные для каждого пользователя в системе, демон же запускает задачи из пользовательских и системной таблиц.


    В стандарте POSIX никак не описывается поведение демона и формализована только пользовательская программа crontab. Существование механизмов запуска пользовательских задач, конечно, подразумевается, но не описано подробно.


    Вызовом утилиты crontab можно сделать четыре вещи: отредактировать пользовательскую таблицу задач в редакторе, загрузить таблицу из файла, показать текущую таблицу задач и очистить таблицу задач. Примеры работы утилиты crontab:


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

    При вызове crontab -e будет использоваться редактор, указанный в стандартной переменной окружения EDITOR.


    Сами задачи описаны в следующем формате:


    # строки-комментарии игнорируются
    #
    # задача, выполняемая ежеминутно
    * * * * * /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

    Первые пять полей записей: минуты [1..60], часы [0..23], дни месяца [1..31], месяцы [1..12], дни недели [0..6], где 0 — воскресенье. Последнее, шестое, поле — строка, которая будет выполнена стандартным интерпретатором команд.


    В первых пяти полях значения можно перечислять через запятую:


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

    Или через дефис:


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

    Доступ пользователей к планированию задач регулируется в POSIX файлам cron.allow и cron.deny в которых перечисляются, соответственно, пользователи с доступом к crontab и пользователи без доступа к программе. Расположение этих файлов стандарт никак не регламентирует.


    Запускаемым программам, согласно стандарту, должны передаваться по меньшей мере четыре переменные окружения:


    1. HOME — домашняя директория пользователя.
    2. LOGNAME — логин пользователя.
    3. PATH — путь, по которому можно найти стандартные утилиты системы.
    4. SHELL — путь к использованному командному интерпретатору.

    Примечательно, что POSIX ничего не говорит о том, откуда берутся значения для этих переменных.


    Хит продаж — Vixie cron 3.0pl1


    Общий предок популярных вариантов cron — Vixie cron 3.0pl1, представленный в рассылке comp.sources.unix в 1992 году. Основные возможности этой версии мы и рассмотрим подробнее.


    Vixie cron поставляется в двух программах (cron и crontab). Как обычно, демон отвечает за чтение и запуск задач из системной таблицы задач и таблиц задач отдельных пользователей, а утилита crontab — за редактирование пользовательских таблиц.


    Таблица задач и файлы конфигурации


    Таблица задач суперпользователя расположена в /etc/crontab. Синтаксис системной таблицы соответствует синтаксису Vixie cron с поправкой на то, что в ней шестой колонкой указывается имя пользователя, от лица которого запускается задача:


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

    Таблицы задач обычных пользователей располагаются в /var/cron/tabs/username и используют общий синтаксис. При запуске утилиты crontab от имени пользователя редактируются именно эти файлы.


    Управление списками пользователей, имеющих доступ к crontab, происходит в файлах /var/cron/allow и /var/cron/deny, куда достаточно внести имя пользователя отдельной строкой.


    Расширенный синтаксис


    По сравнению с POSIX crontab решение Пола Викси содержит несколько очень полезных модификаций в синтаксисе таблиц задач утилиты.


    Стал доступен новый синтаксис таблиц: например, можно указывать дни недели или месяцы поимённо (Mon, Tue и так далее):


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

    Можно указывать шаг, через который запускаются задачи:


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

    Шаги и интервалы можно смешивать:


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

    Поддерживаются интуитивные альтернативы обычному синтаксису (reboot, yearly, annually, monthly, weekly, daily, midnight, hourly):


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

    Среда выполнения задач


    Vixie cron позволяет менять окружение запускаемых приложений.


    Переменные окружения USER, LOGNAME и HOME не просто предоставляются демоном, а берутся из файла passwd. Переменная PATH получает значение "/usr/bin:/bin", а SHELL — "/bin/sh". Значения всех переменных, кроме LOGNAME, можно изменить в таблицах пользователей.


    Некоторые переменные окружения (прежде всего SHELL и HOME) используются самим cron для запуска задачи. Вот как может выглядеть использование bash вместо стандартного sh для запуска пользовательских задач:


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

    В конечном итоге все определённые в таблице переменные окружения (используемые cron или необходимые процессу) будут переданы запущенной задаче.


    Для редактирования файлов утилитой crontab используется редактор, указанный в переменной окружения VISUAL или EDITOR. Если в среде, где был запущен crontab, эти переменные не определены, то используется "/usr/ucb/vi" (ucb — это, вероятно, University of California, Berkeley).


    cron в Debian и Ubuntu


    Разработчики Debian и производных дистрибутивов выпустили сильно модифицированную версию версию Vixie cron 3.0pl1. Отличий в синтаксисе файлов-таблиц нет, для пользователей это тот же самый Vixie cron. Крупнейшие новые возможности: поддержка syslog, SELinux и PAM.


    Из менее заметных, но осязаемых изменений — расположение конфигурационных файлов и таблиц задач.


    Пользовательские таблицы в Debian располагаются в директории /var/spool/cron/crontabs, системная таблица всё там же — в /etc/crontab. Специфичные для пакетов Debian таблицы задач помещаются в /etc/cron.d, откуда демон cron их автоматически считывает. Управление доступом пользователей регулируется файлами /etc/cron.allow и /etc/cron.deny.


    В качестве командной оболочки по умолчанию по-прежнему используется /bin/sh, в роли которого в Debian выступает небольшой POSIX-совместимый шелл dash, запущенный без чтения какой-либо конфигурации (в неинтерактивном режиме).


    Сам cron в последних версиях Debian запускается через systemd, а конфигурацию запуска можно посмотреть в /lib/systemd/system/cron.service. Ничего особенного в конфигурации сервиса нет, любое более тонкое управление задачами возможно осуществить через переменные окружения, объявленные прямо в crontab каждого из пользователей.


    cronie в RedHat, Fedora и CentOS


    cronie — форк Vixie cron версии 4.1. Как и в Debian, синтаксис не менялся, но добавлена поддержка PAM и SELinux, работы в кластере, слежения за файлами при помощи inotify и других возможностей.


    Конфигурация по умолчанию находится в обычных местах: системная таблица — в /etc/crontab, пакеты помещают свои таблицы в /etc/cron.d, пользовательские таблицы попадают в /var/spool/cron/crontabs.


    Демон запускается под управлением systemd, конфигурация сервиса — /lib/systemd/system/crond.service.


    В Red Hat-подобных дистрибутивах при запуске по умолчанию используется /bin/sh, в роли которого выступает стандартный bash. Надо заметить, что при запуске задач cron через /bin/sh командная оболочка bash запускается в POSIX-совместимом режиме и не читает никакой дополнительной конфигурации, работая в неинтерактивном режиме.


    cronie в SLES и openSUSE


    Немецкий дистрибутив SLES и его дериватив openSUSE используют всё тот же cronie. Демон здесь тоже запускается под systemd, конфигурация сервиса лежит в /usr/lib/systemd/system/cron.service. Конфигурация: /etc/crontab, /etc/cron.d, /var/spool/cron/tabs. В качестве /bin/sh выступает тот же самый bash, запущенный в POSIX-совместимом неинтерактивном режиме.


    Устройство Vixie cron


    Современные потомки cron по сравнению с Vixie cron не изменились радикально, но всё же обзавелись новыми возможностями, не требующимися для понимания принципов работы программы. Многие из этих расширений оформлены неаккуратно и путают код. Оригинальный же исходный код cron в исполнении Пола Викси читать одно удовольствие.


    Поэтому разбор устройства cron я решил провести на примере общей для обеих ветвей развития cron программы — Vixie cron 3.0pl1. Примеры я упрощу, убрав усложняющие чтение ifdef-ы и опустив второстепенные детали.


    Работу демона можно разделить на несколько этапов:


    1. Инициализация программы.
    2. Сбор и обновление списка задач для запуска.
    3. Работа главного цикла cron.
    4. Запуск задачи.

    Разберём их по порядку.


    Инициализация


    При запуске после проверки аргументов процесса cron устанавливает обработчики сигналов SIGCHLD и SIGHUP. Первый вносит в лог запись о завершении работы дочернего процесса, второй — закрывает файловый дескриптор файла-лога:


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

    Демон cron в системе всегда работает один, только в роли суперпользователя и из главной директории cron. Следующие вызовы создают файл-лок с PID-ом процесса-демона, убеждаются, что пользователь правильный и меняют текущую директорию на главную:


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

    Выставляется путь по умолчанию, который будет использоваться при запуске процессов:


    setenv("PATH", _PATH_DEFPATH, 1);

    Дальше процесс «демонизируется»: создаёт дочернюю копию процесса вызовом fork и новую сессию в дочернем процессе (вызов setsid). В родительском процессе больше надобности нет — и он завершает работу:


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

    Завершение родительского процесса высвобождает лок на файле-локе. Кроме того, требуется обновить PID в файле на дочерний. После этого заполняется база задач:


    /* повторный захват лока */
    acquire_daemonlock(0);
    
    /* Заполнение БД  */
    database.head = NULL;
    database.tail = NULL;
    database.mtime = (time_t) 0;
    load_database(&database);

    Дальше cron переходит к главному циклу работы. Но перед этим стоит взглянуть на загрузку списка задач.


    Сбор и обновление списка задач


    За загрузку списка задач отвечает функция load_database. Она проверяет главный системный crontab и директорию с пользовательскими файлами. Если файлы и директория не менялись, то список задач не перечитывается. В противном случае начинает формироваться новый список задач.


    Загрузка системного файла со специальными именами файла и таблицы:


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

    Загрузка пользовательских таблиц в цикле:


    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);
    }
    

    После чего старая база данных подменяется новой.


    В примерах выше вызов функции process_crontab убеждается в существовании пользователя, соответствующего имени файла таблицы (если только это не суперпользователь), после чего вызывает load_user. Последняя уже читает сам файл построчно:


    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;
        }
    }

    Здесь либо выставляется переменная окружения (строки вида VAR=value) функциями load_env / env_set, либо читается описание задачи (* * * * * /path/to/exec) функцией load_entry.


    Сущность entry, которую возвращает load_entry, — это и есть наша задача, помещаемая в общий список задач. В самой функции проводится многословный разбор формата времени, нас же больше интересует формирование переменных окружения и параметров запуска задачи:


    /* пользователь и группа для запуска задачи берутся из 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);

    С актуальным списком задач и работает главный цикл.


    Главный цикл


    Оригинальный cron из Version 7 Unix работал совсем просто: в цикле перечитывал конфигурацию, запускал суперпользователем задачи текущей минуты и спал до начала следующей минуты. Этот простой подход на старых машинах требовал слишком много ресурсов.


    В SysV была предложена альтернативная версия, в которой демон засыпал либо до ближайшей минуты, для которой определена задача, либо на 30 минут. Ресурсов на перечитывание конфигурации и проверку задач в таком режиме потреблялось меньше, но быстро обновлять список задач стало неудобно.


    Vixie cron вернулся к проверке списков задач раз в минуту, благо к концу 80-х ресурсов на стандартных Unix-машинах стало значительно больше:


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

    Непосредственно выполнением задач занимается функция cron_sleep, вызывающая функции job_runqueue (перебор и запуск задач) и do_command (запуск каждой отдельной задачи). Последнюю функцию стоит разобрать подробнее.


    Запуск задачи


    Функция do_command исполнена в хорошем Unix-стиле, то есть для асинхронного выполнения задачи она делает fork. Родительский процесс продолжает запуск задач, дочерний — занимается подготовкой процесса задачи:


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

    В child_process довольно много логики: она принимает стандартные потоки вывода и ошибок на себя, чтобы потом переслать на почту (если в таблице задач указана переменная окружения MAILTO), и, наконец, ждёт завершения работы основного процесса задачи.


    Процесс задачи формируется еще одним fork:


    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;
    }

    Вот, в общем-то, и весь cron. Какие-то интересные детали, например учёт удалённых пользователей, я опустил, но главное изложил.


    Послесловие


    Сron — на удивление простая и полезная программа, выполненная в лучших традициях мира Unix. Она не делает ничего лишнего, но свою работу выполняет замечательно на протяжении уже нескольких десятилетий. Ознакомление с кодом той версии, что поставляется с Ubuntu, заняло не больше часа, а удовольствия я получил массу! Надеюсь, я смог поделиться им с вами.


    Не знаю, как вам, но мне немного грустно осознавать, что современное программирование с его склонностью к переусложнению и переабстрагированию уже давно не располагает к подобной простоте.


    Существует множество современных альтернатив cron: systemd-timers позволяют организовать сложные системы с зависимостями, в fcron можно гибче регулировать потребление ресурсов задачами. Но лично мне всегда хватало простейших crontab.


    Словом, любите Unix, используйте простые программы и не забывайте читать маны для вашей платформы!

    Badoo
    389,26
    Big Dating
    Поделиться публикацией

    Комментарии 65

      +2
      ИМХО Было бы полезно так же упомянуть как unset некоторые переменные окружения для выполняемой программы. Ту же MAILTO, например.
        0

        Насколько видел из кода, переменные нельзя удалить в самом кроне, только перезаписать типа MAILTO=""

        0
        cron был для меня настоящим открытием. Штука, которая просто работает.
          0

          Ну, открытием он для меня был очень давно, но инструмент, безусловно полезный и простой до банальности. Я там уже писал, что очень люблю программы в этой традиции.

            –1
            Многое из того, что сейчас делают на всяких крутых штуках вроде докера, кубернета и так далее командами по 10 человек, раньше за то же самое время делал один на голом линуксе плюс баш плюч крон.
              0

              Давайте просто пример приведу, почему это не так.
              У вас на сервере, работающем в UTC, несколько проектов, два нормальных, а один работает в какой-то локальной таймзоне. И все требуют крона.


              И надо определённую задачу запускать для этого проекта по локальному для него времени.
              Можно пересчитать часы запуска команд автоматически при записи кронтаба, используя отстаток от 24.
              Усложним: запуск задачи в 2 утра в определенной таймзоне в пятницу и вторник. При пересчете придется менять день недели для некоторых таймзон. И это не автоматизируется в адекватное количество трудозатрат, пересчитывать придется вручную.

                –1
                Проще проекты раскидать по виртуалкам, а на них уже настроить нужный часовой пояс.
                  +4

                  Для разработчиков нет никакой другой таймзоны, окромя UTC.

                    0

                    Вы никогда не работали в проектах, у истоков которых не стояли?
                    Разве не было такого, что приходите в компанию, а там во всех проектах Moscow/CET/PDT?


                    При этом все опускают головы и говорят: "Так исторически сложилось" или "Когда я пришел, это дерьмо уже было".
                    И потом приходится строить костыли на костылях, чтобы это поддерживать, потому что отрефакторить такой объем единовременно или по частям хоть с какой-то стабильностью крайне опасно и на подобное просто не дают добро.

                    0

                    Простите, что поздно отвечаю, как-то пропустил.


                    а переменная CRON_TZ тут не помогает?

                      0

                      Проверял — не помогает, точно не помню, что она меняет, но не подошло.
                      К тому же она в Ubuntu, насколько помню, только в 18 появилась и в 16 её не было.

                  +7
                  Здесь будет ветка переписи тех, кто случайно выполнял «crontab -r», промахнувшись мимо клавиши «e» :)
                    +2
                    Из статьи узнал про crontab -r, до этого всегда открывал crontab -e и удалял текст внутри. Судя по ветке, и хорошо, что не пользовал.
                      0
                      бэкап — наше все) Он и спас при промахе.
                        +3
                        Лет 15 назад я искал удаленную подработку. До этого я не имел опыта Linux и работал только на FreeBSD.
                        И вот в первый-же день (скорее даже час) работы я «промахнулся» и узнал, что в Centos у crontab нет «глупых» вопросов а-ля
                        # crontab -r
                        remove crontab for root? 
                        

                        Всю ночь я его восстанавливал по логам. Странно, что не уволили…
                          0
                          Зато ты теперь очень осторожен и ценен!
                          Я вот случайно под рутом точку забыл и написал
                          rm -rf /*
                          а надо было
                           rm -rf ./*
                          Вот это было веселье на всю ночь! В итоге всё окей, много чего узнал полезного)
                        0

                        Ограничение, что крон может запускать задачи не чаще чем раз в секунду заставляло писать свои костыли.
                        Крайне не хватало возможности указать частоту опроса, пускай и с перезапуском самого крона.

                          +1
                          Да, интересный вопрос. Ограничение по-умолчанию — минута, является большим для машины и относительно маленьким для человека. Предлагают скриптиком чаще запускать.
                          Больше похоже на компромис, который сложился исторически, когда машины были ещё не так быстры. Частота в секунду для современных компьютеров была бы приемлемой, наверно.
                            0

                            Смею предположить, что, во-первых, ежесекундные задачи — дело редкое, а во-вторых, в свое время ежесекундный запуск чего-либо было непозволительной роскошью.

                              +1

                              Ежесекундные задачи может и редкость, но вот разнести запуск ежеминутных в пределах минуты или запускать задачи два, три, четыре раза в минуту бывает нужно. То есть посекундная точность не помешала бы.

                                0

                                это решаемая проблема. Какой-нибудь файл-лок можно, например, хватать на запуске задач.


                                Хотя я понимаю, о чем вы. Но мы вот делали именно flock -x в похожей ситуации.

                                  0
                                  Это просто, bash скрипт запускающий нужное количество команд через слип прекрасно работал у нас лет 10
                                +1
                                Если есть задача запускать что-то раз в секунду, то проще и правильнее демон написать.
                                  +1
                                  всё равно не хватает иногда такой штуки */20 для секунд. в остальном крон это прелесть.
                              –2

                              man systemd.timers

                                +2
                                … ну и упомянуть Cron для Windows тоже стоило бы ;), на порядок удобнее встроенного планировщика.
                                  0
                                  А можно ссылку?
                                    +1
                                    nnCron на сайте разработчика
                                    Заходим к разработчику

                                    Для себя использовал версию nnCron Lite — хватает заглаза.
                                    Пользователи уже описывали использование полной версии, не знаю, насколько это описание актуально
                                    Описание на русском
                                      0
                                      Понял. Спасибо.
                                    +1
                                    О, да. nnCron прекрасен
                                  0
                                  Гуру cron-a! Подскажите по следующей ситуации — потребовалось мне как-то хранить логи почтовых треэйсов N-месяцев с граничного Антиспам-сервера. Задача проста до нельзя — раз в сутки-двое копировать файл /var/log/mail.log, аппендить к нему дату и ложить в какую-то папочку. Код crontab-a был приблизительно такой: 0 2 * * * cp /var/log/mail/ /home/user/mail_`%d%m%Y. Так вот в этом случае крон не отрабатывал, логи честно не помню, т.к. скорее всего не ыбло ничего вразумительного в них. Поступил проще — создал скрипт /home/user/copy_log.sh, вставил в него cp /var/log/mail/ /home/user/mail_`%d%m%Y, добавил «o+х» (каюсь, можно было и u+x, то бишь запуск только root-у) на скрипт, а сам скрипт закинул в crontab: 0 2 * * * /home/user/copy_log.sh. Вот тогда всё заработало без косяков. Вопрос — почему крон не работал напрямую с командой cp и требует скриптовую прослойку?
                                    0
                                    Скорее всего проблема в переменных среды, которые при запуске от cron совсем не те что при запуске из консоли. Например не то содержание переменной path и программы/команды просто не находит. Можно попробовать прописать везде абсолютные пути, можно вывести переменные в файлы и посмотреть какие там значения. Не претендую на правильность советов :)
                                      0

                                      Надо бы указать версию вашего дистрибутива и крона. Но вообще использование скриптов-оберток — стандартный подход.


                                      Логов, кстати, у кронов может и не быть, стандартно бывает только отправка на почту, указанную в переменной MAILTO.


                                      Если хочется разобраться подробней, то можно попровать вызывать прямиком шелл с вашей командой, так, как его вызывает сам cron: /bin/sh -c "cp /from/file /to/file". Часто путаница бывает связана с тем, что шелл по умолчанию берется POSIX-совместимый и запущенный в неинтерактивном режиме.

                                        0
                                        Лог можно настроить, но есть ли смысл в нём? Обычно им выполняют скрипты, которые логируют в своём окружении, елси это пёрл, питон или пхп.
                                        Однажды ковырялся в консоли и тут появляется сообщение: у вас есть новый емейл, по такому пути можете найти. «Ничего себе, консоль мне пишет про емейлы» — подумал я и, действительно, нашёл сообщение от крона в емейлах. Очень удобно оказалось.
                                          0

                                          да, в старые добрые MAILTO хорошо работало. Но сейчас несколько потеряло актуальность.

                                          0
                                          Скорее всего в этом и дело. Спасибо, что подсказали. Хорошо что хоть из POSIX-режима можно скриптом вызывать шельные команды. Без этого ценность крона была бы околонулевой. Кстати в виндовом шедулере я встречал аналогичный трабл — длинные команды с кучей аргументов, напрямую прописанные в задании не работают. Как только закидываешь всё задание в батник — вуаля! Всё работает. Имхуется мне, что POSIX и там пробежал.
                                          +1
                                          Не требует. Пишут, что нужно \% или через переменную среды делать. Ссылка
                                          Скрипт удобен тем, что его можно как вручную для теста запустить, так и кроном выполнить.
                                          И в случае crontab -r скрипт останется.
                                            +1

                                            точно, надо ж экранировать проценты, т.к. проценты в кроне означают перенос строки, для передачи в команды нескольких строк :-)


                                            Из стандарта:


                                            A <percent-sign> character in this field shall be translated to a <newline>. Any character preceded by a
                                            <backslash> (including the '%' ) shall cause that character to be treated literally. Only the first line (up to a
                                            '%' or end-of-line) of the command field shall be executed by the command interpreter. The other lines shall > be made available to the command as standard input.
                                            0

                                            вам ниже ответили, что проценты надо экранировать :-) Там же я привел выжимку из стандарта

                                              +3

                                              У logrotate'а есть параметр olddir и описание задачи выглядит так, как будто стоило использовать его для копирования, а сам logrotate запускать cron'ом

                                                +1
                                                Если каждые сутки, то можно вообще ограничиться средствами logrotate с параметром daily
                                              +2
                                              Для винды есть nnCron и nnCron Lite (последний — бесплатен).
                                              Формат crontab такой же + дополнения.
                                              Я использую версию 1.17.119.0 от 2005 года, которая, как заявлено, работает на Win95 — WinXP, но у меня работает и на Win7 и кажется даже на Win10.

                                              Неоднократно пробовал пользоваться родным виндовым планировщиком, но потом опять уходил на nnCron Lite.
                                                0
                                                Ну как есть… Скорее был. В 2005 я его тоже использовал, но он не поддерживается с 2008. И помнится, под Win 7 он уже требовал каких-то телодвижений для нормальной работы.
                                                Так-то крутая была софтина, но она написана на форте и скрипты под нее надо было писать на диалекте форта, а это чудовищно. Обратная польская запись хороша для машин, а не для людей.
                                                +1

                                                Для тех, кто ещё не столкнулся с этой замечательной фичей, процитирую википедию:


                                                Все условия (времени запуска) проверяются по «логическому И», кроме условий «день недели» и «день месяца» — указанные совместно, они обрабатываются по «логическому ИЛИ», то есть «по любому из дней», что отражено в документации (Ubuntu, Debian, FreeBSD). Однако такая логика неочевидна и не позволяет создать условие типа «первый понедельник каждого месяца» или «каждую пятницу в 13 число».

                                                Приходится писать костыли в баше.

                                                  0

                                                  Так можно же непосредственно в кронтабе. Например, настроить правило на 13 число, а в коде команды проставить if [ $(date +%u) = 5 ].

                                                    0

                                                    Или [ "$(date '+\%w')" = "1" ] && cmd.
                                                    Так и делаю. Но это всё же синтаксис баша, а не самого кронтаба.

                                                  0
                                                  Т.е. Cron ежеминутно проверяет таблицы и выполняет команды при совпадении с системным временем/датой. Конечно возможно это что-то необычное, но если необходимо запустить задачу в определенную секунду, миллисекунду?
                                                    0

                                                    Секунды (тем более — миллисекунды) традиционно не делали, т.к. для всех пользователей каждую секунду проверять задачи — дело неблагодарное. Тут Крон не поможет.


                                                    Еще более гранулярно, на миллисекундах, фактически не имеет смысла делать, т.к. вся эта возня с шеллами, форками и экзеками уже занимает сотни миллисекунд, точности не выйдет.


                                                    В таких случаях вам понадобится что-то очень специальное.

                                                      0
                                                      Дуп
                                                        +2
                                                        Главная фишка систем, созданных на базе крона, это высокая надежность, достигнутая малой кровью. Т.е. например вы можете сделать скрипт, который падает каждый второй раз, запускать его из крона и все будет работать, лишь бы скрипт не портил данные.
                                                        Тогда для себя я решил, насчет секунд, что раз cron имеет точность в минуту, значит так и надо и этот гвоздь торчит из стула не просто так. Поэтому ИМХО, если нужна точность лучше, чем минуты, нужно делать это в своей программе, а из крона только проверять, не упала ли эта программа и перезапускать в случае падения.
                                                          0
                                                          А еще проще использовать тот же supervisord или systemctl, которые:
                                                          1) включаются вместе с ОС и имеют возможность запуска скрипта после запуска зависимостей (т.е., когда включилась база данных или обработчик очередей)
                                                          2) функция контроля (перезагружать ли после ошибки или штатного завершения работы, сколько попыток сделать, с каким диапазоном сделать перезапуск)
                                                          3) все это логгируется и вытаскивается через тот же journalctl
                                                          А сам скрипт исполняет работу и спит от 5 до 30 секунд. Чтобы не текла память, можно также завершать скрипт после получаса-часа работы, тогда systemctl автоматически включит задачу повторно.
                                                          Поэтому cron в этом смысле выглядит довольно странным и не универсальным решением.
                                                            +1
                                                            > Поэтому cron в этом смысле выглядит довольно странным и не универсальным решением.
                                                            «Каждая программа в своем развитии доходит до необходимости отправлять электронную почту» © Не помню кто.
                                                            В смысле каждая программа тащит в себя все фичи, до которых только может дотянуться.
                                                            Крон не странная программа, это очень простая штука, для 95% задач его хватает и в этом его сила. В простоте.
                                                            Универсальные решения это не UNIX way.
                                                              0
                                                              Для многих задач — да, я его активно использую для автозапуска многих задач.
                                                              Но для наблюдателя над ходом исполнения программы — возможно, это не самое эффективное решение, хотя и довольно простое. Конечно, все зависит от программы, которая запускается кроном, но supervisord или systemctl в этом плане более заточенные.
                                                                +1
                                                                ИМХО у них разное назначение.
                                                                cron — запуск скриптов, которые выполняют задачу и выходят.
                                                                supervisord, systemctl — запуск демонов.
                                                          +3
                                                          > но если необходимо запустить задачу в определенную секунду, миллисекунду?
                                                          Если нужно запустить программу в нужную миллисекунду, то тут уже нехило пахнет специальной операционной системой реального времени да еще и с очерь хорошей синхронизацией времени.
                                                          +3
                                                          Шаги и интервалы можно смешивать:

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

                                                          Я бы не использовал в описании интервально-шаговых событий слово «каждую». Отнюдь, не придираюсь, просто зачастую формулировку «каждую вторую» воспринимают как «каждую чётную».
                                                          С учётом данного примера с 0-й левой границей интервала это справедливо, но может вызывать непонимание конструкций вида
                                                          1-11/2 * * * * /path/to/exec

                                                          Разумеется и здесь можно говорить «каждую вторую начиная с 1», но сам стараюсь использовать формулировки «через две минуты», «с шагом в две минуты».
                                                            0

                                                            согласен, сам раньше путал. Поправлю.

                                                            0

                                                            Что раздражает у crontab, так это отсутствие (?) подтверждения при удалении через crontab -r. Особенно с учётом того, что клавиши "e" и "r" расположены рядом на клавиатуре, поэтому набирая "crontab -e" нужно быть КРАЙНЕ внимательным.

                                                              0
                                                              Не знаю, как вам, но мне немного грустно осознавать, что современное программирование с его склонностью к переусложнению и переабстрагированию уже давно не располагает к подобной простоте.

                                                              Тем временем в Gentoo как раз выпиливают vixie-cron как неподдерживаемый. В качестве одной из альтернатив как раз предлагают cronie.


                                                              Эх, а я помню, как в 2004-м первый раз игрался с гентой, и emerge vixie-cron был одним из шагов в хендбуке. Уходят эпохи.

                                                                0

                                                                Да судя по коду cronie не очень радикально изменился :-) цо… викси жив!

                                                                +1
                                                                Коллега. А где же anacron? Где команды at?
                                                                  0

                                                                  Это отдельные программы с отдельной историей :-) Хотя в redhat вроде как anacron вместе с cron поставляется...

                                                                    –1
                                                                    Это все программы планирования заданий/выполнение заданий по времени.
                                                                  0

                                                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                                  Самое читаемое