Zaplanuj ostatni dzień każdego miesiąca

10

Czytam z instrukcji, jak zaplanować skrypt na ostatni dzień miesiąca:

Uwaga:
bystry czytelnik może zastanawiać się, w jaki sposób można ustawić polecenie wykonywania w ostatnim dniu każdego miesiąca, ponieważ nie można ustawić wartości dayofmonth na pokrycie każdego miesiąca. Ten problem nękał programistów Linuksa i Uniksa i stworzył całkiem sporo różnych rozwiązań. Popularną metodą jest dodanie instrukcji if-then, która używa polecenia date, aby sprawdzić, czy jutrzejsza data to 01:

00 12 * * * if [`date +%d -d tomorrow` = 01 ] ; then ; command1

Sprawdza to codziennie o godzinie 12, aby sprawdzić, czy jest to ostatni dzień miesiąca, a jeśli tak, cron uruchamia polecenie.

wprowadź opis zdjęcia tutaj

Jak [`date +%d -d tomorrow` = 01 ]działa
Czy poprawne jest stwierdzenie then; command1?

Rachunek różniczkowy
źródło
Czy jesteś pewien, że to dosłownie tak mówi? Jak tu napisano, w rzeczywistości nie działa.
Michael Homer
Zamieściłem migawkę. @ MichaelHomer
Calculus
Dzięki! Zmanipulowałem formatowanie, aby dopasować - nadal nie jest w porządku, ale tak mówi zdjęcie.
Michael Homer
1
Brakuje ; endif?
danblack
To nie działa Zawiera błędy składniowe: brak spacji po [i brak fina końcu. Ponadto %jest wyjątkowy w crontabs.
Kusalananda

Odpowiedzi:

17

Abstrakcyjny

Prawidłowy kod powinien być:

#!/bin/sh
[ "$#" -eq 0 ] && echo "Usage: $0 command [args]" && exit 1
[ "$(date -d tomorrow +'%d')" = 01 ] || exit 0
exec "$@"

Wywołaj ten skrypt, end_of_month.sha wywołanie w cron jest po prostu:

00 12 28-31 * * /path/to/script/end_of_month.sh command

Spowodowałoby to uruchomienie skryptu end_of_month(który wewnętrznie sprawdzi, czy dzień jest ostatnim dniem miesiąca) tylko w dniach 28, 29, 30 i 31. Nie ma potrzeby sprawdzania końca miesiąca w żadnym innym dniu.

Stary post

To cytat z książki „Linux Command Line and Shell Scripting Bible” Richarda Bluma, Christine Bresnahan str. 442, wydanie trzecie, John Wiley & Sons © 2015.

Tak, tak jest napisane, ale to źle / niekompletnie:

  • Brakuje zamknięcia fi.
  • Potrzebuje miejsca między [i poniżej `.
  • Jest zdecydowanie zaleca się użycie $ (...) zamiast `…`.
  • Ważne jest, aby używać cudzysłowów wokół rozszerzeń takich jak"$(…)"
  • Jest dodatkowy ;pothen

Skąd mam wiedzieć? (cóż, z doświadczenia ☺), ale możesz wypróbować Shellcheck . Wklej kod z książki (po gwiazdkach), a zobaczysz błędy wymienione powyżej oraz „brakujący shebang”. Skrypt bez błędów w Shellcheck jest następujący:

#!/bin/sh if [ "$(date +%d -d tomorrow)" = 01 ] ; then script.sh; fi

Ta strona działa, ponieważ napisano „kod powłoki”. To jest składnia, która działa w wielu powłokach.

Niektóre problemy, o których Shellcheck nie wspomina, to:

  • Zakłada się, że polecenie daty jest wersją daty GNU. Ten z -dopcją, która przyjmuje tomorrowjako wartość (busybox ma opcję -d, ale nie rozumie jutra, a BSD ma -dopcję, ale nie jest związana z „wyświetlaniem” czasu).

  • Lepiej ustawić format po wszystkich opcjach date -d tomorrow +'%d'.

  • Czas rozpoczęcia crona jest zawsze podany w czasie lokalnym, co może spowodować rozpoczęcie pracy o 1 godzinę wcześniej niż dokładna liczba dni, jeśli DST (czas letni) został ustawiony lub rozbrojony.

To, co zrobiliśmy, to skrypt powłoki, który można wywołać za pomocą crona. Możemy dalej modyfikować skrypt, aby akceptował argumenty programu lub polecenia do wykonania, w ten sposób (w końcu poprawny kod):

#!/bin/sh
[ "$#" -eq 0 ] && echo "Usage: $0 command [args]" && exit 1
[ "$(date -d tomorrow +'%d')" = 01 ] || exit 0
exec "$@"

Wywołaj ten skrypt, end_of_month.sha wywołanie w cron jest po prostu:

00 12 28-31 * * /path/to/script/end_of_month.sh command

Spowodowałoby to uruchomienie skryptu end_of_month(który wewnętrznie sprawdzi, czy dzień jest ostatnim dniem miesiąca) tylko w dniach 28, 29, 30 i 31. Nie ma potrzeby sprawdzania końca miesiąca w żadnym innym dniu.

Upewnij się, że podana jest poprawna ścieżka. ŚCIEŻKA wewnątrz crona nie będzie (mało prawdopodobna) taka sama jak ŚCIEŻKA użytkownika.

Należy pamiętać, że istnieje jeden koniec miesiąca skryptu badanego (jak pokazano poniżej), które mogą wywołać wiele innych narzędzi i skryptów.

Pozwoli to również uniknąć dodatkowego problemu generowanego przez cron z pełnym wierszem poleceń:

  • Cron dzieli linię poleceń na dowolną, %nawet jeśli jest cytowana za pomocą 'lub "( \działa tylko tutaj). Jest to powszechny sposób na niepowodzenie zadań CRON.

Możesz sprawdzić, czy end_of_month.shskrypt działa poprawnie w określonym dniu (nie czekając do końca miesiąca, aby odkryć, że nie działa), testując go z faketime:

$ faketime 2018/10/31 ./end_of_month echo "Command will be executed...."
Command will be executed....
Izaak
źródło
ast-open date(lub datewbudowany ksh93, jeśli ksh93 został zbudowany jako część ast-open) obsługuje date -d tomorrow +%slub date +%s tomorrow).
Stéphane Chazelas
2
Doświadczenie udowodniło (wiele razy), że o wiele lepiej jest przetestować skrypt działający poprawnie z faketime, niż czekać do końca miesiąca, aby stwierdzić, że zaplanowane zadanie nie zadziałało. Jak odkrywanie, że to * * * * * echo "$(date -u +'date %c')" >>~/testfilenie zadziała, ponieważ zapomniałeś podać cytat \%(co staje się trudne do debugowania, jeśli możliwa jest tylko jedna próba każdego miesiąca). @Kusalananda
Izaak
8

Zakładając, że błędy składniowe zostały naprawione, a polecenie nieznacznie przeformułowało, aby było mniej szczegółowe:

00 12 28-31 * * [ "$( date -d tomorrow +\%d )" != "01" ] || command1

Działa to date +%d -d tomorrow(zakładając, dateże używany jest GNU ), aby uzyskać jutrzejszą datę jako dwucyfrową liczbę. Jeśli liczba nie jest 01, to dzisiaj nie jest ostatni dzień miesiąca. W takim przypadku testy zakończą się powodzeniem i niecommand1 zostaną wykonane. Zadanie jest uruchamiane w południe w dni, które mogą być ostatnim dniem miesiąca.

Oryginalne polecenie:

00 12 * * * if [`date +%d -d tomorrow` = 01 ] ; then ; command1

Ma to kilka problemów:

  • Brak miejsca po [.
  • ;Bezpośrednio po then.
  • %jest specjalny w specyfikacjach zadań crona i musi być oznaczony jako \%(patrz man 5 crontab).
  • Na fikońcu nie ma finału, który pasowałby do if.
Kusalananda
źródło
2
Problem z używaniem [ ... ] && command1zamiast if...polega na tym, że w dni, które nie są ostatnim dniem miesiąca, zadanie crona zakończy się niezerowym statusem wyjścia i może być konieczne zgłoszenie awarii . Używanie [ "$(...)" != 01 ] || command1to kolejny sposób na uniknięcie problemu.
Stéphane Chazelas
1
Innym problemem związanym z oryginalnym kodem jest ;między theni command1.
Stéphane Chazelas
@ StéphaneChazelas Kolejny powód, dla którego nie lubię jednoliniowych, są trudne do odczytania.
Kusalananda
Różnica czasu między kolejnymi uruchomieniami polecenia może nie być liczbą całkowitą wynoszącą (24 godziny) dni, ponieważ zmiana czasu letniego zmieni czas rozpoczęcia crona.
Izaak
3
@cat To nie ma znaczenia, w jaki sposób cytować, albo "albo '(z wyjątkiem \), znak procent %uczyni cron przełamać linię na dwie części. To jeden ze zwykłych sposobów na niepowodzenie crona.
Izaak
-1

Aby zaplanować ostatni dzień każdego miesiąca, można spróbować: 0 0 15,L * *.

Kylin Sky
źródło