Wie vergleiche ich zwei Daten in einer Shell?


88

Wie können zwei Daten in einer Shell verglichen werden?

Hier ist ein Beispiel, wie ich das verwenden möchte, obwohl es nicht wie es ist funktioniert:

todate=2013-07-18
cond=2013-07-15

if [ $todate -ge $cond ];
then
    break
fi                           

Wie kann ich das gewünschte Ergebnis erzielen?


Wofür möchtest du eine Schleife machen? Meinen Sie eher bedingt als schleife?
Mat

Warum haben Sie Dateien markiert ? Sind diese Daten tatsächlich Dateizeiten?
Manatwork

Schauen Sie sich dies auf stackoverflow an: stackoverflow.com/questions/8116503/…
slm

Antworten:


107

Die richtige Antwort fehlt noch:

todate=$(date -d 2013-07-18 +%s)
cond=$(date -d 2014-08-19 +%s)

if [ $todate -ge $cond ];
then
    break
fi  

Beachten Sie, dass hierfür das GNU-Datum erforderlich ist. Die äquivalente dateSyntax für BSD date(wie sie standardmäßig in OSX zu finden ist) lautetdate -j -f "%F" 2014-08-19 +"%s"


10
Donno, warum jemand dies abgelehnt hat, es ist ziemlich genau und es geht nicht darum, hässliche Zeichenfolgen zu verketten. Konvertieren Sie Ihr Datum in einen Unix-Zeitstempel (in Sekunden) und vergleichen Sie die Unix-Zeitstempel.
Nicholi

Ich denke, mein Hauptvorteil bei dieser Lösung ist, dass man leicht Additionen oder Subtraktionen machen kann. Zum Beispiel wollte ich eine Auszeit von x Sekunden, aber meine erste Idee ( timeout=$(`date +%Y%m%d%H%M%S` + 500)) ist offensichtlich falsch.
iGEL

Sie können die Genauigkeit von Nanosekunden mit date -d 2013-07-18 +%s%N
einbeziehen

32

Ihnen fehlt das Datumsformat für den Vergleich:

#!/bin/bash

todate=$(date -d 2013-07-18 +"%Y%m%d")  # = 20130718
cond=$(date -d 2013-07-15 +"%Y%m%d")    # = 20130715

if [ $todate -ge $cond ]; #put the loop where you need it
then
 echo 'yes';
fi

Ihnen fehlen auch Schleifenstrukturen. Wie planen Sie, mehr Termine zu bekommen?


Dies funktioniert nicht mit den datemit macOS ausgelieferten.
Flimm

date -dist kein Standard und funktioniert unter einem typischen UNIX nicht. Unter BSD wird sogar versucht, den Kenel-Wert festzulegen DST.
Schily

32

Kann einen Standard-Zeichenfolgenvergleich verwenden, um die chronologische Reihenfolge [von Zeichenfolgen im Format Jahr, Monat, Tag] zu vergleichen.

date_a=2013-07-18
date_b=2013-07-15

if [[ "$date_a" > "$date_b" ]] ;
then
    echo "break"
fi

Wenn [Zeichenfolgen im Format JJJJ-MM-TT] in alphabetischer Reihenfolge * sortiert werden, werden sie erfreulicherweise auch in chronologischer Reihenfolge * sortiert.

(* - sortiert oder verglichen)

In diesem Fall ist nichts Besonderes erforderlich. Yay!


Wo ist die else-Filiale hier?
Vladimir Despotovic

Dies ist die einzige Lösung, die für mich unter Sierra MacOS funktioniert hat
Vladimir Despotovic,

