Jadwalkan hari terakhir setiap bulan

10

Saya membaca dari sebuah instruksi untuk menjadwalkan sebuah skrip pada hari terakhir bulan itu:

Catatan:
Pembaca yang cerdik mungkin bertanya-tanya bagaimana Anda dapat mengatur perintah untuk dieksekusi pada hari terakhir setiap bulan karena Anda tidak dapat menetapkan nilai dayofmonth untuk mencakup setiap bulan. Masalah ini telah menjangkiti programmer Linux dan Unix, dan telah melahirkan beberapa solusi yang berbeda. Metode yang umum adalah menambahkan pernyataan jika-maka yang menggunakan perintah tanggal untuk memeriksa apakah tanggal besok adalah 01:

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

Ini memeriksa setiap hari pada jam 12 siang untuk melihat apakah itu adalah hari terakhir bulan itu, dan jika demikian, cron menjalankan perintah.

masukkan deskripsi gambar di sini

Bagaimana cara [`date +%d -d tomorrow` = 01 ]kerjanya?
Apakah benar menyatakan then; command1?

Kalkulus
sumber
Apakah Anda yakin itu kata demi kata? Seperti yang tertulis di sini sebenarnya tidak bekerja.
Michael Homer
Saya memposting snapshot. @ MichaelHomer
Calculus
Terima kasih! Saya telah mengutak-atik pemformatan untuk membuatnya cocok - itu masih belum benar, tetapi itulah yang dikatakan gambar.
Michael Homer
1
Hilang ; endif?
danblack
Itu tidak bekerja. Ini mengandung kesalahan sintaks: Tidak ada spasi setelah [dan tidak ada fidi akhir. Juga, %khusus untuk crontab.
Kusalananda

Jawaban:

17

Abstrak

Kode yang benar adalah:

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

Panggil skrip ini end_of_month.shdan panggilan dalam cron sederhana:

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

Itu akan menjalankan skrip end_of_month(yang secara internal akan memeriksa bahwa hari itu adalah hari terakhir dalam sebulan) hanya pada hari ke 28, 29, 30 dan 31. Tidak perlu memeriksa akhir bulan pada hari lain.

Posting lama.

Itu adalah kutipan dari buku "Linux Command Line dan Shell Scripting Bible" oleh Richard Blum, Christine Bresnahan hlm 442, Edisi Ketiga, John Wiley & Sons © 2015.

Ya, begitulah katanya, tapi itu salah / tidak lengkap:

  • Tidak ada penutup fi.
  • Membutuhkan ruang antara [dan berikut ini `.
  • Hal ini sangat dianjurkan untuk menggunakan $ (...) bukan `…`.
  • Penting bahwa Anda menggunakan tanda kutip di sekitar ekspansi seperti"$(…)"
  • Ada tambahan ;sesudahnyathen

Bagaimana aku tahu? (yah, berdasarkan pengalaman ☺) tetapi Anda dapat mencoba Shellcheck . Tempel kode dari buku (setelah tanda bintang) dan itu akan menunjukkan kepada Anda kesalahan yang tercantum di atas ditambah "hilang shebang". Script tanpa kesalahan di Shellcheck adalah ini:

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

Situs itu berfungsi karena apa yang ditulis adalah "kode shell". Itu adalah sintaks yang bekerja di banyak shell.

Beberapa masalah yang tidak disebutkan shellcheck adalah:

  • Diasumsikan bahwa perintah tanggal adalah versi tanggal GNU. Yang dengan -dopsi yang menerima tomorrowsebagai nilai (busybox memiliki opsi -d tetapi tidak mengerti besok dan BSD memiliki -dpilihan tetapi tidak terkait dengan "tampilan" waktu).

  • Lebih baik mengatur format setelah semua opsi date -d tomorrow +'%d'.

  • Waktu mulai cron selalu dalam waktu lokal, yang dapat membuat satu pekerjaan dimulai 1 jam lebih awal dari hari yang tepat dihitung jika DST (waktu musim panas) ditetapkan atau tidak disetel.

Apa yang kami lakukan adalah skrip shell yang bisa disebut dengan cron. Kami selanjutnya dapat memodifikasi skrip untuk menerima argumen dari program atau perintah untuk dieksekusi, seperti ini (akhirnya, kode yang benar):

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

Panggil skrip ini end_of_month.shdan panggilan dalam cron sederhana:

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

Itu akan menjalankan skrip end_of_month(yang secara internal akan memeriksa bahwa hari itu adalah hari terakhir dalam sebulan) hanya pada hari ke 28, 29, 30 dan 31. Tidak perlu memeriksa akhir bulan pada hari lain.

Pastikan jalur yang benar disertakan. PATH di dalam cron tidak akan (tidak mungkin) sama dengan PATH pengguna.

Perhatikan bahwa ada satu skrip akhir bulan yang diuji (seperti ditunjukkan di bawah) yang dapat memanggil banyak utilitas atau skrip lain.

Ini juga akan menghindari masalah tambahan yang dihasilkan cron dengan baris perintah penuh:

  • Cron membagi baris perintah pada setiap %bahkan jika dikutip dengan 'atau "(hanya \berfungsi di sini). Itu adalah cara umum di mana pekerjaan cron gagal.

Anda dapat menguji apakah end_of_month.shskrip bekerja dengan benar pada tanggal tertentu (tanpa menunggu hingga akhir bulan untuk menemukan itu tidak berfungsi) dengan mengujinya dengan faketime:

$ faketime 2018/10/31 ./end_of_month echo "Command will be executed...."
Command will be executed....
Ishak
sumber
ast-open date(atau datebawaan dari ksh93 jika ksh93 dibangun sebagai bagian dari ast-open) mendukung date -d tomorrow +%satau date +%s tomorrow).
Stéphane Chazelas
2
Pengalaman telah membuktikan (berkali-kali) bahwa jauh lebih baik untuk menguji skrip berjalan dengan benar dengan faketime daripada menunggu sampai akhir bulan untuk menemukan bahwa pekerjaan yang dijadwalkan tidak berhasil. Seperti menemukan bahwa * * * * * echo "$(date -u +'date %c')" >>~/testfiletidak akan berfungsi karena orang lupa mengutip \%(yang menjadi sulit untuk di-debug jika hanya satu percobaan setiap bulan yang memungkinkan). @ Kusalananda
Isaac
8

Dengan asumsi bahwa kesalahan sintaks sudah diperbaiki, dan perintah dirumuskan ulang sedikit menjadi kurang verbose:

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

Ini berjalan date +%d -d tomorrow(dengan asumsi bahwa itu GNU dateyang digunakan) untuk mendapatkan tanggal besok sebagai angka dua digit. Jika angkanya tidak 01, maka hari ini bukan hari terakhir dalam sebulan. Dalam hal ini, tes berhasil dan command1yang tidak dieksekusi. Pekerjaan dijalankan pada siang hari pada hari-hari yang mungkin merupakan hari terakhir bulan itu.

Perintah asli:

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

Ini memiliki beberapa masalah:

  • Tidak ada ruang setelah [.
  • A ;langsung setelah itu then.
  • %khusus dalam spesifikasi pekerjaan cron dan harus diloloskan sebagai \%(lihat man 5 crontab).
  • Tidak ada final fidi akhir yang cocok dengan if.
Kusalananda
sumber
2
Masalah dengan menggunakan [ ... ] && command1alih-alih if...adalah bahwa pada hari-hari yang bukan hari terakhir bulan itu, pekerjaan cron akan berakhir dengan status keluar yang tidak nol dan bahwa kegagalan mungkin harus dilaporkan. Menggunakan [ "$(...)" != 01 ] || command1adalah cara lain untuk menghindari masalah.
Stéphane Chazelas
1
Masalah lain dengan kode asli adalah ;antara thendan command1.
Stéphane Chazelas
@ StéphaneChazelas Alasan lain mengapa saya tidak suka one-liners, sulit dibaca.
Kusalananda
Perbedaan waktu antara berturut-turut menjalankan perintah mungkin bukan jumlah integer (24 jam) hari karena perubahan DST akan menggeser waktu mulai cron.
Isaac
3
@ kucing Tidak peduli bagaimana Anda mengutip, baik "atau '(kecuali \), tanda persen %akan membuat cron memutus garis menjadi dua bagian. Itu adalah satu cara yang biasa untuk membuat cron gagal.
Isaac
-1

Untuk menjadwalkan hari terakhir setiap bulan, Anda dapat mencoba: 0 0 15,L * *.

Kylin Sky
sumber