Làm thế nào để lặp lại các ngày bằng Bash?


85

Tôi có kịch bản bash như vậy:

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

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

Bây giờ tôi muốn lặp lại một loạt các ngày, ví dụ: 2015-01-01 cho đến 2015-01-31.

Làm thế nào để đạt được trong Bash?

Cập nhật :

Nice-to-have: Không nên bắt đầu công việc nào trước khi hoàn thành lần chạy trước đó. Trong trường hợp này, khi thi hành xong, lời nhắc bash $sẽ trả về.

Ví dụ: tôi có thể kết hợp wait%1vào vòng lặp của mình không?


Bạn có đang sử dụng nền tảng có ngày GNU không?
Charles Duffy

1
kiểm tra liên kết này: glatter-gotz.com/blog/2011/02/19/...
qqibrow

1
BTW, vì bạn có một trình thông dịch Python tiện dụng, điều này sẽ dễ dàng hơn nhiều theo cách đáng tin cậy và di động bằng cách sử dụng datetimemô-đun Python.
Charles Duffy

3
2015-01-01 cho đến 2015-01-31 không kéo dài các ngày trong hơn một tháng, vì vậy đây là một trường hợp rất đơn giản.
Wintermute

2
... như vậy, nếu bạn đang thực sự nhìn thấy một nhu cầu để wait(như trong, lỗi xảy ra do quá trình đồng thời khi bạn không), sau đó bạn có một cái gì đó thú vị hơn / phức tạp đang diễn ra, mà cần có một giải pháp phức tạp hơn ( như yêu cầu quy trình con kế thừa một tệp khóa), đủ phức tạp và đủ không liên quan đến số học ngày tháng đến mức nó phải là một câu hỏi riêng biệt.
Charles Duffy

Câu trả lời:


197

Ngày sử dụng GNU:

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

Lưu ý rằng vì điều này sử dụng so sánh chuỗi, nó yêu cầu ký hiệu ISO 8601 đầy đủ về các ngày cạnh (không xóa các số không ở đầu). Để kiểm tra dữ liệu đầu vào hợp lệ và ép buộc nó sang một biểu mẫu hợp lệ nếu có thể, bạn cũng có thể sử dụng 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

Một bổ sung cuối cùng : Để kiểm tra điều đó $startdatelà trước đây $enddate, nếu bạn chỉ mong đợi các ngày từ năm 1000 đến năm 9999, bạn có thể chỉ cần sử dụng so sánh chuỗi như sau:

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

Để ở phía rất an toàn sau năm 10000, khi so sánh từ vựng bị phá vỡ, hãy sử dụng

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

Biểu thức $(date -d "$d" +%Y%m%d)chuyển đổi $dsang dạng số, nghĩa là 2015-02-23trở thành 20150223, và ý tưởng là các ngày ở dạng này có thể được so sánh bằng số.


1
Chắc chắn, tại sao không. Nó chỉ là một vòng lặp shell, nó sử dụng ngày tháng như trình vòng lặp không thay đổi những gì bạn có thể làm bên trong nó.
Wintermute

1
@SirBenBenji, ... có nghĩa %1là , là một cấu trúc kiểm soát công việc và kiểm soát công việc bị tắt trong các tập lệnh không tương tác trừ khi bạn tự bật nó một cách rõ ràng. Cách thích hợp để tham chiếu đến các quy trình con riêng lẻ bên trong một tập lệnh là bằng PID và thậm chí sau đó, việc chờ các quy trình hoàn tất là tự động trừ khi chúng được làm nền rõ ràng bởi mã của bạn (như với a &) hoặc chúng tự tách ra (trong trường hợp đó waitthậm chí sẽ không hoạt động PID được cung cấp cho trình bao sẽ bị vô hiệu bởi quy trình phân tách kép được sử dụng để tự nền).
Charles Duffy

