Come confrontare due date in una shell?


88

Come si possono confrontare due date in una shell?

Ecco un esempio di come vorrei usarlo, anche se non funziona così com'è:

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

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

Come posso ottenere il risultato desiderato?


Per cosa vuoi fare un giro? Intendi condizionale anziché loop?
— Mat

Perché hai taggato i file ? Queste date sono in realtà orari dei file?
— arte

Dai un'occhiata a stackoverflow: stackoverflow.com/questions/8116503/…
— slm

Risposte:


107

Manca ancora la risposta giusta:

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

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

Si noti che ciò richiede la data GNU. La datesintassi equivalente per BSD date(come quella trovata in OSX di default) èdate -j -f "%F" 2014-08-19 +"%s"


10
Non capisco perché qualcuno ha votato in negativo, è abbastanza preciso e non si occupa di concatenare brutti archi. Converti la tua data in unix timestamp (in secondi), confronta unix timestamp.
— Nicholi,

Penso che il mio principale vantaggio di questa soluzione sia che puoi fare facilmente aggiunte o sottrazioni. Ad esempio, volevo avere un timeout di x secondi, ma la mia prima idea ( timeout=$(`date +%Y%m%d%H%M%S` + 500)) è ovviamente sbagliata.
— iGEL,

Puoi includere la precisione in nanosecondi condate -d 2013-07-18 +%s%N
— typelogic

32

Manca il formato della data per il confronto:

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

Ti mancano anche le strutture ad anello, come pensi di ottenere più date?


Questo non funziona con la datespedizione con macOS.
— Flimm,

date -dnon è standard e non funziona su un tipico UNIX. Su BSD tenta anche di impostare il valore kenel DST.
— schily,

32

Può utilizzare un confronto di stringhe standard per confrontare l'ordinamento cronologico [delle stringhe in un formato di anno, mese, giorno].

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

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

Per fortuna, quando [stringhe che usano il formato AAAA-MM-GG] vengono ordinate * in ordine alfabetico, vengono anche ordinate * in ordine cronologico.

(* - ordinati o confrontati)

Niente di speciale in questo caso. Sìì!


Dov'è il ramo altro qui?
— Vladimir Despotovic,

Questa è l'unica soluzione che ha funzionato per me su Sierra MacOS
— Vladimir Despotovic il

Questo è più semplice della mia soluzione di if [ $(sed -e s/-//g <<< $todate) -ge $(sed -e s/-//g <<< $cond) ];... Questo formato data è stato progettato per essere ordinato utilizzando l'ordinamento alfanumerico standard o per -essere rimosso e ordinato numericamente.
— ctrl-alt-delor,

Questa è di gran lunga la risposta più intelligente qui
— Ed Randall,

7

Questo non è un problema di strutture cicliche ma di tipi di dati.

Quelle date ( todatee cond) sono stringhe, non numeri, quindi non puoi usare l'operatore "-ge" di test. (Ricorda che la notazione tra parentesi quadre equivale al comando test.)

Quello che puoi fare è usare una notazione diversa per le tue date in modo che siano numeri interi. Per esempio:

date +%Y%m%d

produrrà un numero intero come 20130715 per il 15 luglio 2013. Quindi puoi confrontare le tue date con "-ge" e operatori equivalenti.

Aggiornamento: se vengono fornite le date (ad es. Le stai leggendo da un file nel formato 2013-07-13), puoi preelaborarle facilmente con tr.

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

6

L'operatore -gelavora solo con numeri interi, che non sono le tue date.

Se lo script è uno script bash o ksh o zsh, è possibile utilizzare <invece l' operatore. Questo operatore non è disponibile in dash o altre shell che non vanno molto oltre lo standard POSIX.

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

In qualsiasi shell, è possibile convertire le stringhe in numeri rispettando l'ordine delle date semplicemente rimuovendo i trattini.

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

In alternativa, puoi andare tradizionale e utilizzare l' exprutilità.

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

Poiché l'invocazione di sottoprocessi in un ciclo può essere lenta, è preferibile eseguire la trasformazione utilizzando costrutti di elaborazione delle stringhe di shell.

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

Naturalmente, se riesci a recuperare le date senza i trattini in primo luogo, sarai in grado di confrontarle con -ge.


Dov'è l'altro ramo in questo bash?
— Vladimir Despotovic,

2
Questa sembra essere la risposta più corretta. Molto utile!
— Mojtaba Rezaeian,

1

Converto le stringhe in unix-timestamps (secondi dall'1.1.1970 0: 0: 0). Questi possono essere confrontati facilmente

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

Questo non funziona con datequello fornito con macOS.
— Flimm,

Sembra essere lo stesso di unix.stackexchange.com/a/170982/100397 che è stato pubblicato qualche tempo prima del tuo
— roaima

1

C'è anche questo metodo dall'articolo intitolato: Calcolazione semplice di data e ora in BASH da unix.com.

Queste funzioni sono un estratto di uno script in quel 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))
}

