จะวนรอบวันที่โดยใช้ Bash ได้อย่างไร?


87

ฉันมีสคริปต์ทุบตีดังกล่าว:

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

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

ตอนนี้ฉันต้องการวนรอบช่วงวันที่เช่น 2015-01-01 จนถึง 2015-01-31

วิธีการบรรลุใน Bash?

อัปเดต :

Nice-to-have: ไม่ควรเริ่มงานก่อนที่การรันครั้งก่อนจะเสร็จสิ้น ในกรณีนี้เมื่อ executeJobs.py เสร็จสิ้น bash prompt $จะกลับมา

เช่นฉันสามารถรวมwait%1ในวงของฉันได้หรือไม่?


คุณอยู่บนแพลตฟอร์มที่มีวันที่ GNU หรือไม่?
Charles Duffy

1
ตรวจสอบลิงค์นี้: glatter-gotz.com/blog/2011/02/19/…
qqibrow

1
BTW เนื่องจากคุณมีล่าม Python ที่มีประโยชน์สิ่งนี้จะง่ายกว่ามากในวิธีที่เชื่อถือได้และพกพาได้โดยใช้datetimeโมดูล Python
Charles Duffy

3
2015-01-01 จนถึง 2015-01-31 จะไม่มีช่วงวันที่เกินหนึ่งเดือนดังนั้นจึงเป็นกรณีที่ง่ายมาก
Wintermute

2
... ดังนั้นถ้าคุณจริงเห็นความจำเป็นที่จะwait(ในขณะที่ข้อบกพร่องที่เกิดขึ้นเนื่องจากกระบวนการพร้อมกันเมื่อคุณทำไม่ได้) แล้วคุณมีสิ่งที่น่าสนใจมากขึ้น / ซับซ้อนมากขึ้นที่เกิดขึ้นซึ่งต้องมีการแก้ปัญหาที่มีความซับซ้อนมากขึ้น ( เช่นการขอให้กระบวนการย่อยสืบทอดไฟล์ล็อก) ซึ่งมีความซับซ้อนเพียงพอและไม่เกี่ยวข้องกับเลขคณิตวันที่เพียงพอที่ควรเป็นคำถามแยกต่างหาก
Charles Duffy

คำตอบ:


202

ใช้วันที่ 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

โปรดทราบว่าเนื่องจากสิ่งนี้ใช้การเปรียบเทียบสตริงจึงต้องใช้รูปแบบ 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นี้หากคุณคาดหวังเฉพาะวันที่ระหว่างปี 1,000 ถึง 9999 คุณสามารถใช้การเปรียบเทียบสตริงได้ดังนี้:

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

เพื่อให้อยู่ในด้านที่ปลอดภัยเกินกว่าปี 10,000 เมื่อการเปรียบเทียบเชิงศัพท์หยุดลงให้ใช้

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 ที่กำหนดให้กับเชลล์จะไม่ถูกต้องโดยกระบวนการ double-fork ที่ใช้ในการแบ็คกราวด์ตัวเอง)
Charles Duffy

1
หลังจากดูอย่างใกล้ชิดปรากฏว่าวินาทีอธิกสุรทินถูกแยกออกจากการประทับเวลาของ UNIX ดังนั้นการประทับเวลาบางรายการจึงอ้างอิงถึงช่องว่างสองวินาที เห็นได้ชัดว่าสิ่งนี้ทำให้´gettimeofday` น่าสนใจมากที่จะนำไปใช้ในช่วงย่อยวินาทีและฉันคิดว่าเราควรนับว่าตัวเองโชคดีที่วินาทีอธิกสุรทินไม่เคยถูกลบออกจากหนึ่งปี ซึ่งหมายความว่าฉันต้องแก้ไขตัวเอง: การเพิ่ม 86400 วินาทีในการประทับเวลายูนิกซ์นั้นมักจะเหมือนกับการเพิ่มวันเนื่องจากไม่มีวิธีใดที่จะอ้างถึง 2016-12-31T23: 59: 60 โดยเฉพาะ TIL.
Wintermute

2
การเรียกใช้รหัสแรกของคุณ (sh test.sh) ทำให้ฉันมีข้อผิดพลาด: วันที่: ตัวเลือกที่ผิดกฎหมาย - ฉันใช้: วันที่ [-jnRu] [-d dst] [-r วินาที] [- ทิศตะวันตก] [-v [+ | -] val [ymwdHMS]] ... [-f fmt date | [[[mm] dd] HH] MM [[cc] yy] [. ss]] [+ format]
dorien

3
สำหรับ macOS จะใช้งานไม่ได้ให้ติดตั้ง gnu date apple.stackexchange.com/questions/231224/…
Jaime Agudo

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.
kojiro

ฉันต้องบอกว่า executeJobs ต้องการพารามิเตอร์วันที่และฉันต้องรอในการรันแต่ละครั้งจึงจะเสร็จสมบูรณ์ เป็นงาน Big Data และไม่ควรเริ่มต้นก่อนที่การรันก่อนหน้าแต่ละครั้งจะเสร็จสิ้น ฉันควรจะคิดเรื่องนี้มาก่อนขอโทษที่ลืมเรื่องนี้ไป
Steve K

11
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แทนdateใน macOS
northtree

2

หากต้องการวนซ้ำจากวันที่ป้อนข้อมูลไปยังช่วงใดก็ได้ด้านล่างสามารถใช้งานได้เช่นกันมันจะพิมพ์ผลลัพธ์ในรูปแบบของ 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

ฉันต้องการวนซ้ำวันที่บน AIX, BSDs, 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

และรหัส C:


2

Bash เขียนได้ดีที่สุดโดยใช้ประโยชน์จากไปป์ (|) สิ่งนี้จะส่งผลให้หน่วยความจำมีประสิทธิภาพและการประมวลผลพร้อมกัน (เร็วขึ้น) ฉันจะเขียนสิ่งต่อไปนี้:

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 วินาทีเสมอ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.