Wie kann ich mit Bash Daten durchlaufen?


85

Ich habe ein solches Bash-Skript:

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

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

Jetzt möchte ich eine Reihe von Daten durchlaufen, z. B. 2015-01-01 bis 2015-01-31.

Wie in Bash zu erreichen?

Update :

Nice-to-have: Es sollte kein Job gestartet werden, bevor ein vorheriger Lauf abgeschlossen wurde. In diesem Fall wird nach Abschluss von executeJobs.py die Bash-Eingabeaufforderung $zurückgegeben.

könnte ich zB wait%1in meine Schleife aufnehmen?


Sind Sie auf einer Plattform mit GNU-Datum?
Charles Duffy

1
Überprüfen Sie diesen Link: glatter-gotz.com/blog/2011/02/19/…
qqibrow

1
Übrigens, da Sie einen Python-Interpreter zur Hand haben, wäre dies mit dem datetimePython-Modul sehr viel einfacher und zuverlässiger und portabler .
Charles Duffy

3
2015-01-01 bis 2015-01-31 erstreckt sich nicht über Daten in mehr als einem Monat, daher ist dies ein sehr einfacher Fall.
Wintermute

2
wenn Sie ... ja, sind tatsächlich ein Sehen Bedarf an wait(wie in, Fehler aufgrund gleichzeitiger Prozesse geschehen , wenn Sie nicht tun), dann haben Sie etwas Interessanteres / kompliziertere los, was eine kompliziertere Lösung benötigt ( B. den Unterprozess zu bitten, eine Sperrdatei zu erben), was ausreichend komplex ist und nicht mit der aktuellen Arithmetik zusammenhängt, so dass es sich um eine separate Frage handeln sollte.
Charles Duffy

Antworten:


198

Verwenden des GNU-Datums:

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

Da dies einen Zeichenfolgenvergleich verwendet, ist eine vollständige ISO 8601-Notation der Kantendaten erforderlich (führende Nullen nicht entfernen). Sie können verwenden, um nach gültigen Eingabedaten zu suchen und diese nach Möglichkeit in ein gültiges Formular zu zwingendate :

# 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

Eine letzte Ergänzung : Um dies zu überprüfen, können Sie einfach einen Zeichenfolgenvergleich wie $startdatefolgt verwenden $enddate, wenn Sie nur Daten zwischen den Jahren 1000 und 9999 erwarten:

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

Um über das Jahr 10000 hinaus auf der sicheren Seite zu sein, verwenden Sie, wenn der lexikografische Vergleich zusammenbricht

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

Der Ausdruck $(date -d "$d" +%Y%m%d)wird $din eine numerische Form konvertiert , dh 2015-02-23wird 20150223, und die Idee ist, dass Daten in dieser Form numerisch verglichen werden können.


1
Sicher warum nicht. Es ist nur eine Shell-Schleife, die Datumsangaben verwendet, da der Iterator nicht ändert, was Sie darin tun können.
Wintermute

1
@SirBenBenji, ... das heißt, %1ist ein Jobsteuerungskonstrukt, und die Jobsteuerung ist in nicht interaktiven Skripten deaktiviert, es sei denn, Sie aktivieren sie explizit selbst. Der richtige Weg, um auf einzelne Unterprozesse innerhalb eines Skripts zu verweisen, ist die PID. Selbst dann erfolgt das Warten auf den Abschluss von Prozessen automatisch, es sei denn, sie werden explizit von Ihrem Code (wie bei a &) hinterlegt oder lösen sich selbst (in diesem Fall) waitfunktioniert nicht einmal und die der Shell zugewiesene PID wird durch den Double-Fork-Prozess ungültig, der für den Selbsthintergrund verwendet wird.
Charles Duffy

1
Bei näherer Betrachtung scheint es, dass Schaltsekunden vom UNIX-Zeitstempel ausgeschlossen sind, sodass sich einige Zeitstempel auf ein Leerzeichen von zwei Sekunden beziehen. Das macht offenbar'gettimeofday` sehr interessant in dem Sub-Sekunden - Bereich zu implementieren, und ich nehme an, wir sie glücklich schätzen sollen , dass Schaltsekunden wurden nie entfernt von einem Jahr. Dies bedeutet, dass ich mich selbst korrigieren muss: Das Hinzufügen von 86400 Sekunden zu einem Unix-Zeitstempel ist wohl immer dasselbe wie das Hinzufügen eines Tages, da es keine Möglichkeit gibt, speziell auf 2016-12-31T23: 59: 60 zu verweisen. Bis.
Wintermute