Dies ist einfacher als meine Lösung von if [ $(sed -e s/-//g <<< $todate) -ge $(sed -e s/-//g <<< $cond) ];... Dieses Datumsformat wurde so entworfen, dass es nach der normalen alphanumerischen Sortierung sortiert oder -gestrippt und numerisch sortiert werden kann.
Strg-Alt-Delor

Dies ist bei weitem die klügste Antwort hier auf
Ed Randall

7

Dies ist kein Problem von Schleifenstrukturen, sondern von Datentypen.

Diese Daten ( todateund cond) sind Zeichenfolgen und keine Zahlen. Sie können daher nicht den Operator "-ge" von verwenden test. (Denken Sie daran, dass die Schreibweise in eckigen Klammern dem Befehl entspricht test.)

Sie können für Ihre Daten eine andere Notation verwenden, sodass es sich um Ganzzahlen handelt. Zum Beispiel:

date +%Y%m%d

erzeugt eine Ganzzahl wie 20130715 für den 15. Juli 2013. Dann können Sie Ihre Daten mit "-ge" und entsprechenden Operatoren vergleichen.

Update: Wenn Ihre Daten angegeben sind (z. B. Sie sie aus einer Datei im Format 2013-07-13 lesen), können Sie sie mit tr einfach vorverarbeiten.

$ echo "2013-07-15" | tr -d "-"
20130715

6

Der Operator arbeitet -genur mit Ganzzahlen, die Ihre Daten nicht enthalten.

Wenn es sich bei Ihrem Skript um ein Bash-, Ksh- oder Zsh-Skript handelt, können Sie <stattdessen den Operator verwenden. Dieser Operator ist nicht in Dash- oder anderen Shells verfügbar, die den POSIX-Standard nicht wesentlich überschreiten.

if [[ $cond < $todate ]]; then break; fi

In jeder Shell können Sie die Zeichenfolgen in Zahlen konvertieren und dabei die Reihenfolge der Datumsangaben einhalten, indem Sie einfach die Bindestriche entfernen.

if [ "$(echo "$todate" | tr -d -)" -ge "$(echo "$cond" | tr -d -)" ]; then break; fi

Alternativ können Sie das exprDienstprogramm auch auf herkömmliche Weise verwenden .

if expr "$todate" ">=" "$cond" > /dev/null; then break; fi

Da das Aufrufen von Unterprozessen in einer Schleife langsam sein kann, ziehen Sie möglicherweise die Transformation mithilfe von Shell-String-Verarbeitungskonstrukten vor.

todate_num=${todate%%-*}${todate#*-}; todate_num=${todate_num%%-*}${todate_num#*-}
cond_num=${cond%%-*}${cond#*-}; cond_num=${cond_num%%-*}${cond_num#*-}
if [ "$todate_num" -ge "$cond_num" ]; then break; fi

Wenn Sie die Daten zunächst ohne die Bindestriche abrufen können, können Sie sie natürlich mit denen vergleichen -ge.


Wo ist der Zweig else in dieser Bash?
Vladimir Despotovic

2
Dies scheint die richtigste Antwort zu sein. Sehr hilfreich!
Mojtaba Rezaeian

1

Ich konvertiere die Strings in Unix-Zeitstempel (Sekunden seit 1.1.1970 0: 0: 0). Diese lassen sich leicht vergleichen

unix_todate=$(date -d "${todate}" "+%s")
unix_cond=$(date -d "${cond}" "+%s")
if [ ${unix_todate} -ge ${unix_cond} ]; then
   echo "over condition"
fi

Dies funktioniert nicht mit dem date, der mit macOS ausgeliefert wird.
Flimm

Scheint dasselbe zu sein wie unix.stackexchange.com/a/170982/100397, das einige Zeit vor Ihrer
Veröffentlichung veröffentlicht wurde

1

Es gibt auch diese Methode aus dem Artikel mit dem Titel: Einfache Datums- und Zeitberechnung in BASH von unix.com.

Diese Funktionen sind ein Auszug aus einem Skript in diesem Thread!

date2stamp () {
    date --utc --date "$1" +%s
}

dateDiff (){
    case $1 in
        -s)   sec=1;      shift;;
        -m)   sec=60;     shift;;
        -h)   sec=3600;   shift;;
        -d)   sec=86400;  shift;;
        *)    sec=86400;;
    esac
    dte1=$(date2stamp $1)
    dte2=$(date2stamp $2)
    diffSec=$((dte2-dte1))
    if ((diffSec < 0)); then abs=-1; else abs=1; fi
    echo $((diffSec/sec*abs))
}

Verwendungszweck

