Як прокрутити дати за допомогою Bash?


85

У мене такий bash-скрипт:

array=( '2015-01-01', '2015-01-02' )

for i in "${array[@]}"
do
    python /home/user/executeJobs.py {i} &> /home/user/${i}.log
done

Тепер я хочу прокрутити діапазон дат, наприклад, з 01.01.2015 по 31.01.2015.

Як досягти в Bash?

Оновлення :

Приємно мати: жодна робота не повинна починатися до завершення попереднього циклу. У цьому випадку, коли executeJobs.py завершено, запит bash $повернеться.

наприклад, чи можу я включити wait%1у свій цикл?


Ви знаходитесь на платформі з датою GNU?
Чарльз Даффі

1
перевірте це посилання: glatter-gotz.com/blog/2011/02/19/…
qqibrow

1
До речі, оскільки у вас є зручний інтерпретатор Python, це було б набагато простіше зробити надійним та портативним способом за допомогою datetimeмодуля Python.
Чарльз Даффі

3
01.01.2015 до 31.01.2015 не охоплює дати більше одного місяця, тому це дуже простий випадок.
Wintermute

2
... так що , якщо ви на самому справі бачите необхідність в wait(як, помилки відбувається з - за паралельні процеси , коли ви не), то у вас є що - то більш цікаве / більш ускладнений відбувається, що вимагає більш складного рішення ( як прохання підпроцесу успадкувати файл блокування), що є достатньою складністю і досить не пов'язаною на сьогодні арифметикою, що це повинно бути окремим питанням.
Чарльз Даффі

Відповіді:


197

Використовуючи дату GNU:

d=2015-01-01
while [ "$d" != 2015-02-20 ]; do 
  echo $d
  d=$(date -I -d "$d + 1 day")
done

Зверніть увагу, що оскільки для цього використовується порівняння рядків, для цього потрібні повні позначення дат ребра ISO 8601 (не видаляти початкові нулі). Щоб перевірити наявність дійсних вхідних даних і примусити їх до дійсної форми, якщо це можливо, ви також можете використовувати 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

Останнє доповнення : щоб перевірити $startdateце раніше $enddate, якщо ви очікуєте лише дати між 1000 і 9999 роками, ви можете просто використати порівняння рядків, як це:

while [[ "$d" < "$enddate" ]]; do

Щоб бути на дуже безпечній стороні після 10000 року, коли лексикографічне порівняння руйнується, використовуйте

while [ "$(date -d "$d" +%Y%m%d)" -lt "$(date -d "$enddate" +%Y%m%d)" ]; do

Вираз $(date -d "$d" +%Y%m%d)перетворюється $dв числову форму, тобто 2015-02-23стає 20150223, і ідея полягає в тому, що дати в цій формі можна порівняти чисельно.


1
Звісно, ​​чому б ні. Це просто цикл оболонки, який використовує дати, оскільки ітератор не змінює того, що ви можете робити всередині нього.
Wintermute

1
@SirBenBenji, ... що сказано, %1є конструкцією управління завданнями, і контроль роботи вимкнено в неінтерактивних сценаріях, якщо ви явно не вмикаєте його самі. Правильний спосіб посилатися на окремі підпроцеси всередині сценарію - це PID, і навіть тоді очікування завершення процесів відбувається автоматично, якщо вони явно не фонові у вашому коді (як у a &), або вони самостійно від’єднуються (у такому випадку waitнавіть не буде працювати, і PID, наданий оболонці, буде анульований процесом подвійного розгалуження, що використовується для самофонування).
Чарльз Даффі

1
Подивившись уважніше, виявляється, що високосні секунди виключаються з мітки часу UNIX, так що деякі мітки часу відносяться до проміжку в дві секунди. Це, мабуть, робить "gettimeofday" дуже цікавим для реалізації в діапазоні, що перевищує секунду, і я вважаю, що ми повинні вважати себе щасливчиком, що стрибкові секунди ніколи не були вилучені з року. Це означає, що я повинен виправити себе: додавання 86400 секунд до мітки часу unix, мабуть, завжди те саме, що і додавання дня, оскільки немає можливості спеціально посилатися на 2016-12-31T23: 59: 60. ТІЛ.
Wintermute

2
Запуск вашого першого коду (sh test.sh) видає мені помилку: date: незаконна опція - I use: date [-jnRu] [-d dst] [-r секунд] [-t west] [-v [+ | -] val [ymwdHMS]] ... [-f fmt дата | [[[mm] dd] HH] MM [[cc] yy] [. ss]] [+ формат]
dorien

2
Для macOS це не спрацює, спочатку встановіть gnu date apple.stackexchange.com/questions/231224/…
Хайме Агудо,

22

Розширення брекета :

