sh: Monatserste innerhalb eines Intervalls ausgeben

Vom einfachen Programm zum fertigen Debian-Paket, Fragen rund um Programmiersprachen, Scripting und Lizenzierung.
Antworten
Benutzeravatar
Meillo
Moderator
Beiträge: 8813
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

sh: Monatserste innerhalb eines Intervalls ausgeben

Beitrag von Meillo » 09.03.2016 20:50:15

Hoi,

ich stehe gerade etwas auf dem Schlauch. Vielleicht habt ihr ja ein paar hilfreiche Ideen.

Ich will das Datum aller Monatsersten zwischen einem Start- und Enddatum ausgeben bekommen, und das in (moeglichst portabler) Shell.

Beispiel:
Start = 2015-10-01
Ende = 2016-03-01
Ausgabe = 2015-10-01 2015-11-01 2015-12-01 2016-01-01 2016-02-01 2016-03-01


Momentan habe ich eine statische Liste, die ich mit Unterstuetzung der Brace Extension erzeuge, was natuerlich doof ist.

Eine nette Idee war noch diese:

Code: Alles auswählen

start="`date -d 2015-10-01 +%s`"
end="`date -d 2016-03-01 +%s`"
secpermonth="`expr 3600 \* 24 \* 30`"
for i in `seq $start $secpermonth $end` ; do
	date -d @$i +%F
done
... nur leider sind Monate nicht gleich lang, so dass ich davon Abstand nehmen musste.


Bin auf eure Vorschlaege gespannt.
Use ed once in a while!

Benutzeravatar
TRex
Moderator
Beiträge: 8071
Registriert: 23.11.2006 12:23:54
Lizenz eigener Beiträge: MIT Lizenz
Wohnort: KA

Re: sh: Monatserste innerhalb eines Intervalls ausgeben

Beitrag von TRex » 09.03.2016 21:35:28

Wäre die Verwendung von cal(1) eine Option?
Jesus saves. Buddha does incremental backups.
Windows ist doof, Linux funktioniert nichtDon't break debian!Wie man widerspricht

Benutzeravatar
heisenberg
Beiträge: 3548
Registriert: 04.06.2015 01:17:27
Lizenz eigener Beiträge: MIT Lizenz

Re: sh: Monatserste innerhalb eines Intervalls ausgeben

Beitrag von heisenberg » 09.03.2016 22:10:38

Code: Alles auswählen

start="`date -d 2015-10-01 +%s`"
Subshells sind Bäh! :)

So ungefähr?

Code: Alles auswählen

#!/bin/bash