# calculate the number of days between 2 dates
    # -s in sec. | -m in min. | -h in hours  | -d in days (default)
    dateDiff -s "2006-10-01" "2006-10-31"
    dateDiff -m "2006-10-01" "2006-10-31"
    dateDiff -h "2006-10-01" "2006-10-31"
    dateDiff -d "2006-10-01" "2006-10-31"
    dateDiff  "2006-10-01" "2006-10-31"

Dies funktioniert nicht mit dem date, der mit macOS ausgeliefert wird.
Flimm

Wenn Sie gdate unter MacOS über brew installieren, können Sie dies problemlos ändern date, indem Sie auf ändern gdate.
slm

1
Diese Antwort war in meinem Fall sehr hilfreich. Es sollte höher bewerten!
Mojtaba Rezaeian

1

spezifische Antwort

Da ich Gabeln und gerne reduziere , erlaube ich viele Tricks, gibt es mein Ziel:

todate=2013-07-18
cond=2013-07-15

Na dann:

{ read todate; read cond ;} < <(date -f - +%s <<<"$todate"$'\n'"$cond")

Dadurch werden beide Variablen neu gefüllt $todateund $condmit nur einem Fork wird das Ergebnis angezeigt date -f -, mit dem stdio für das zeilenweise Lesen eines Datums verwendet wird.

Schließlich könnten Sie Ihre Schleife mit brechen

((todate>=cond))&&break

Oder als Funktion :

myfunc() {
    local todate cond
    { read todate
      read cond
    } < <(
      date -f - +%s <<<"$1"$'\n'"$2"
    )
    ((todate>=cond))&&return
    printf "%(%a %d %b %Y)T older than %(%a %d %b %Y)T...\n" $todate $cond
}

Mit der eingebauten , printfdie das Datum und die Uhrzeit in Sekunden von der Epoche rendern kann (siehe man bash;-)

Dieses Skript verwendet nur eine Gabel.

Alternative mit eingeschränkter Gabel- und Datumsleserfunktion

Dadurch wird ein dedizierter Unterprozess erstellt (nur eine Verzweigung):

mkfifo /tmp/fifo
exec 99> >(exec stdbuf -i 0 -o 0 date -f - +%s >/tmp/fifo 2>&1)
exec 98</tmp/fifo
rm /tmp/fifo

Da Ein- und Ausgang offen sind, kann der FIFO-Eintrag gelöscht werden.

Die Funktion:

myDate() {
    local var="${@:$#}"
    shift
    echo >&99 "${@:1:$#-1}"
    read -t .01 -u 98 $var
}

Hinweis Um unnötige Gabeln wie zu vermeiden todate=$(myDate 2013-07-18), muss die Variable von der Funktion selbst gesetzt werden. Um eine freie Syntax (mit oder ohne Anführungszeichen für die Datenfolge) zuzulassen, muss der Variablenname das letzte Argument sein.

Dann Datumsvergleich:

myDate 2013-07-18        todate
myDate Mon Jul 15 2013   cond
(( todate >= cond )) && {
    printf "To: %(%c)T > Cond: %(%c)T\n" $todate $cond
    break
}

kann rendern:

To: Thu Jul 18 00:00:00 2013 > Cond: Mon Jul 15 00:00:00 2013
bash: break: only meaningful in a `for', `while', or `until' loop

wenn außerhalb einer Schleife.

Oder verwenden Sie die Shell-Connector-Bash-Funktion:

wget https://github.com/F-Hauri/Connector-bash/raw/master/shell_connector.bash

oder

wget https://f-hauri.ch/vrac/shell_connector.sh

(Was nicht genau dasselbe ist: .shEnthält das vollständige Testskript, wenn keine Quellen angegeben wurden.)

source shell_connector.sh
newConnector /bin/date '-f - +%s' @0 0

myDate 2013-07-18        todate
myDate "Mon Jul 15 2013"   cond
(( todate >= cond )) && {
    printf "To: %(%c)T > Cond: %(%c)T\n" $todate $cond
    break
}

Profiling Bash enthält eine gute Demonstration, wie die Verwendung date -f -den Ressourcenbedarf reduzieren kann.
F. Hauri

