Bash kullanarak tarihler arasında nasıl geçiş yapılır?


87

Böyle bir bash betiğim var:

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

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

Şimdi, örneğin 2015-01-01 ile 2015-01-31 arasındaki bir tarih aralığı arasında döngü yapmak istiyorum.

Bash'de nasıl başarılır?

Güncelleme :

Güzel-olması: Önceki bir çalıştırma tamamlanmadan önce hiçbir iş başlatılmamalıdır. Bu durumda, executeJobs.py tamamlandığında bash istemi $geri dönecektir.

örneğin kendi döngüme dahil edebilir miyim wait%1?


GNU tarihi olan bir platformda mısınız?
Charles Duffy

1
bu bağlantıyı kontrol edin: glatter-gotz.com/blog/2011/02/19/…
qqibrow

1
BTW, kullanışlı bir Python yorumlayıcınız olduğundan, bunu datetimePython modülünü kullanarak güvenilir ve taşınabilir bir şekilde yapmak çok çok daha kolay olacaktır .
Charles Duffy

3
2015-01-01'den 2015-01-31'e kadar bir aydan fazla olan tarihleri ​​kapsamıyor, bu yüzden bu çok basit bir durum.
Wintermute

2
aslında bir görüyorsanız ... yani, ihtiyaç için wait(olduğu gibi, yok vadesi geldiğinde eşzamanlı süreçlere oluyor böcek), o zaman daha ilginç bir şey var / daha (daha komplike bir çözüm ihtiyacı olan oluyor karmaşık alt işlemden bir kilit dosyasını miras almasını istemek gibi), bu yeterli karmaşıklıktır ve ayrı bir soru olması gereken tarih aritmetiği ile yeterince ilgisizdir.
Charles Duffy

Yanıtlar:


202

GNU tarihini kullanma:

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

Bunun dize karşılaştırması kullandığından, kenar tarihlerinin tam ISO 8601 gösterimini gerektirdiğini unutmayın (baştaki sıfırları kaldırmayın). Geçerli giriş verilerini kontrol etmek ve mümkünse geçerli bir forma zorlamak için şunları da kullanabilirsiniz 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

Son bir ekleme : Daha $startdateönce olduğunu kontrol etmek için , $enddateyalnızca 1000 ile 9999 yılları arasında tarihler bekliyorsanız, aşağıdaki gibi dize karşılaştırmasını kullanabilirsiniz:

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

Sözlükbilimsel karşılaştırmanın bozulduğu 10000 yılından sonra çok güvenli tarafta olmak için şunu kullanın:

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

İfade $(date -d "$d" +%Y%m%d)dönüştürür $dyani sayısal formuna dönüşmesi, 2015-02-23olur 20150223, ve fikri bu formda tarihleri sayısal karşılaştırılabilir olmasıdır.


1
Tabii neden olmasın. Bu sadece bir kabuk döngüsü, tarihler kullanır, çünkü yineleyici içinde neler yapabileceğinizi değiştirmez.
Wintermute

1
@SirBenBenji, ... dedi ki, %1bir iş kontrol yapısıdır ve siz onu açıkça açmadığınız sürece etkileşimli olmayan komut dosyalarında iş kontrolü kapatılır. Bir komut dosyası içindeki bireysel alt işlemlere atıfta bulunmanın uygun yolu PID'dir ve o zaman bile, kodunuz tarafından açıkça arka planı oluşturulmadıkça ( a'da olduğu gibi ) veya kendi kendine ayrılmadıkça (bu durumda işlemlerin tamamlanmasını beklemek) otomatiktir. çalışmaz bile ve kabuğa verilen PID, kendinden arka planda kullanılan çift çatal işlemi tarafından geçersiz kılınacaktır). &wait
Charles Duffy

1
Daha yakından baktıktan sonra, artık saniyelerin UNIX zaman damgasının dışında bırakıldığı, dolayısıyla bazı zaman damgalarının iki saniyelik bir boşluğu ifade ettiği görülmektedir. Bu görünüşe göre "gettimeofday" ı saniyenin altındaki aralıkta uygulamayı çok ilginç kılıyor ve sanırım artık saniyeler bir yıldan hiç çıkarılmadığı için kendimizi şanslı saymalıyız . Bu, kendimi düzeltmem gerektiği anlamına gelir: Bir unix zaman damgasına 86400 saniye eklemek, tartışmasız her zaman bir gün eklemekle aynıdır, çünkü özellikle 2016-12-31T23: 59: 60'a atıfta bulunmanın bir yolu yoktur. TIL.
Wintermute