uso

# 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"

Questo non funziona con datequello fornito con macOS.
— Flimm,

Se installi gdate su MacOS tramite brew, questo può essere facilmente modificato per funzionare cambiando datein gdate.
— slm

1
Questa risposta è stata molto utile sul mio caso. Dovrebbe votare!
— Mojtaba Rezaeian,

1

risposta specifica

Dato che mi piace ridurre forchette e che permettono molti trucchi, c'è il mio scopo:

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

Bene ora:

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

Questo ripopolerà entrambe le variabili $todatee $cond, usando solo un fork, con l'output del quale date -f -prenderà stdio per leggere una data per riga.

Infine, potresti rompere il ciclo con

((todate>=cond))&&break

O come una funzione :

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
}

L'uso di printf che potrebbe rendere l'ora della data con i secondi dall'epoca (vedi man bash;-)

Questo script usa solo un fork.

Alternativa con forchette limitate e funzione di lettura della data

Ciò creerà un sottoprocesso dedicato (solo un fork):

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

Poiché input e output sono aperti, è possibile eliminare la quinta voce.

La funzione:

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

Nota Al fine di prevenire forche inutili come todate=$(myDate 2013-07-18), la variabile deve essere impostata dalla funzione stessa. E per consentire la sintassi libera (con o senza virgolette per il datestring), il nome della variabile deve essere l'ultimo argomento.

Quindi data confronto:

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

può rendere:

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

se al di fuori di un ciclo.

Oppure usa la funzione bash del connettore shell:

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

o

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

(Che non sono esattamente gli stessi: .shcontengono script di test completi se non forniti)

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
}

Bash di profilazione contiene una buona dimostrazione di come l'utilizzo date -f -potrebbe ridurre il fabbisogno di risorse.
— F. Hauri,

0

le date sono stringhe, non numeri interi; non è possibile confrontarli con operatori aritmetici standard.

un approccio che puoi usare se il separatore è garantito -:

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

Questo funziona fino al lontano bash 2.05b.0(1)-release.


0

Puoi anche usare la funzione integrata di mysql per confrontare le date. Dà il risultato in 'giorni'.

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

Basta inserire i valori di $DBUSERe $DBPASSWORDvariabili in base al tuo.


0

FWIW: su Mac, sembra inserire solo una data come "2017-05-05" nella data che aggiungerà l'ora corrente alla data e otterrai un valore diverso ogni volta che la converti in ora. per ottenere un tempo di epoca più coerente per le date fisse, includere valori fittizi per ore, minuti e secondi:

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

Questa potrebbe essere una stranezza limitata alla versione della data fornita con macOS .... testata su macOS 10.12.6.


0

Facendo eco alla risposta di @Gilles ("L'operatore -ge funziona solo con numeri interi"), ecco un esempio.

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

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

datetimeconversioni intere a epoch, per la risposta di @ mike-q a 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"è maggiore di (più recente di) "$old_date", ma questa espressione valuta erroneamente come False:

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

... mostrato esplicitamente, qui:

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

Confronti interi:

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

Il motivo per cui questo confronto non funziona bene con una stringa di data sillabata è che la shell presuppone che un numero con uno 0 iniziale sia un ottale, in questo caso il mese "07". Sono state proposte varie soluzioni, ma la più rapida e semplice è quella di eliminare i trattini. Bash ha una funzione di sostituzione delle stringhe che lo rende rapido e semplice, quindi il confronto può essere eseguito come espressione aritmetica:

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

if (( ${todate//-/} > ${cond//-/} ));
then
    echo larger
fi
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.