1
Sau khi xem xét kỹ hơn, có vẻ như giây nhuận bị loại trừ khỏi dấu thời gian UNIX, do đó một số dấu thời gian đề cập đến khoảng cách hai giây. Điều này rõ ràng làm cho ´gettimeofday` rất thú vị để triển khai trong phạm vi phụ giây và tôi cho rằng chúng ta nên tự cho mình là người may mắn khi giây nhuận chưa bao giờ bị xóa khỏi một năm. Điều này có nghĩa là tôi phải tự sửa mình: Thêm 86400 giây vào dấu thời gian unix được cho là luôn giống như thêm một ngày, vì không có cách nào để tham chiếu cụ thể đến 2016-12-31T23: 59: 60. GẠCH.
Wintermute

2
Chạy mã đầu tiên của bạn (sh test.sh) cho tôi lỗi: tùy chọn date: bất hợp pháp - Tôi sử dụng: date [-jnRu] [-d dst] [-r seconds] [-t west] [-v [+ | -] val [ymwdHMS]] ... [-f fmt date | [[[mm] dd] HH] MM [[cc] yy] [. ss]] [+ format]
dorien

2
Đối với macOS, nó sẽ không hoạt động, trước tiên hãy cài đặt gnu date apple.stackexchange.com/questions/231224/…
Jaime Agudo,

22

Mở rộng dấu hiệu :

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

Hơn:

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

Bằng chứng:

$ 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

Nhỏ gọn / lồng nhau:

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

Đã đặt hàng, nếu nó quan trọng:

$ 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

Vì nó không có thứ tự, bạn chỉ có thể trải qua những năm nhuận sau:

$ 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
Còn những năm nhuận thì sao?
Wintermute

Sau đó tôi có thể sử dụng python /home/user/executeJobs.py 2015-01-{01..31} &> /home/user/2015-01-{01..31}.log không?
Steve K

@SirBenBenji Điều đó phụ thuộc vào executeJobs.py.
kojiro

Tôi phải nói rằng executeJobs cần tham số ngày và tôi cần đợi mỗi lần chạy để hoàn thành. Đây là một công việc Dữ liệu lớn và không nên bắt đầu trong mọi trường hợp trước khi mỗi lần chạy trước đó hoàn thành. Tôi nên nghĩ đến điều này trước đây, xin lỗi vì đã quên nó.
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

Tôi đã gặp vấn đề tương tự và tôi đã thử một số câu trả lời ở trên, có thể chúng ổn, nhưng không câu trả lời nào trong số đó khắc phục được những gì tôi đang cố gắng thực hiện, sử dụng macOS.

Tôi đã cố gắng lặp lại các ngày trong quá khứ và sau đây là những gì phù hợp với tôi:

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

Hy vọng nó giúp.


Tôi khuyên bạn không nên sử dụng một tên biến trùng hợp với lệnh sao chép bit-for-bit rất mạnh mẽ, nhưng đó chỉ là tôi.
MerrillFraz

Cách đơn giản hơn là sử dụng gdatethay vì datetrong macOS.
Northtree

2

Nếu ai đó muốn lặp lại từ ngày nhập vào bất kỳ phạm vi nào bên dưới có thể được sử dụng, nó cũng sẽ in đầu ra ở định dạng yyyyMMdd ...

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

Tôi cần lặp lại các ngày trên AIX, BSD, Linux, OS X và Solaris. Các datelệnh là một trong những lệnh di nhất và đau khổ nhất để sử dụng trên nền tảng tôi đã gặp phải. Tôi thấy dễ dàng hơn khi viết một my_datelệnh hoạt động ở mọi nơi.

Chương trình C bên dưới lấy ngày bắt đầu và cộng hoặc trừ ngày từ đó. Nếu không có ngày nào được cung cấp, nó sẽ thêm hoặc bớt các ngày từ ngày hiện tại.

Các my_datelệnh cho phép bạn thực hiện những điều sau đây ở khắp mọi nơi:

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

Và mã 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;
}

2

Bash được viết tốt nhất bằng đòn bẩy ống (|). Điều này sẽ dẫn đến hiệu quả bộ nhớ và xử lý đồng thời (nhanh hơn). Tôi sẽ viết như sau:

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

Phần sau sẽ in ngày tháng 20 aug 2020và in ngày của 100 ngày trước nó.

Oneliner này có thể được tạo thành một tiện ích.

#!/usr/bin/env bash

# date-range template <template>

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

export LANG;

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

Theo mặc định, chúng tôi chọn lặp lại 1 ngày qua tại một thời điểm.

$ 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

Giả sử chúng tôi muốn tạo ngày đến một ngày nhất định. Chúng tôi vẫn chưa biết chúng tôi cần bao nhiêu lần lặp lại để đạt được điều đó. Giả sử Tom sinh ngày 1 tháng 1 năm 2001. Chúng tôi muốn tạo từng ngày cho đến một ngày nhất định. Chúng ta có thể đạt được điều này bằng cách sử dụng sed.

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

Thủ $((2**63-1))thuật được sử dụng để tạo một số nguyên lớn.

Khi sed thoát ra, nó cũng sẽ thoát khỏi tiện ích phạm vi ngày.

Người ta cũng có thể lặp lại bằng cách sử dụng khoảng thời gian 3 tháng:

$ 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

Tôi đã tạo một kho lưu trữ date-seq để cải thiện ý tưởng này và tài liệu hóa nó tốt hơn một chút. github.com/bas080/date-seq
bas080

1

Nếu bạn bị mắc kẹt với ngày của hộp thư bận , tôi thấy làm việc với dấu thời gian là cách tiếp cận đáng tin cậy nhất:

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

Điều này sẽ xuất ra:

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

Dấu thời gian Unix không bao gồm giây nhuận, vì vậy 1 ngày luôn bằng chính xác 86400 giây.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.