start_date=2010-05-01
end_date=2012-08-01
start_mon=${start_date%-*}
start_mon=${start_mon#*-}
start_year=${start_date%%-*}

for((year=$start_year;1;year++)){
   start_mon=${start_mon:-1}
   for((mon=$start_mon;$mon<=12;mon++)){
      cur_date=$(printf "%s-%02d-01" "$year" "$mon")     # Subshells sind bäh!
      printf "$cur_date"
      [ "$cur_date" == "$end_date" ] && break 2
      ((i=$i+1))
   }
   start_mon=""
}
Hmmm... Portable shell ist das jetzt nicht. Höchstens portable Bash. Kann man aber alles zur Verwendung mit cut, expr, seq ummodeln.

Ok. Nochmal für sh...

Code: Alles auswählen

#!/bin/sh

start_date=2010-05-01
end_date=2012-08-01
start_mon=`echo $start_date | cut -d- -f2`
start_year=`echo $start_date | cut -d- -f1`
end_year=`echo $end_date | cut -d- -f1`

for year in `seq $start_year $end_year` ; do 
   if test -z "$start_mon" ; then
        start_mon=1
   fi
   for mon in `seq $start_mon 12` ;do
      mon_out=`seq -f %02g $mon $mon`
      cur_date="$year-$mon_out-01"
      echo -n "$cur_date "
      if test "$cur_date" = "$end_date" ; then
                exit 0
      fi
   done
   start_mon=""
done
Jede Rohheit hat ihren Ursprung in einer Schwäche.

wanne
Moderator
Beiträge: 7462
Registriert: 24.05.2010 12:39:42

Re: sh: Monatserste innerhalb eines Intervalls ausgeben

Beitrag von wanne » 09.03.2016 23:12:18

Portabel. Aber dafür vermutlich deutlich langsamer:

Code: Alles auswählen

start=$(date -d 2015-10-01 +%s)
end=$(date -d 2016-03-01 +%s)
secperday=$((3600 * 24))
for i in $(seq $start $secperday $end ) ; do
   if [ $(date -d @$i +%d) -eq 1 ]
      then date -d @$i  +%F
   fi
done
Und nicht ganz failsafe, wenn das Script um Mitternacht vor einer negativen Schaltsekunde (gab's noch nicht,) aufgerufen wird.
rot: Moderator wanne spricht, default: User wanne spricht.

Benutzeravatar
Meillo
Moderator
Beiträge: 8813
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: sh: Monatserste innerhalb eines Intervalls ausgeben

Beitrag von Meillo » 09.03.2016 23:13:27

TRex hat geschrieben:Wäre die Verwendung von cal(1) eine Option?
Ich wuesste nicht wie.

heisenberg hat geschrieben: So ungefähr?

Code: Alles auswählen

for((year=$start_year;1;year++)){
   for((mon=$start_mon;$mon<=12;mon++)){
      ...
   }
}
Hmm, eigentlich wollte ich vermeiden, es von Hand zu rechnen (das haette ich schon selber hinbekommen). Nein, ich suche eine elegantere Moeglichkeit ...


Jetzt bin ich doch noch auf einen Ansatz gekommen:

Code: Alles auswählen

echo {`seq -s, 2000 2020`}-{`seq -s, -f%02.0f 1 12`}-01 | tr \  \\n  | sed -n '/2015-10-01/,/2016-03-01/p'
Darueber kann man dann iterieren.
Use ed once in a while!

rendegast
Beiträge: 15041
Registriert: 27.02.2006 16:50:33
Lizenz eigener Beiträge: MIT Lizenz

Re: sh: Monatserste innerhalb eines Intervalls ausgeben

Beitrag von rendegast » 10.03.2016 12:36:02

Mit den Spezi von 'date' arbeiten?

Code: Alles auswählen

#!/bin/sh

start=2015-10
start=${start}-1
end=10

for i in $(seq -$end $end); do 
    date -d "${start} ${i}month" +%F
done
Aber auch

Code: Alles auswählen

$ date -d "2015-10-30 4month"
Di 1. Mär 00:00:00 CET 2016
wäre so hier wohl nicht gewünscht.
Zuletzt geändert von rendegast am 10.03.2016 12:42:22, insgesamt 1-mal geändert.
mfg rendegast
-----------------------
Viel Eifer, viel Irrtum; weniger Eifer, weniger Irrtum; kein Eifer, kein Irrtum.
(Lin Yutang "Moment in Peking")

Benutzeravatar
hikaru
Moderator
Beiträge: 13585
Registriert: 09.04.2008 12:48:59

Re: sh: Monatserste innerhalb eines Intervalls ausgeben

Beitrag von hikaru » 10.03.2016 12:40:23

Ist der date-Aufruf hier nicht wahnsinnig teuer?
Ohne es getestet zu haben scheinen mir die Varianten mit Stringmanipulationen eleganter zu sein. Zumal ja der Monatserste auch einfach ist (im Vergleich z.B. zum Monatsletzten).

rendegast
Beiträge: 15041
Registriert: 27.02.2006 16:50:33
Lizenz eigener Beiträge: MIT Lizenz

Re: sh: Monatserste innerhalb eines Intervalls ausgeben

Beitrag von rendegast » 10.03.2016 12:44:07

Ja, das Monatsende, der 30. zum Beispiel:
$ ./scr.sh
2014-12-30
2015-01-30
2015-03-02
2015-03-30
2015-04-30
2015-05-30
2015-06-30
2015-07-30
2015-08-30
2015-09-30
2015-10-30
2015-11-30
2015-12-30
2016-01-30
2016-03-01
2016-03-30
2016-04-30
2016-05-30
2016-06-30
2016-07-30
2016-08-30
mfg rendegast
-----------------------
Viel Eifer, viel Irrtum; weniger Eifer, weniger Irrtum; kein Eifer, kein Irrtum.
(Lin Yutang "Moment in Peking")

Benutzeravatar
hikaru
Moderator
Beiträge: 13585
Registriert: 09.04.2008 12:48:59

Re: sh: Monatserste innerhalb eines Intervalls ausgeben

Beitrag von hikaru » 10.03.2016 12:58:27

@rendegast:
Ich verstehe deinen Post nicht. Der 30. ist nicht zwangsläufig das Monatsende.

Benutzeravatar
ThorstenS
Beiträge: 2875
Registriert: 24.04.2004 15:33:31

Re: sh: Monatserste innerhalb eines Intervalls ausgeben

Beitrag von ThorstenS » 10.03.2016 13:16:48

OT, aber den Monatsletzten eines Jahres kannst du dir wie folgt anzeigen lassen

Code: Alles auswählen

for i in {1..12}; do date -d "yesterday $i/01 +1 month " "+%B %d Tage"; done| column -t
Januar     31  Tage
Februar    29  Tage
März       31  Tage
April      30  Tage
Mai        31  Tage
Juni       30  Tage
Juli       31  Tage
August     31  Tage
September  30  Tage
Oktober    31  Tage
November   30  Tage
Den 1. eines jeden Jahres demzufolge so:

Code: Alles auswählen

$ for i in {1..12}; do date -d "$i/01" -I ; done
2016-01-01
2016-02-01
2016-03-01
2016-04-01
2016-05-01
2016-06-01
2016-07-01
2016-08-01
2016-09-01
2016-10-01
2016-11-01
2016-12-01

Code: Alles auswählen

$ for i in {1..12}; do date -d "$i/01" -I ; done| tr -s '\n' ' '
2016-01-01 2016-02-01 2016-03-01 2016-04-01 2016-05-01 2016-06-01 2016-07-01 2016-08-01 2016-09-01 2016-10-01 2016-11-01 2016-12-01 
Das hilft Meillo allerdings bei dem Problem nicht weiter.

Benutzeravatar
ThorstenS
Beiträge: 2875
Registriert: 24.04.2004 15:33:31

Re: sh: Monatserste innerhalb eines Intervalls ausgeben

Beitrag von ThorstenS » 10.03.2016 14:45:29

Das tut es, wobei awk den sed über gensub &Co.KG sicherlich überflüssig machen könnte:

Code: Alles auswählen

#!/bin/sh
# YYYYMM
START=200710
END=201602                                                                                                                
seq -f "%06g01" $START $END | sed 's#\([[:digit:]]\{4\}\)\([[:digit:]]\{2\}\)#\1-\2-#' | awk -F- '$2 < 13 && $2 >00 '| tr -s '\n' ' '
Zuletzt geändert von ThorstenS am 10.03.2016 14:55:24, insgesamt 1-mal geändert.

Benutzeravatar
heisenberg
Beiträge: 3548
Registriert: 04.06.2015 01:17:27
Lizenz eigener Beiträge: MIT Lizenz

Re: sh: Monatserste innerhalb eines Intervalls ausgeben

Beitrag von heisenberg » 10.03.2016 14:55:12

Code: Alles auswählen

sd=2012-12-01
ed=2015-10-01
while :;do 
        ((i=${i:--1}+1))
        date=$(date +"%Y-%m-01" --date="$sd + $i month")
        echo $date
        [ $date == $ed ] && break
done
...oder:

Code: Alles auswählen

sd=2012-12-01
ed=2013-10-01
while :;do 
        ((i=${i:--1}+1))
        date +"%Y-%m-01" --date="$sd + $i month" | tee >(cat >&2) | grep -q $ed && break 
done  2>&1
Jede Rohheit hat ihren Ursprung in einer Schwäche.

rendegast
Beiträge: 15041
Registriert: 27.02.2006 16:50:33
Lizenz eigener Beiträge: MIT Lizenz

Re: sh: Monatserste innerhalb eines Intervalls ausgeben

Beitrag von rendegast » 10.03.2016 16:41:22

hikaru hat geschrieben: Ich verstehe deinen Post nicht. Der 30. ist nicht zwangsläufig das Monatsende.
Dann muß das geändert werden, alle Monate bekommen 30 Tage.
Weinachten bis Sylvester wird zu variablen Schaltwoche.

Eine Woche bekommt 5 oder 6 Tage, beides geeignete Teiler.

Die Kalenderindustrie könnte dicht machen.
mfg rendegast
-----------------------
Viel Eifer, viel Irrtum; weniger Eifer, weniger Irrtum; kein Eifer, kein Irrtum.
(Lin Yutang "Moment in Peking")

newdeb
Beiträge: 134
Registriert: 03.02.2011 11:11:21
Lizenz eigener Beiträge: MIT Lizenz
Wohnort: Frankfurt

Re: sh: Monatserste innerhalb eines Intervalls ausgeben

Beitrag von newdeb » 10.03.2016 18:24:09

Man muss nicht immer an der bash kleben:

Code: Alles auswählen

ksh93 -c 'START=2015-10-01;for m in {0..5}; do printf "%(%Y-%m-%d)T\n" "$START + $m month"; done'

Cae
Beiträge: 6349
Registriert: 17.07.2011 23:36:39
Wohnort: 2130706433

Re: sh: Monatserste innerhalb eines Intervalls ausgeben

Beitrag von Cae » 11.03.2016 11:27:48

Ein eher bruteforce-maessiger Ansatz:

Code: Alles auswählen

#! /bin/sh -eu

first=2015-01-01
last=2020-01-01

for candidate in $(seq "$(date -d "$first" +%s)" "$(echo '20*3600' | bc -ql)" "$(date -d "$last" +%s)"); do
	date -d @"$candidate" +%F
done |
grep '01$'
Ich rechne in Unix-Sekunden um, iteriere mit einem Intervall von willkuerlichen 20 Stunden (um garantiert jeden Tag zu erwischen, auch bei Sommer/Winterzeit und aehnlichen Zeitspruengen) und sammele per Regex alle Monatsersten raus. Nicht speichereffizient (besser vielleicht seq | while read), sehr fork(2)-lastig, nicht fuer Jahrhunderte geeignet (braucht auf meiner Hardware 2.5s fuer die oben angegebenen 5 Jahre). Ausserdem frage ich mich, ob seq(1) irgendwann overflowt, ganz zu schweigen vom Jahr-2038-Problem.

Gruss Cae
If universal surveillance were the answer, lots of us would have moved to the former East Germany. If surveillance cameras were the answer, camera-happy London, with something like 500,000 of them at a cost of $700 million, would be the safest city on the planet.

—Bruce Schneier

Benutzeravatar
ThorstenS
Beiträge: 2875
Registriert: 24.04.2004 15:33:31

Re: sh: Monatserste innerhalb eines Intervalls ausgeben

Beitrag von ThorstenS » 11.03.2016 11:55:21

Meine Variante ohne date liegt bei 0.004s

Benutzeravatar
heisenberg
Beiträge: 3548
Registriert: 04.06.2015 01:17:27
Lizenz eigener Beiträge: MIT Lizenz

dateutils

Beitrag von heisenberg » 10.06.2016 02:37:21

Auch wenn es dem Threadinhalt nicht ganz entspricht: Es gibt da noch Debiandateutils, was einige nette Werkzeuge zum arbeiten mit Datumsinformationen hat.

--> http://www.fresse.org/dateutils/
Jede Rohheit hat ihren Ursprung in einer Schwäche.

Benutzeravatar
habakug
Moderator
Beiträge: 4313
Registriert: 23.10.2004 13:08:41
Lizenz eigener Beiträge: MIT Lizenz

Re: sh: Monatserste innerhalb eines Intervalls ausgeben

Beitrag von habakug » 10.06.2016 06:31:00

Hallo!

Ich hätte es so versucht:

Code: Alles auswählen

# for i in {12..1}; do date -d "-$i month -$(($(date +%d)-1)) days"; done
Gruss, habakug
( # = root | $ = user | !! = mod ) (Vor der PN) (Debianforum-Wiki) (NoPaste)

Benutzeravatar
Meillo
Moderator
Beiträge: 8813
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: dateutils

Beitrag von Meillo » 10.06.2016 09:17:05

heisenberg hat geschrieben:Auch wenn es dem Threadinhalt nicht ganz entspricht: Es gibt da noch Debiandateutils, was einige nette Werkzeuge zum arbeiten mit Datumsinformationen hat.

--> http://www.fresse.org/dateutils/
Darauf warte ich ja schon lange! Hab mich immer schon gefragt, warum die ihren Weg nicht in Unix gefunden haben. Timestamps sind zwar gut rechenbar ... aber will man das immer von Hand machen?

(Ohne es bislang selbst getestet zu haben ... aber von der Idee ist klar was es tut.) Sowas wie datediff(1) ist ein Tool, das eine Orthogonalitaet bietet, die bislang in Unix fehlt. Super, dass sich das jetzt endlich aendert. :-)

EDIT: ... und alles im One-True-Date-Format.Genial! YMMD! :THX:
Use ed once in a while!

Benutzeravatar
heisenberg
Beiträge: 3548
Registriert: 04.06.2015 01:17:27
Lizenz eigener Beiträge: MIT Lizenz

Re: dateutils

Beitrag von heisenberg » 10.06.2016 13:30:51

Meillo hat geschrieben:...die bislang in Unix fehlt...
Braucht halt immer so ein bisschen Zeit bis das dann auch bekannt wird, dass es sowas gibt :)

Dateutils sind in Debian seit September 2013.

viewtopic.php?f=1&t=144847
Jede Rohheit hat ihren Ursprung in einer Schwäche.

wanne
Moderator
Beiträge: 7462
Registriert: 24.05.2010 12:39:42

Re: sh: Monatserste innerhalb eines Intervalls ausgeben

Beitrag von wanne » 10.06.2016 14:58:12

Was kann das ding, was date nicht kann? (Außer einer möglicherweise etwas ansprechenderen Syntax.)
rot: Moderator wanne spricht, default: User wanne spricht.

Benutzeravatar
Meillo
Moderator
Beiträge: 8813
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: sh: Monatserste innerhalb eines Intervalls ausgeben

Beitrag von Meillo » 10.06.2016 22:54:24

wanne hat geschrieben:Was kann das ding, was date nicht kann? (Außer einer möglicherweise etwas ansprechenderen Syntax.)
Dann schau dir mal die Beispiele zu datediff(1) hier an: http://www.fresse.org/dateutils/ Das kann date(1) nicht. Mit date(1) muss man immer ueber Unix Timestamps gehen, wenn man rechnen will. Das geht schon auch, aber datediff(1) macht das Unhandliche handlich. :-)
Use ed once in a while!

Benutzeravatar
ThorstenS
Beiträge: 2875
Registriert: 24.04.2004 15:33:31

Re: sh: Monatserste innerhalb eines Intervalls ausgeben

Beitrag von ThorstenS » 11.06.2016 08:28:45

Mit date kann man scho einiges tun - wenn man den richtigen syntax kennt und nichts besseres kennt :wink:
Ich hab mal vor einer Weile mein Wissen darüber niedergeschrieben:
http://www.linuxforen.de/forums/showthr ... ate-Befehl

dateutils.ddiff werde ich ab jetzt auch benutzen - das gefällt mir sehr gut. :THX:

Antworten