2
Das Ausführen Ihres ersten Codes (sh test.sh) gibt mir einen Fehler: Datum: unzulässige Option - Ich verwende: Datum [-jnRu] [-d dst] [-r Sekunden] [-t West] [-v [+ | -] val [ymwdHMS]] ... [-f fmt date | [[[mm] dd] HH] MM [[cc] yy] [. ss]] [+ format]
dorien

3
Für macOS wird es nicht funktionieren, installieren Sie zuerst gnu
Jaime Agudo

22

Klammererweiterung :

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

Mehr:

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

Beweis:

$ 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 / verschachtelt:

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

Bestellt, wenn es darauf ankommt:

$ 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

Da es ungeordnet ist, können Sie einfach Schaltjahre später angehen:

$ 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
Was ist mit Schaltjahren?
Wintermute

Kann ich dann verwenden python /home/user/executeJobs.py 2015-01-{01..31} &> /home/user/2015-01-{01..31}.log ?
Steve K

@ SirBenBenji Das kommt darauf an executeJobs.py.
Kojiro

Ich muss sagen, executeJobs benötigt den Datumsparameter und ich muss bei jedem Lauf warten, um abzuschließen. Es handelt sich um einen Big-Data-Job, der unter keinen Umständen vor Abschluss jedes vorherigen Laufs gestartet werden sollte. Ich hätte vorher darüber nachdenken sollen, tut mir leid, dass ich es vergessen habe.
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

Ich hatte das gleiche Problem und habe einige der oben genannten Antworten ausprobiert. Vielleicht sind sie in Ordnung, aber keine dieser Antworten hat sich auf das konzentriert, was ich mit macOS versucht habe.

Ich habe in der Vergangenheit versucht, Daten zu durchlaufen, und Folgendes hat bei mir funktioniert:

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

Ich hoffe es hilft.


Ich empfehle, keinen Variablennamen zu verwenden, der zufällig mit einem sehr leistungsfähigen Bit-für-Bit-Kopierbefehl zusammenfällt, aber das bin nur ich.
MerrillFraz

Einfacher ist die Verwendung gdateanstelle von datein macOS.
Nordbaum

2

Wenn eine Schleife vom Eingabedatum bis zu einem der folgenden Bereiche verwendet werden soll, wird auch die Ausgabe im Format JJJJMMTT ...

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

Ich musste Daten unter AIX, BSDs, Linux, OS X und Solaris durchlaufen. Der dateBefehl ist einer der am wenigsten portablen und miserabelsten Befehle, die plattformübergreifend verwendet werden können. Ich fand es einfacher, eine zu schreibenmy_date Befehl , der einfach überall funktionierte.

Das folgende C-Programm nimmt ein Startdatum und addiert oder subtrahiert Tage davon. Wenn kein Datum angegeben wird, werden Tage zum aktuellen Datum addiert oder subtrahiert.

Mit dem my_dateBefehl können Sie überall Folgendes ausführen:

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

Und der C-Code:


2

Bash wird am besten durch Nutzung von Rohren (|) geschrieben. Dies sollte zu einer speichereffizienten und gleichzeitigen (schnelleren) Verarbeitung führen. Ich würde folgendes schreiben:

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

Im Folgenden wird das Datum 20 aug 2020und die Daten der 100 Tage davor gedruckt.

Dieser Oneliner kann zu einem Dienstprogramm gemacht werden.

#!/usr/bin/env bash

# date-range template <template>

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

export LANG;

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

Standardmäßig iterieren wir jeweils in den letzten 1 Tag.

$ 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

Angenommen, wir möchten Daten bis zu einem bestimmten Datum generieren. Wir wissen noch nicht, wie viele Iterationen wir benötigen, um dorthin zu gelangen. Angenommen, Tom wurde am 1. Januar 2001 geboren. Wir möchten jedes Datum bis zu einem bestimmten Datum generieren. Wir können dies erreichen, indem wir sed verwenden.

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

Der $((2**63-1))Trick wird verwendet, um eine große Ganzzahl zu erstellen.

Nach dem Beenden von sed wird auch das Dienstprogramm für den Datumsbereich beendet.

Man kann auch mit einem Intervall von 3 Monaten iterieren:

$ 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

Ich habe ein Date-Seq-Repository erstellt, das diese Idee verbessert und ein bisschen besser dokumentiert. github.com/bas080/date-seq
bas080

1

Wenn Sie mit dem Datum der Busybox nicht weiterkommen , hat sich die Arbeit mit Zeitstempeln als der zuverlässigste Ansatz herausgestellt:

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

Dies wird Folgendes ausgeben:

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

Der Unix-Zeitstempel enthält keine Schaltsekunden, sodass 1 Tag immer genau 86400 Sekunden entspricht.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.