for i in 2015-01-{01..31} …

Більше:

for i in 2015-02-{01..28} 2015-{04,06,09,11}-{01..30} 2015-{01,03,05,07,08,10,12}-{01..31} …

Доказ:

$ 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

Компактний / вкладений:

$ echo 2015-{02-{01..28},{04,06,09,11}-{01..30},{01,03,05,07,08,10,12}-{01..31}} | wc -w
 365

Замовлено, якщо це важливо:

$ 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

Оскільки це невпорядковано, ви можете просто вирішити високосні роки:

$ 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

3
А як щодо високосних років?
Wintermute

Чи можу я тоді використовувати python /home/user/executeJobs.py 2015-01-{01..31} &> /home/user/2015-01-{01..31}.log ?
Steve K

@SirBenBenji Це залежить від executeJobs.py.
Коджіро

Я повинен сказати, що executeJobs потребує параметра date, і мені потрібно почекати, щоб кожен запуск завершився. Це робота з великими даними, і вона ні в якому разі не повинна розпочинатися до завершення кожного попереднього запуску. Я повинен був подумати про це раніше, вибачте, що забув про це.
Steve K

10
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

3

У мене була та сама проблема, і я спробував деякі з наведених вище відповідей, можливо, вони нормальні, але жодна з цих відповідей не фіксувалася на тому, що я намагався зробити, використовуючи macOS.

Я намагався переглядати дати в минулому, і мені працювало наступне:

#!/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

Сподіваюся, це допоможе.


Я рекомендую не використовувати ім'я змінної, яке збігається з дуже потужною командою побітового копіювання, але це лише я.
MerrillFraz

Простіший спосіб - використовувати gdateзамість datemacOS.
північне дерево

2

Якщо ви хочете використати цикл від дати введення до будь-якого діапазону нижче, він також надрукує вихід у форматі ррррMMdd ...

#!/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

2

Мені потрібно було переглядати дати в AIX, BSD, Linux, OS X та Solaris. dateКоманда є одним з найменш портативних і найнещасніші команд для використання на різних платформах з якими я зіткнувся. Мені було простіше написати my_dateкоманду, яка просто працювала скрізь.

Наведена нижче програма C бере дату початку та додає або віднімає від неї дні. Якщо жодна дата не вказана, вона додає або віднімає дні від поточної дати.

my_dateКоманда дозволяє виконувати наступні операції всюди:

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

І код С:

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

2

Баш найкраще писати, використовуючи труби (|). Це має призвести до ефективної та одночасної (швидшої) обробки пам'яті. Я б написав наступне:

seq 0 100 | xargs printf "20 Aug 2020 - %sdays\n" \
  | xargs -d '\n' -l date -d

Далі буде надруковано дату 20 aug 2020та надруковано дати за 100 днів до неї.

Цей універсальний лайнер можна перетворити на утиліту.

#!/usr/bin/env bash

# date-range template <template>

template="${1:--%sdays}"

export LANG;

xargs printf "$template\n" | xargs -d '\n' -l date -d

За замовчуванням ми обираємо ітерацію за останні 1 день за раз.

$ seq 10 | date-range
Mon Mar  2 17:42:43 CET 2020
Sun Mar  1 17:42:43 CET 2020
Sat Feb 29 17:42:43 CET 2020
Fri Feb 28 17:42:43 CET 2020
Thu Feb 27 17:42:43 CET 2020
Wed Feb 26 17:42:43 CET 2020
Tue Feb 25 17:42:43 CET 2020
Mon Feb 24 17:42:43 CET 2020
Sun Feb 23 17:42:43 CET 2020
Sat Feb 22 17:42:43 CET 2020

Скажімо, ми хочемо генерувати дати до певної дати. Ми поки не знаємо, скільки ітерацій нам потрібно, щоб туди потрапити. Скажімо, Том народився 1 січня 2001 р. Ми хочемо сформувати кожну дату до певної. Ми можемо досягти цього, використовуючи sed.

seq 0 $((2**63-1)) | date-range | sed '/.. Jan 2001 /q'

$((2**63-1))Прийом використовується для створення великого цілого числа.

Після виходу sed він також вийде з утиліти діапазону дат.

Можна також виконати ітерацію, використовуючи 3-місячний інтервал:

$ 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

Я створив сховище date-seq, яке вдосконалює цю ідею та документує її трохи краще. github.com/bas080/date-seq
bas080

1

Якщо ви застрягли в даті busybox , я вважаю роботу з мітками часу найбільш надійним підходом:

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

Це виведе:

2019-12-30
2019-12-31
2020-01-01
2020-01-02
2020-01-03
2020-01-04

Мітка часу Unix не включає високосних секунд, тому 1 день дорівнює завжди рівно 86400 секундам.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.