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.