2
İlk kodunuzu (sh test.sh) çalıştırmak bana bir hata veriyor: tarih: geçersiz seçenek - Kullanım: tarih [-jnRu] [-d dst] [-r saniye] [-t batı] [-v [+ | -] val [ymwdHMS]] ... [-f fmt tarihi | [[[mm] gg] SS] AA [[cc] yy] [. ss]] [+ format]
dorien

3
MacOS için çalışmaz, önce gnu date apple.stackexchange.com/questions/231224/…
Jaime Agudo

22

Brace genişlemesi :

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

Daha:

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

Kanıt:

$ 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

Kompakt / iç içe:

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

Önemliyse sipariş edildi:

$ 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

Sırasız olduğundan, artık yılların üstesinden gelebilirsiniz:

$ 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
Artık yıllar ne olacak?
Wintermute

O zaman kullanabilir miyim python /home/user/executeJobs.py 2015-01-{01..31} &> /home/user/2015-01-{01..31}.log ?
Steve K

@SirBenBenji Buna bağlıdır executeJobs.py.
kojiro

ExecuteJobs’un tarih parametresine ihtiyacı olduğunu ve her çalışmanın tamamlanmasını beklemem gerektiğini söylemeliyim. Bu bir Büyük Veri işidir ve hiçbir koşulda önceki her çalıştırma tamamlanmadan önce başlatılmamalıdır. Bunu daha önce düşünmeliydim, unuttuğum için üzgünüm.
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

Aynı sorunu yaşadım ve yukarıdaki cevaplardan bazılarını denedim, belki tamamlar, ancak bu cevapların hiçbiri macOS kullanarak yapmaya çalıştığım şeyle sabitlenmedi.

Geçmişteki tarihleri ​​yinelemeye çalışıyordum ve şu benim için işe yaradı:

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

Umarım yardımcı olur.


Çok güçlü bir bit'e bit kopyalama komutuyla çakışan bir değişken adı kullanmamanızı öneririm, ama bu sadece benim.
MerrillFraz

Daha basit yol, macOS gdateyerine kullanmaktır date.
northtree

2

Giriş tarihinden aşağıdaki herhangi bir aralığa döngü yapmak istenirse kullanılabilir, ayrıca çıktı yyyyMMdd formatında yazdırılır ...

#!/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'ler, Linux, OS X ve Solaris'te tarihler arasında geçiş yapmam gerekiyordu. dateKomut ben karşılaştım platformlarındaki kullanımına az taşınabilir ve en sefil komutları biridir. Her my_dateyerde çalışan bir komut yazmayı daha kolay buldum .

Aşağıdaki C programı bir başlangıç ​​tarihi alır ve ondan günler ekler veya çıkarır. Tarih belirtilmezse, geçerli tarihe gün ekler veya çıkarır.

my_dateKomut her yerde aşağıdakileri yapabilirsiniz:

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

Ve C kodu:


2

Bash, en iyi borulardan (|) yararlanılarak yazılır. Bu, bellek verimli ve eşzamanlı (daha hızlı) işleme ile sonuçlanmalıdır. Ben şunu yazardım:

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

Aşağıdakiler 20 aug 2020, 100 günün tarihini ve ondan önceki 100 günün tarihlerini yazdıracaktır.

Bu oneliner bir yardımcı program haline getirilebilir.

#!/usr/bin/env bash

# date-range template <template>

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

export LANG;

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

Varsayılan olarak her seferinde son 1 güne yineleme yapmayı seçiyoruz.

$ 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

Diyelim ki belirli bir tarihe kadar tarihler oluşturmak istiyoruz. Oraya ulaşmak için henüz kaç tane yinelemeye ihtiyacımız olduğunu bilmiyoruz. Diyelim ki Tom 1 Ocak 2001'de doğdu. Her tarihi belirli bir tarihe kadar oluşturmak istiyoruz. Bunu sed kullanarak başarabiliriz.

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

$((2**63-1))Hüner büyük tamsayı oluşturmak için kullanılır.

Sed çıktıktan sonra, tarih aralığı yardımcı programından da çıkacaktır.

3 aylık bir aralık kullanarak da yineleme yapılabilir:

$ 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

Bu fikri geliştiren ve biraz daha iyi belgeleyen bir tarih-seq deposu yaptım. github.com/bas080/date-seq
bas080

1

Meşgul kutusu tarihiyle sıkışıp kaldıysanız , zaman damgalarıyla çalışmanın en güvenilir yaklaşım olduğunu gördüm:

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

Bu çıktı:

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

Unix zaman damgası artık saniye içermez, bu nedenle 1 gün her zaman tam olarak 86400 saniyeye eşittir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.