Aritmetica delle date negli script della shell Unix

Ho bisogno di fare aritmetica data in script di shell Unix che io uso per controllare l’esecuzione di programmi di terze parti.

Sto usando una funzione per incrementare un giorno e un’altra per decrementare:

IncrementaDia(){ echo $1 | awk ' BEGIN { diasDelMes[1] = 31 diasDelMes[2] = 28 diasDelMes[3] = 31 diasDelMes[4] = 30 diasDelMes[5] = 31 diasDelMes[6] = 30 diasDelMes[7] = 31 diasDelMes[8] = 31 diasDelMes[9] = 30 diasDelMes[10] = 31 diasDelMes[11] = 30 diasDelMes[12] = 31 } { anio=substr($1,1,4) mes=substr($1,5,2) dia=substr($1,7,2) if((anio % 4 == 0 && anio % 100 != 0) || anio % 400 == 0) { diasDelMes[2] = 29; } if( dia == diasDelMes[int(mes)] ) { if( int(mes) == 12 ) { anio = anio + 1 mes = 1 dia = 1 } else { mes = mes + 1 dia = 1 } } else { dia = dia + 1 } } END { printf("%04d%02d%02d", anio, mes, dia) } ' } if [ $# -eq 1 ]; then tomorrow=$1 else today=$(date +"%Y%m%d") tomorrow=$(IncrementaDia $hoy) fi 

ma ora ho bisogno di fare aritmetica più complessa.

Qual è il modo migliore e più compatibile per farlo?

Ho scritto uno script bash per convertire date espresse in inglese in date mm / gg / aaaa convenzionali. Si chiama ComputeDate .

Ecco alcuni esempi del suo utilizzo. Per brevità ho messo l’output di ogni invocazione sulla stessa riga dell’invocazione, separati da due punti (:). Le virgolette mostrate sotto non sono necessarie quando si esegue ComputeDate :

 $ ComputeDate 'yesterday': 03/19/2010 $ ComputeDate 'yes': 03/19/2010 $ ComputeDate 'today': 03/20/2010 $ ComputeDate 'tod': 03/20/2010 $ ComputeDate 'now': 03/20/2010 $ ComputeDate 'tomorrow': 03/21/2010 $ ComputeDate 'tom': 03/21/2010 $ ComputeDate '10/29/32': 10/29/2032 $ ComputeDate 'October 29': 10/1/2029 $ ComputeDate 'October 29, 2010': 10/29/2010 $ ComputeDate 'this monday': 'this monday' has passed. Did you mean 'next monday?' $ ComputeDate 'a week after today': 03/27/2010 $ ComputeDate 'this satu': 03/20/2010 $ ComputeDate 'next monday': 03/22/2010 $ ComputeDate 'next thur': 03/25/2010 $ ComputeDate 'mon in 2 weeks': 03/28/2010 $ ComputeDate 'the last day of the month': 03/31/2010 $ ComputeDate 'the last day of feb': 2/28/2010 $ ComputeDate 'the last day of feb 2000': 2/29/2000 $ ComputeDate '1 week from yesterday': 03/26/2010 $ ComputeDate '1 week from today': 03/27/2010 $ ComputeDate '1 week from tomorrow': 03/28/2010 $ ComputeDate '2 weeks from yesterday': 4/2/2010 $ ComputeDate '2 weeks from today': 4/3/2010 $ ComputeDate '2 weeks from tomorrow': 4/4/2010 $ ComputeDate '1 week after the last day of march': 4/7/2010 $ ComputeDate '1 week after next Thursday': 4/1/2010 $ ComputeDate '2 weeks after the last day of march': 4/14/2010 $ ComputeDate '2 weeks after 1 day after the last day of march': 4/15/2010 $ ComputeDate '1 day after the last day of march': 4/1/2010 $ ComputeDate '1 day after 1 day after 1 day after 1 day after today': 03/24/2010 

Ho incluso questo script come una risposta a questo problema perché illustra come eseguire l’aritmetica della data tramite una serie di funzioni di bash e queste funzioni potrebbero rivelarsi utili per gli altri. Gestisce correttamente gli anni bisestili e i secoli bisestili:

 #! /bin/bash # ConvertDate -- convert a human-readable date to a MM/DD/YY date # # Date ::= Month/Day/Year # | Month/Day # | DayOfWeek # | [this|next] DayOfWeek # | DayofWeek [of|in] [Number|next] weeks[s] # | Number [day|week][s] from Date # | the last day of the month # | the last day of Month # # Month ::= January | February | March | April | May | ... | December # January ::= jan | january | 1 # February ::= feb | january | 2 # ... # December ::= dec | december | 12 # Day ::= 1 | 2 | ... | 31 # DayOfWeek ::= today | Sunday | Monday | Tuesday | ... | Saturday # Sunday ::= sun* # ... # Saturday ::= sat* # # Number ::= Day | a # # Author: Larry Morell if [ $# = 0 ]; then printdirections $0 exit fi # Request the value of a variable GetVar () { Var=$1 echo -n "$Var= [${!Var}]: " local X read X if [ ! -z $X ]; then eval $Var="$X" fi } IsLeapYear () { local Year=$1 if [ $[20$Year % 4] -eq 0 ]; then echo yes else echo no fi } # AddToDate -- compute another date within the same year DayNames=(mon tue wed thu fri sat sun ) # To correspond with 'date' output Day2Int () { ErrorFlag= case $1 in -e ) ErrorFlag=-e; shift ;; esac local dow=$1 n=0 while [ $n -lt 7 -a $dow != "${DayNames[n]}" ]; do let n++ done if [ -z "$ErrorFlag" -a $n -eq 7 ]; then echo Cannot convert $dow to a numeric day of wee exit fi echo $[n+1] } Months=(31 28 31 30 31 30 31 31 30 31 30 31) MonthNames=(jan feb mar apr may jun jul aug sep oct nov dec) # Returns the month (1-12) from a date, or a month name Month2Int () { ErrorFlag= case $1 in -e ) ErrorFlag=-e; shift ;; esac M=$1 Month=${M%%/*} # Remove /... case $Month in [az]* ) Month=${Month:0:3} M=0 while [ $M -lt 12 -a ${MonthNames[M]} != $Month ]; do let M++ done let M++ esac if [ -z "$ErrorFlag" -a $M -gt 12 ]; then echo "'$Month' Is not a valid month." exit fi echo $M } # Retrieve month,day,year from a legal date GetMonth() { echo ${1%%/*} } GetDay() { echo $1 | col / 2 } GetYear() { echo ${1##*/} } AddToDate() { local Date=$1 local days=$2 local Month=`GetMonth $Date` local Day=`echo $Date | col / 2` # Day of Date local Year=`echo $Date | col / 3` # Year of Date local LeapYear=`IsLeapYear $Year` if [ $LeapYear = "yes" ]; then let Months[1]++ fi Day=$[Day+days] while [ $Day -gt ${Months[$Month-1]} ]; do Day=$[Day - ${Months[$Month-1]}] let Month++ done echo "$Month/$Day/$Year" } # Convert a date to normal form NormalizeDate () { Date=`echo "$*" | sed 'sX *X/Xg'` local Day=`date +%d` local Month=`date +%m` local Year=`date +%Y` #echo Normalizing Date=$Date > /dev/tty case $Date in */*/* ) Month=`echo $Date | col / 1 ` Month=`Month2Int $Month` Day=`echo $Date | col / 2` Year=`echo $Date | col / 3` ;; */* ) Month=`echo $Date | col / 1 ` Month=`Month2Int $Month` Day=1 Year=`echo $Date | col / 2 ` ;; [az]* ) # Better be a month or day of week Exp=${Date:0:3} case $Exp in jan|feb|mar|apr|may|june|jul|aug|sep|oct|nov|dec ) Month=$Exp Month=`Month2Int $Month` Day=1 #Year stays the same ;; mon|tue|wed|thu|fri|sat|sun ) # Compute the next such day local DayOfWeek=`date +%u` D=`Day2Int $Exp` if [ $DayOfWeek -le $D ]; then Date=`AddToDate $Month/$Day/$Year $[D-DayOfWeek]` else Date=`AddToDate $Month/$Day/$Year $[7+D-DayOfWeek]` fi # Reset Month/Day/Year Month=`echo $Date | col / 1 ` Day=`echo $Date | col / 2` Year=`echo $Date | col / 3` ;; * ) echo "$Exp is not a valid month or day" exit ;; esac ;; * ) echo "$Date is not a valid date" exit ;; esac case $Day in [0-9]* );; # Day must be numeric * ) echo "$Date is not a valid date" exit ;; esac [0-9][0-9][0-9][0-9] );; # Year must be 4 digits [0-9][0-9] ) Year=20$Year ;; esac Date=$Month/$Day/$Year echo $Date } # NormalizeDate jan # NormalizeDate january # NormalizeDate jan 2009 # NormalizeDate jan 22 1983 # NormalizeDate 1/22 # NormalizeDate 1 22 # NormalizeDate sat # NormalizeDate sun # NormalizeDate mon ComputeExtension () { local Date=$1; shift local Month=`GetMonth $Date` local Day=`echo $Date | col / 2` local Year=`echo $Date | col / 3` local ExtensionExp="$*" case $ExtensionExp in *w*d* ) # like 5 weeks 3 days or even 5w2d ExtensionExp=`echo $ExtensionExp | sed 's/[az]/ /g'` weeks=`echo $ExtensionExp | col 1` days=`echo $ExtensionExp | col 2` days=$[7*weeks+days] Due=`AddToDate $Month/$Day/$Year $days` ;; *d ) # Like 5 days or 5d ExtensionExp=`echo $ExtensionExp | sed 's/[az]/ /g'` days=$ExtensionExp Due=`AddToDate $Month/$Day/$Year $days` ;; * ) Due=$ExtensionExp ;; esac echo $Due } # Pop -- remove the first element from an array and shift left Pop () { Var=$1 eval "unset $Var[0]" eval "$Var=(\${$Var[*]})" } ComputeDate () { local Date=`NormalizeDate $1`; shift local Expression=`echo $* | sed 's/^ *a /1 /;s/,/ /' | tr AZ az ` local Exp=(`echo $Expression `) local Token=$Exp # first one local Ans= #echo "Computing date for ${Exp[*]}" > /dev/tty case $Token in */* ) # Regular date M=`GetMonth $Token` D=`GetDay $Token` Y=`GetYear $Token` if [ -z "$Y" ]; then Y=$Year elif [ ${#Y} -eq 2 ]; then Y=20$Y fi Ans="$M/$D/$Y" ;; yes* ) Ans=`AddToDate $Date -1` ;; tod*|now ) Ans=$Date ;; tom* ) Ans=`AddToDate $Date 1` ;; the ) case $Expression in *day*after* ) #the day after Date Pop Exp; # Skip the Pop Exp; # Skip day Pop Exp; # Skip after #echo Calling ComputeDate $Date ${Exp[*]} > /dev/tty Date=`ComputeDate $Date ${Exp[*]}` #Recursive call #echo "New date is " $Date > /dev/tty Ans=`AddToDate $Date 1` ;; *last*day*of*th*month|*end*of*th*month ) M=`date +%m` Day=${Months[M-1]} if [ $M -eq 2 -a `IsLeapYear $Year` = yes ]; then let Day++ fi Ans=$Month/$Day/$Year ;; *last*day*of* ) D=${Expression##*of } D=`NormalizeDate $D` M=`GetMonth $D` Y=`GetYear $D` # echo M is $M > /dev/tty Day=${Months[M-1]} if [ $M -eq 2 -a `IsLeapYear $Y` = yes ]; then let Day++ fi Ans=$[M]/$Day/$Y ;; * ) echo "Unknown expression: " $Expression exit ;; esac ;; next* ) # next DayOfWeek Pop Exp dow=`Day2Int $DayOfWeek` # First 3 chars tdow=`Day2Int ${Exp:0:3}` # First 3 chars n=$[7-dow+tdow] Ans=`AddToDate $Date $n` ;; this* ) Pop Exp dow=`Day2Int $DayOfWeek` tdow=`Day2Int ${Exp:0:3}` # First 3 chars if [ $dow -gt $tdow ]; then echo "'this $Exp' has passed. Did you mean 'next $Exp?'" exit fi n=$[tdow-dow] Ans=`AddToDate $Date $n` ;; [az]* ) # DayOfWeek ... M=${Exp:0:3} case $M in jan|feb|mar|apr|may|june|jul|aug|sep|oct|nov|dec ) ND=`NormalizeDate ${Exp[*]}` Ans=$ND ;; mon|tue|wed|thu|fri|sat|sun ) dow=`Day2Int $DayOfWeek` Ans=`NormalizeDate $Exp` if [ ${#Exp[*]} -gt 1 ]; then # Just a DayOfWeek #tdow=`GetDay $Exp` # First 3 chars #if [ $dow -gt $tdow ]; then #echo "'this $Exp' has passed. Did you mean 'next $Exp'?" #exit #fi #n=$[tdow-dow] #else # DayOfWeek in a future week Pop Exp # toss monday Pop Exp # toss in/off if [ $Exp = next ]; then Exp=2 fi n=$[7*(Exp-1)] # number of weeks n=$[n+7-dow+tdow] Ans=`AddToDate $Date $n` fi ;; esac ;; [0-9]* ) # Number weeks [from|after] Date n=$Exp Pop Exp; case $Exp in w* ) let n=7*n;; esac Pop Exp; Pop Exp #echo Calling ComputeDate $Date ${Exp[*]} > /dev/tty Date=`ComputeDate $Date ${Exp[*]}` #Recursive call #echo "New date is " $Date > /dev/tty Ans=`AddToDate $Date $n` ;; esac echo $Ans } Year=`date +%Y` Month=`date +%m` Day=`date +%d` DayOfWeek=`date +%a |tr AZ az` Date="$Month/$Day/$Year" ComputeDate $Date $* 

Questo script fa ampio uso di un altro script che ho scritto (chiamato col … molte scuse a coloro che usano il col standard fornito con Linux). Questa versione di col semplifica l’estrazione di colonne dallo stdin. Così,

 $ echo abcde | col 5 3 2 

stampe

 ecb 

Ecco la sceneggiatura:

 #!/bin/sh # col -- extract columns from a file # Usage: # col [-r] [c] col-1 col-2 ... # where [c] if supplied defines the field separator # where each col-i represents a column interpreted according to the presence of -r as follows: # -r present : counting starts from the right end of the line # -r absent : counting starts from the left side of the line Separator=" " Reverse=false case "$1" in -r ) Reverse=true; shift; ;; [0-9]* ) ;; * )Separator="$1"; shift; ;; esac case "$1" in -r ) Reverse=true; shift; ;; [0-9]* ) ;; * )Separator="$1"; shift; ;; esac # Replace each col-i with $i Cols="" for f in $* do if [ $Reverse = true ]; then Cols="$Cols \$(NF-$f+1)," else Cols="$Cols \$$f," fi done Cols=`echo "$Cols" | sed 's/,$//'` #echo "Using column specifications of $Cols" awk -F "$Separator" "{print $Cols}" 

Utilizza inoltre le direzioni di stampa per stampare indicazioni quando lo script viene invocato in modo non corretto:

 #!/bin/sh # # printdirections -- print header lines of a shell script # # Usage: # printdirections path # where # path is a *full* path to the shell script in question # beginning with '/' # # To use printdirections, you must include (as comments at the top # of your shell script) documentation for running the shell script. if [ $# -eq 0 -o "$*" = "-h" ]; then printdirections $0 exit fi # Delete the command invocation at the top of the file, if any # Delete from the place where printdirections occurs to the end of the file # Remove the # comments # There is a bizarre oddity here. sed '/#!/d;/.*printdirections/,$d;/ *#/!d;s/# //;s/#//' $1 > /tmp/printdirections.$$ # Count the number of lines numlines=`wc -l /tmp/printdirections.$$ | awk '{print $1}'` # Remove the last line numlines=`expr $numlines - 1` head -n $numlines /tmp/printdirections.$$ rm /tmp/printdirections.$$ 

Per utilizzare questo posto, i tre script nei file ComputeDate , col e printdirections , rispettivamente. Inserisci il file nella directory indicata dal tuo PATH, in genere, ~ / bin. Quindi rendili eseguibili con:

 $ chmod a+x ComputeDate col printdirections 

I problemi? Inviatemi un eMail: morell AT cs.atu.edu Inserisci ComputeDate nell’object.

Supponendo che tu abbia una data GNU , in questo modo:

 date --date='1 days ago' '+%a' 

E frasi simili .

Ecco un modo semplice per eseguire calcoli di date nello scripting di shell.

 meetingDate='12/31/2011' # MM/DD/YYYY Format reminderDate=`date --date=$meetingDate'-1 day' +'%m/%d/%Y'` echo $reminderDate 

Di seguito sono riportate più varianti del calcolo della data che è ansible ottenere utilizzando l’utilità della date . http://www.cyberciti.biz/tips/linux-unix-get-yesterdays-tomorrows-date.html http://www.cyberciti.biz/faq/linux-unix-formatting-dates-for-display/

Questo ha funzionato per me su RHEL.

Per compatibilità con BSD / OS X, è anche ansible utilizzare l’utilità data con -j e -v per eseguire la matematica delle date. Vedi la pagina di manuale di FreeBSD per la data . È ansible combinare le precedenti risposte Linux con questa risposta che potrebbe fornire sufficiente compatibilità.

Su BSD, come Linux, la date corrente ti darà la data attuale:

 $ date Wed 12 Nov 2014 13:36:00 AEDT 

Ora con la data di BSD puoi fare matematica con -v , ad esempio elencando la data di domani ( +1d è più un giorno ):

 $ date -v +1d Thu 13 Nov 2014 13:36:34 AEDT 

È ansible utilizzare una data esistente come base e, facoltativamente, specificare il formato di analisi utilizzando strftime e assicurarsi di utilizzare -j modo da non modificare la data di sistema:

 $ date -j -f "%a %b %d %H:%M:%S %Y %z" "Sat Aug 09 13:37:14 2014 +1100" Sat 9 Aug 2014 12:37:14 AEST 

E puoi usare questo come base per i calcoli della data:

 $ date -v +1d -f "%a %b %d %H:%M:%S %Y %z" "Sat Aug 09 13:37:14 2014 +1100" Sun 10 Aug 2014 12:37:14 AEST 

Nota che -v implica -j .

Più regolazioni possono essere fornite in sequenza:

 $ date -v +1m -v -1w Fri 5 Dec 2014 13:40:07 AEDT 

Vedi la manpage per maggiori dettagli.

Per eseguire operazioni aritmetiche con date su UNIX, si ottiene la data come il numero di secondi dall’epoca UNIX, si esegue qualche calcolo, quindi si converte nuovamente nel formato della data stampabile. Il comando date dovrebbe essere in grado di darti i secondi dall’epoch e convertire da quel numero in una data stampabile. Il mio comando di data locale lo fa,

 % date -n 1219371462 % date 1219371462 Thu Aug 21 22:17:42 EDT 2008 % 

Vedi la tua pagina man date(1) locale date(1) . Per incrementare un giorno aggiungi 86400 secondi.

Perché non scrivere gli script usando un linguaggio come perl o python invece che supporta in modo più naturale l’elaborazione di date complesse? Certo, puoi fare tutto in bash, ma penso che otterrai anche maggiore coerenza tra le piattaforms usando python per esempio, a patto che tu possa assicurare che perl o python sia installato.

Dovrei aggiungere che è abbastanza semplice colbind gli script Python e Perl in uno script di shell contenente.

Mi sono imbattuto in questo un paio di volte. I miei pensieri sono:

  1. L’aritmetica delle date è sempre un dolore
  2. È un po ‘più facile quando si usa il formato di data EPOCH
  3. data su Linux converte in EPOCH, ma non su Solaris
  4. Per una soluzione portatile, è necessario eseguire una delle seguenti operazioni:
    1. Installare la data di gnu su solaris (già menzionato, richiede l’interazione umana per completare)
    2. Usa perl per la parte di data (la maggior parte delle installazioni di Unix include perl, quindi in genere presumo che questa azione non richieda ulteriore lavoro).

Uno script di esempio (controlla l’età di determinati file utente per vedere se è ansible eliminare l’account):

 #!/usr/local/bin/perl $today = time(); $user = $ARGV[0]; $command="awk -F: '/$user/ {print \$6}' /etc/passwd"; chomp ($user_dir = `$command`); if ( -f "$user_dir/.sh_history" ) { @file_dates = stat("$user_dir/.sh_history"); $sh_file_date = $file_dates[8]; } else { $sh_file_date = 0; } if ( -f "$user_dir/.bash_history" ) { @file_dates = stat("$user_dir/.bash_history"); $bash_file_date = $file_dates[8]; } else { $bash_file_date = 0; } if ( $sh_file_date > $bash_file_date ) { $file_date = $sh_file_date; } else { $file_date = $bash_file_date; } $difference = $today - $file_date; if ( $difference >= 3888000 ) { print "User needs to be disabled, 45 days old or older!\n"; exit (1); } else { print "OK\n"; exit (0); } 
 date --date='1 days ago' '+%a' 

Non è una soluzione molto compatibile. Funzionerà solo su Linux. Almeno, non ha funzionato in Aix e Solaris.

Funziona in RHEL:

 date --date='1 days ago' '+%Y%m%d' 20080807 

Guardando oltre, penso che tu possa semplicemente usare la data. Ho provato quanto segue su OpenBSD: ho preso la data del 29 febbraio 2008 e un’ora casuale (nella forma di 080229301535) e ho aggiunto +1 alla parte del giorno, in questo modo:

 $ date -j 0802301535 Sat Mar 1 15:35:00 EST 2008 

Come puoi vedere, la data ha formattato correttamente l’ora …

HTH

Se vuoi continuare con awk, allora le funzioni mktime e strftime sono utili:

 BEGIN { dateinit } { newdate=daysadd(OldDate,DaysToAdd)} # daynum: convert DD-MON-YYYY to day count #----------------------------------------- function daynum(date, d,m,y,i,n) { y=substr(date,8,4) m=gmonths[toupper(substr(date,4,3))] d=substr(date,1,2) return mktime(y" "m" "d" 12 00 00") } #numday: convert day count to DD-MON-YYYY #------------------------------------------- function numday(n, y,m,d) { m=toupper(substr(strftime("%B",n),1,3)) return strftime("%d-"m"-%Y",n) } # daysadd: add (or subtract) days from date (DD-MON-YYYY), return new date (DD-MON-YYYY) #------------------------------------------ function daysadd(date, days) { return numday(daynum(date)+(days*86400)) } #init variables for date calcs #----------------------------------------- function dateinit( x,y,z) { # Stuff for date calcs split("JAN:1,FEB:2,MAR:3,APR:4,MAY:5,JUN:6,JUL:7,AUG:8,SEP:9,OCT:10,NOV:11,DEC:12", z) for (x in z) { split(z[x],y,":") gmonths[y[1]]=y[2] } } 

Il libro “Shell Script Recipes: A Problem Solution Approach” (ISBN: 978-1-59059-471-1) di Chris FA Johnson ha una libreria di funzioni di date che potrebbe essere utile. Il codice sorgente è disponibile all’indirizzo http://apress.com/book/downloadfile/2146 (le funzioni di data sono in Chapter08 / data-funcs-sh all’interno del file tar).

Se la versione di data GNU funziona per te, perché non prendi il codice sorgente e lo compili su AIX e Solaris?

http://www.gnu.org/software/coreutils/

In ogni caso, la fonte dovrebbe aiutarti a ottenere l’aritmetica della data corretta se stai per scrivere il tuo codice personale.

Per inciso, commenti come “quella soluzione è buona, ma sicuramente puoi notare che non è buona come può essere. Sembra che nessuno abbia pensato ad armeggiare con le date durante la costruzione di Unix.” non ci portano davvero da nessuna parte Ho trovato ciascuno dei suggerimenti finora molto utile e mirato.

Ecco i miei due penny vale la pena – un wrapper di script che usa date e grep .

Esempio di utilizzo

 > sh ./datecalc.sh "2012-08-04 19:43:00" + 1s 2012-08-04 19:43:00 + 0d0h0m1s 2012-08-04 19:43:01 > sh ./datecalc.sh "2012-08-04 19:43:00" - 1s1m1h1d 2012-08-04 19:43:00 - 1d1h1m1s 2012-08-03 18:41:59 > sh ./datecalc.sh "2012-08-04 19:43:00" - 1d2d1h2h1m2m1s2sblahblah 2012-08-04 19:43:00 - 1d1h1m1s 2012-08-03 18:41:59 > sh ./datecalc.sh "2012-08-04 19:43:00" x 1d Bad operator :-( > sh ./datecalc.sh "2012-08-04 19:43:00" Missing arguments :-( > sh ./datecalc.sh gibberish + 1h date: invalid date `gibberish' Invalid date :-( 

copione

 #!/bin/sh # Usage: # # datecalc ""   # #  ::= see "man date", section "DATE STRING" #  ::= + | - #  ::= INTEGER | INTEGER #  ::= s | m | h | d if [ $# -lt 3 ]; then echo "Missing arguments :-(" exit; fi date=`eval "date -d \"$1\" +%s"` if [ -z $date ]; then echo "Invalid date :-(" exit; fi if ! ([ $2 == "-" ] || [ $2 == "+" ]); then echo "Bad operator :-(" exit; fi op=$2 minute=$[60] hour=$[$minute*$minute] day=$[24*$hour] s=`echo $3 | grep -oe '[0-9]*s' | grep -m 1 -oe '[0-9]*'` m=`echo $3 | grep -oe '[0-9]*m' | grep -m 1 -oe '[0-9]*'` h=`echo $3 | grep -oe '[0-9]*h' | grep -m 1 -oe '[0-9]*'` d=`echo $3 | grep -oe '[0-9]*d' | grep -m 1 -oe '[0-9]*'` if [ -z $s ]; then s=0; fi if [ -z $m ]; then m=0; fi if [ -z $h ]; then h=0; fi if [ -z $d ]; then d=0; fi ms=$[$m*$minute] hs=$[$h*$hour] ds=$[$d*$day] sum=$[$s+$ms+$hs+$ds] out=$[$date$op$sum] formattedout=`eval "date -d @$out +\"%Y-%m-%d %H:%M:%S\""` echo $1 $2 $d"d"$h"h"$m"m"$s"s" echo $formattedout 

Questo funziona per me:

 TZ=GMT+6; export TZ mes=`date --date='2 days ago' '+%m'` dia=`date --date='2 days ago' '+%d'` anio=`date --date='2 days ago' '+%Y'` hora=`date --date='2 days ago' '+%H'`