0

Daten sind Zeichenfolgen, keine ganzen Zahlen. Sie können nicht mit Standardarithmetikoperatoren verglichen werden.

Ein Ansatz, den Sie verwenden können, wenn das Trennzeichen garantiert ist -:

IFS=-
read -ra todate <<<"$todate"
read -ra cond <<<"$cond"
for ((idx=0;idx<=numfields;idx++)); do
  (( todate[idx] > cond[idx] )) && break
done
unset IFS

Das funktioniert schon seit bash 2.05b.0(1)-release.


0

Sie können auch die eingebaute Funktion von mysql verwenden, um die Daten zu vergleichen. Es gibt das Ergebnis in "Tagen".

from_date="2015-01-02"
date_today="2015-03-10"
diff=$(mysql -u${DBUSER} -p${DBPASSWORD} -N -e"SELECT DATEDIFF('${date_today}','${from_date}');")

Fügen Sie einfach die Werte $DBUSERund $DBPASSWORDVariablen nach Ihren Wünschen ein.


0

FWIW: Auf einem Mac wird beim Einspeisen eines Datums wie "2017-05-05" die aktuelle Uhrzeit an das Datum angehängt und bei jeder Konvertierung in die Epochenzeit ein anderer Wert angezeigt. Um eine konsistentere Epochenzeit für feste Daten zu erhalten, geben Sie Dummy-Werte für Stunden, Minuten und Sekunden an:

date -j -f "%Y-%m-%d %H:%M:%S" "2017-05-05 00:00:00" +"%s"

Dies kann eine Seltsamkeit sein, die auf die Version des Datums beschränkt ist, die mit macOS ausgeliefert wurde .... getestet unter macOS 10.12.6.


0

Echoing @ Gilles Antwort ("Der Operator -ge funktioniert nur mit ganzen Zahlen"), hier ist ein Beispiel.

curr_date=$(date +'%Y-%m-%d')
echo "$curr_date"
2019-06-20

old_date="2019-06-19"
echo "$old_date"
2019-06-19

datetimeGanzzahlige Konvertierungen nach epochder Antwort von @ mike-q unter https://stackoverflow.com/questions/10990949/convert-date-time-string-to-epoch-in-bash :

curr_date_int=$(date -d "${curr_date}" +"%s")
echo "$curr_date_int"
1561014000

old_date_int=$(date -d "${old_date}" +"%s")
echo "$old_date_int"
1560927600

"$curr_date"ist größer als (neuer als) "$old_date", aber dieser Ausdruck wird fälschlicherweise ausgewertet als False:

if [[ "$curr_date" -ge "$old_date" ]]; then echo 'foo'; fi

... hier explizit gezeigt:

if [[ "$curr_date" -ge "$old_date" ]]; then echo 'foo'; else echo 'bar'; fi
bar

Ganzzahlige Vergleiche:

if [[ "$curr_date_int" -ge "$old_date_int" ]]; then echo 'foo'; fi
foo

if [[ "$curr_date_int" -gt "$old_date_int" ]]; then echo 'foo'; fi
foo

if [[ "$curr_date_int" -lt "$old_date_int" ]]; then echo 'foo'; fi

if [[ "$curr_date_int" -lt "$old_date_int" ]]; then echo 'foo'; else echo 'bar'; fi
bar

0

Der Grund, warum dieser Vergleich mit einer mit Bindestrich versehenen Datumszeichenfolge nicht gut funktioniert, ist die Annahme, dass eine Zahl mit einer führenden 0 ein Oktal ist, in diesem Fall der Monat "07". Es wurden verschiedene Lösungen vorgeschlagen, aber am schnellsten und einfachsten lassen sich die Bindestriche entfernen. Bash verfügt über eine Funktion zum Ersetzen von Zeichenfolgen, die dies schnell und einfach macht. Der Vergleich kann dann als arithmetischer Ausdruck ausgeführt werden:

todate=2013-07-18
cond=2013-07-15

if (( ${todate//-/} > ${cond//-/} ));
then
    echo larger
fi
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.