Eu tenho esse script bash:
array=( '2015-01-01', '2015-01-02' )
for i in "${array[@]}"
do
python /home/user/executeJobs.py {i} &> /home/user/${i}.log
done
Agora, quero percorrer um intervalo de datas, por exemplo, 01-01-2015 até 31/01/2015.
Como conseguir no Bash?
Atualização :
Agradável: nenhum trabalho deve ser iniciado antes da conclusão de uma execução anterior. Nesse caso, quando executeJobs.py for concluído, o prompt do bash $
retornará.
por exemplo, eu poderia incorporar wait%1
no meu loop?
datetime
módulo Python.wait
(como em bugs acontecendo devido a processos simultâneos quando você não), então você tem algo mais interessante / mais complicado acontecendo, que precisa de uma solução mais complicada ( como pedir ao subprocesso para herdar um arquivo de bloqueio), o que é suficientemente complexo e não relacionado com a aritmética de data para que seja uma questão separada.Respostas:
Usando a data GNU:
d=2015-01-01 while [ "$d" != 2015-02-20 ]; do echo $d d=$(date -I -d "$d + 1 day") # mac option for d decl (the +1d is equivalent to + 1 day) # d=$(date -j -v +1d -f "%Y-%m-%d" "2020-12-12" +%Y-%m-%d) done
Observe que, por usar comparação de strings, é necessária a notação ISO 8601 completa das datas das bordas (não remova os zeros à esquerda). Para verificar se há dados de entrada válidos e forçá-los a um formato válido, se possível, você também pode usar
date
:# slightly malformed input data input_start=2015-1-1 input_end=2015-2-23 # After this, startdate and enddate will be valid ISO 8601 dates, # or the script will have aborted when it encountered unparseable data # such as input_end=abcd startdate=$(date -I -d "$input_start") || exit -1 enddate=$(date -I -d "$input_end") || exit -1 d="$startdate" while [ "$d" != "$enddate" ]; do echo $d d=$(date -I -d "$d + 1 day") done
Uma adição final : para verificar se
$startdate
é antes$enddate
, se você espera apenas datas entre os anos 1000 e 9999, pode simplesmente usar a comparação de strings como esta:while [[ "$d" < "$enddate" ]]; do
Para estar no lado muito seguro além do ano 10.000, quando a comparação lexicográfica falhar, use
while [ "$(date -d "$d" +%Y%m%d)" -lt "$(date -d "$enddate" +%Y%m%d)" ]; do
A expressão é
$(date -d "$d" +%Y%m%d)
convertida$d
para a forma numérica, ou seja,2015-02-23
torna-se20150223
, e a ideia é que as datas nessa forma possam ser comparadas numericamente.fonte
%1
é uma construção de controle de trabalho, e o controle de trabalho é desativado em scripts não interativos, a menos que você o ligue explicitamente. A maneira adequada de se referir a subprocessos individuais dentro de um script é por PID e, mesmo assim, esperar que os processos sejam concluídos é automático, a menos que estejam explicitamente em segundo plano por seu código (como com um&
), ou se auto-separem (nesse casowait
nem funcionará, e o PID fornecido ao shell será invalidado pelo processo de bifurcação dupla usado para o fundo próprio).Expansão da cinta :
for i in 2015-01-{01..31} …
Mais:
for i in 2015-02-{01..28} 2015-{04,06,09,11}-{01..30} 2015-{01,03,05,07,08,10,12}-{01..31} …
Prova:
$ echo 2015-02-{01..28} 2015-{04,06,09,11}-{01..30} 2015-{01,03,05,07,08,10,12}-{01..31} | wc -w 365
Compacto / aninhado:
$ echo 2015-{02-{01..28},{04,06,09,11}-{01..30},{01,03,05,07,08,10,12}-{01..31}} | wc -w 365
Solicitado, se for importante:
$ x=( $(printf '%s\n' 2015-{02-{01..28},{04,06,09,11}-{01..30},{01,03,05,07,08,10,12}-{01..31}} | sort) ) $ echo "${#x[@]}" 365
Como não está ordenado, você pode simplesmente adicionar anos bissextos a:
$ echo {2015..2030}-{02-{01..28},{04,06,09,11}-{01..30},{01,03,05,07,08,10,12}-{01..31}} {2016..2028..4}-02-29 | wc -w 5844
fonte
python /home/user/executeJobs.py 2015-01-{01..31} &> /home/user/2015-01-{01..31}.log
?executeJobs.py
.start='2019-01-01' end='2019-02-01' start=$(date -d $start +%Y%m%d) end=$(date -d $end +%Y%m%d) while [[ $start -le $end ]] do echo $start start=$(date -d"$start + 1 day" +"%Y%m%d") done
fonte
Eu tive o mesmo problema e tentei algumas das respostas acima, talvez estejam ok, mas nenhuma dessas respostas corrigiu o que eu estava tentando fazer, usando o macOS.
Eu estava tentando iterar datas no passado, e o seguinte funcionou para mim:
#!/bin/bash # Get the machine date newDate=$(date '+%m-%d-%y') # Set a counter variable counter=1 # Increase the counter to get back in time while [ "$newDate" != 06-01-18 ]; do echo $newDate newDate=$(date -v -${counter}d '+%m-%d-%y') counter=$((counter + 1)) done
Espero que ajude.
fonte
gdate
vez dodate
macOS.Se alguém quiser fazer um loop da data de entrada para qualquer intervalo abaixo pode ser usado, ele também imprimirá a saída no formato de aaaaMMdd ...
#!/bin/bash in=2018-01-15 while [ "$in" != 2018-01-25 ]; do in=$(date -I -d "$in + 1 day") x=$(date -d "$in" +%Y%m%d) echo $x done
fonte
Eu precisava fazer um loop através de datas no AIX, BSDs, Linux, OS X e Solaris. O
date
comando é um dos comandos menos portáteis e mais miseráveis para usar em todas as plataformas que encontrei. Achei mais fácil escrever ummy_date
comando que funcionasse em qualquer lugar.O programa C abaixo pega uma data de início e adiciona ou subtrai dias dela. Se nenhuma data for fornecida, ele adiciona ou subtrai dias da data atual.
O
my_date
comando permite que você execute o seguinte em qualquer lugar:start="2015-01-01" stop="2015-01-31" echo "Iterating dates from ${start} to ${stop}." while [[ "${start}" != "${stop}" ]] do python /home/user/executeJobs.py {i} &> "/home/user/${start}.log" start=$(my_date -s "${start}" -n +1) done
E o código C:
#include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <time.h> int show_help(); int main(int argc, char* argv[]) { int eol = 0, help = 0, n_days = 0; int ret = EXIT_FAILURE; time_t startDate = time(NULL); const time_t ONE_DAY = 24 * 60 * 60; for (int i=0; i<argc; i++) { if (strcmp(argv[i], "-l") == 0) { eol = 1; } else if (strcmp(argv[i], "-n") == 0) { if (++i == argc) { show_help(); ret = EXIT_FAILURE; goto finish; } n_days = strtoll(argv[i], NULL, 0); } else if (strcmp(argv[i], "-s") == 0) { if (++i == argc) { show_help(); ret = EXIT_FAILURE; goto finish; } struct tm dateTime; memset (&dateTime, 0x00, sizeof(dateTime)); const char* start = argv[i]; const char* end = strptime (start, "%Y-%m-%d", &dateTime); /* Ensure all characters are consumed */ if (end - start != 10) { show_help(); ret = EXIT_FAILURE; goto finish; } startDate = mktime (&dateTime); } } if (help == 1) { show_help(); ret = EXIT_SUCCESS; goto finish; } char buff[32]; const time_t next = startDate + ONE_DAY * n_days; strftime(buff, sizeof(buff), "%Y-%m-%d", localtime(&next)); /* Paydirt */ if (eol) fprintf(stdout, "%s\n", buff); else fprintf(stdout, "%s", buff); ret = EXIT_SUCCESS; finish: return ret; } int show_help() { fprintf(stderr, "Usage:\n"); fprintf(stderr, " my_date [-s date] [-n [+|-]days] [-l]\n"); fprintf(stderr, " -s date: optional, starting date in YYYY-MM-DD format\n"); fprintf(stderr, " -n days: optional, number of days to add or subtract\n"); fprintf(stderr, " -l: optional, add new-line to output\n"); fprintf(stderr, "\n"); fprintf(stderr, " If no options are supplied, then today is printed.\n"); fprintf(stderr, "\n"); return 0; }
fonte
O Bash é melhor escrito utilizando tubos (|). Isso deve resultar em processamento eficiente da memória e simultâneo (mais rápido). Eu escreveria o seguinte:
seq 0 100 | xargs printf "20 Aug 2020 - %sdays\n" \ | xargs -d '\n' -l date -d
O seguinte imprimirá a data
20 aug 2020
e imprimirá as datas dos 100 dias anteriores.Este oneliner pode ser transformado em um utilitário.
#!/usr/bin/env bash # date-range template <template> template="${1:--%sdays}" export LANG; xargs printf "$template\n" | xargs -d '\n' -l date -d
Por padrão, optamos por iterar no último dia 1 por vez.
Digamos que desejamos gerar datas até uma determinada data. Não sabemos ainda quantas iterações precisamos para chegar lá. Digamos que Tom nasceu em 1 de janeiro de 2001. Queremos gerar cada data até uma determinada. Podemos conseguir isso usando sed.
seq 0 $((2**63-1)) | date-range | sed '/.. Jan 2001 /q'
Assim que o sed sair, ele também sairá do utilitário de intervalo de datas.
Também é possível iterar usando um intervalo de 3 meses:
$ seq 0 3 12 | date-range '+%smonths' Tue Mar 3 18:17:17 CET 2020 Wed Jun 3 19:17:17 CEST 2020 Thu Sep 3 19:17:17 CEST 2020 Thu Dec 3 18:17:17 CET 2020 Wed Mar 3 18:17:17 CET 2021
fonte
Se você está preso à data do busybox , descobri que trabalhar com timestamps é a abordagem mais confiável:
STARTDATE="2019-12-30" ENDDATE="2020-01-04" start=$(date -d $STARTDATE +%s) end=$(date -d $ENDDATE +%s) d="$start" while [[ $d -le $end ]] do date -d @$d +%Y-%m-%d d=$(( $d + 86400 )) done
Isso resultará em:
O carimbo de data / hora Unix não inclui segundos bissextos, então 1 dia é sempre igual a exatamente 86400 segundos.
fonte