Bashを使用して日付をループする方法は?


87

私はそのようなbashスクリプトを持っています:

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で達成する方法は?

更新

便利:前の実行が完了する前にジョブを開始しないでください。この場合、executeJobs.pyが完了すると、bashプロンプト$が返されます。

たとえばwait%1、ループに組み込むことはできますか?


GNU日付のプラットフォームを使用していますか?
Charles Duffy

1
このリンクをチェック:glatter-gotz.com/blog/2011/02/19/...
qqibrow

1
ところで、Pythonインタープリターが手元にあるので、これはdatetimePythonモジュールを使用して信頼性が高く移植可能な方法で行うのがはるかに簡単です。
チャールズダフィー

3
2015-01-01から2015-01-31までは、1か月を超える日付にまたがらないため、非常に単純なケースです。
Wintermute 2015年

2
あなたが実際に見ているならば...そう、必要wait(そうでないとき、並行プロセスに起因する出来事のように、バグを)、あなたは、より複雑なソリューションを(必要な何かもっと面白い/もっと複雑で起こっを、持っていますサブプロセスにロックファイルを継承するように要求するようなものです)。これは十分に複雑で、日付演算とは十分に無関係であるため、別の質問にする必要があります。
チャールズダフィー

回答:


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であることを確認するには、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 2015年

1
@SirBenBenjiは、...とはいえ、%1ジョブ制御構造であり、明示的にオンにしない限り、非対話型スクリプトではジョブ制御がオフになります。スクリプト内の個々のサブプロセスを参照する適切な方法はPIDによるものであり、その場合でも、プロセスがコードによって明示的にバックグラウンドされていない限り(のように)、プロセスが完了するのを待つのは自動的です。れていない作業、あろうシェルに与えられたPID)は、自己の背景に使用される二重フォークプロセスによって無効にされるであろう。&wait
Charles Duffy

1
よく調べてみると、うるう秒がUNIXタイムスタンプから除外されているため、一部のタイムスタンプは2秒のスペースを参照しているようです。これにより、「gettimeofday」を1秒未満の範囲で実装するのは非常に興味深いものになります。うるう秒が1年から削除ていないことは、幸運なことです。つまり、自分で修正する必要があります。UNIXタイムスタンプに86400秒を追加することは、2016-12-31T23:59:60を具体的に参照する方法がないため、ほぼ常に1日を追加することと同じです。TIL。
Wintermute 2017

2
最初のコード(sh test.sh)を実行すると、エラーが発生します:日付:不正なオプション-使用法:日付[-jnRu] [-d dst] [-r秒] [-t西] [-v [+ | -] val [ymwdHMS]] ... [-f fmt date | [[[mm]とDD] HH] MM [[CC] YY] [SS] [+フォーマット]
dorien

3
それは動作しませんMacOSのために、最初のGNU日付インストールapple.stackexchange.com/questions/231224/...
ハイメ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 2015年

その後、使用できますpython /home/user/executeJobs.py 2015-01-{01..31} &> /home/user/2015-01-{01..31}.log か?
スティーブK

@SirBenBenjiそれはに依存しexecuteJobs.pyます。
kojiro 2015年

executeJobsにはdateパラメーターが必要であり、実行が完了するまで待つ必要があると言わなければなりません。これはビッグデータジョブであり、前の各実行が完了する前に開始してはなりません。忘れてすみません、以前に考えておくべきでした。
スティーブ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 2018

より簡単な方法は、macOSのgdate代わりに使用することですdate
ノースツリー

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、BSD、Linux、OS X、Solarisで日付をループする必要がありました。このdateコマンドは、私が遭遇したプラットフォーム間で使用するのに最も移植性が低く、最も惨めなコマンドの1つです。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

特定の日付までの日付を生成するとします。そこに到達するために必要な反復回数はまだわかりません。トムが2001年1月1日に生まれたとしましょう。特定の日付までの各日付を生成したいと思います。これは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
bas0 8020

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.