Арифметика дат в сценариях оболочки Unix

Быстрое решение:

  1. добавить код в основную функцию:
    String log4jConfPath = "/path/to/log4j.properties";
    PropertyConfigurator.configure(log4jConfPath);
    
  2. создать файл с именем log4j.properties в / path / to
    log4j.rootLogger=INFO, stdout
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.Target=System.out
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{2}: %m%n
    
19
задан ggasp 8 August 2008 в 23:37
поделиться

10 ответов

Я написал сценарий bash для преобразования дат, выраженных на английском языке, в обычные мм / дд / гггг даты. Он называется ComputeDate .

Вот несколько примеров его использования. Для краткости я поместил вывод каждого вызова в той же строке, что и вызов, через двоеточие (:). Цитаты, показанные ниже, не необходимы при запуске 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

Я включил этот сценарий как ответ на эту проблему, потому что он показывает, как выполнять арифметические операции с датами с помощью набора функций bash, и эти функции могут оказаться полезными для других. Он правильно обрабатывает високосные годы и високосные века:

#! /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
      [a-z]* )
         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 `
         ;;
      [a-z]* ) # 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/[a-z]/ /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/[a-z]/ /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 A-Z a-z `
   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`
         ;;
      [a-z]* ) # 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 A-Z a-z`

Date="$Month/$Day/$Year"
ComputeDate $Date $*

Этот сценарий широко использует другой сценарий, который я написал (названный col ... многие извинения тем, кто использует стандартный col , предоставленный с Linux). Эта версия col упрощает извлечение столбцов из стандартного ввода. Таким образом,

$ echo a b c d e | col 5 3 2

печатает

e c b

Вот сценарий col :

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

Он также использует printdirections для печати направлений, когда сценарий вызывается неправильно:

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

Чтобы использовать это поместит три сценария в файлы ComputeDate , col и printdirections соответственно. Поместите файл в каталог, названный вашим PATH, обычно ~ / bin. Затем сделайте их исполняемыми с помощью:

$ chmod a+x ComputeDate col printdirections

Проблемы? Пришлите мне письмо: Morell AT cs.atu.edu Поместите ComputeDate в тему.

15
ответ дан 30 November 2019 в 01:44
поделиться

Если версия GNU даты работает на Вас, почему Вы не захватываете источник и компилируете его на AIX и Солярисе?

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

В любом случае, источник должен помочь Вам получить арифметику дат, корректную, если Вы собираетесь записать владение кодом.

Как в стороне, комментирует как, "что решение хорошо, но конечно можно отметить, что это не столь хорошо, как может быть. Это никем не кажется мысль о переделывании дат при построении Unix". действительно не получайте нас нигде. Я нашел, что каждое из предложений до сих пор было очень полезно и на цели.

0
ответ дан 30 November 2019 в 01:44
поделиться

Книга "Рецепты Сценария оболочки: Подход Решения задач" (ISBN: 978-1-59059-471-1) Chris F.A. Johnson имеет библиотеку функций даты, которая могла бы быть полезной. Исходный код доступен в http://apress.com/book/downloadfile/2146 (функции даты находятся в Chapter08/data-funcs-sh в файле tar).

3
ответ дан 30 November 2019 в 01:44
поделиться
date --date='1 days ago' '+%a'

Это не очень совместимое решение. Это будет работать только в Linux. По крайней мере, это не сделало работавший в Экс-ан-Провансе и Солярис.

Это работает в RHEL:

date --date='1 days ago' '+%Y%m%d'
20080807
4
ответ дан 30 November 2019 в 01:44
поделиться

Изучая его далее, я думаю, что можно просто использовать дату. Я попробовал следующий OpenBSD: Я взял дату от 29-го февраля 2008 и случайный час (в форме 080229301535) и добавил +1 к дневной части, как так:

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

, Как Вы видите, дата отформатировала время правильно...

HTH

3
ответ дан 30 November 2019 в 01:44
поделиться

Если Вы хотите продолжить awk, то mktime и функции strftime полезны:


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]
     }
}
3
ответ дан 30 November 2019 в 01:44
поделиться

Почему бы не записать Ваши сценарии с помощью языка как жемчуг или Python вместо этого, который более естественно поддерживает сложную обработку даты? Уверенный Вы можете делать все это в ударе, но я думаю, что Вы также получите больше непротиворечивости через платформы с помощью Python, например, пока можно удостовериться, что жемчуг или Python установлены.

я должен добавить, что довольно легко соединить проводом в Python и сценариях жемчуга в содержание сценария оболочки.

4
ответ дан 30 November 2019 в 01:44
поделиться

Принятие Вас имеет дата GNU , как так:

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

И подобные фразы .

71
ответ дан 30 November 2019 в 01:44
поделиться

Я врезался в это пару раз. Мои мысли:

  1. Арифметика дат всегда является болью
  2. , немного легче, когда использование даты формата
  3. Опорной даты на Linux преобразовывает в ЭПОХУ, но не на Солярисе
  4. Для портативного решения, необходимо выполнить одно из следующих действий:
    1. дата гну Установки на solaris (уже упомянутый, человеческое взаимодействие потребностей для завершения)
    2. жемчуг Использования для части даты ( большинство , установки Unix включают жемчуг, таким образом, я обычно предполагал бы, что это действие делает не , требуют дополнительной работы).

демонстрационный сценарий А (проверяет на возраст определенных пользовательских файлов, чтобы видеть, может ли учетная запись быть удалена):

#!/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);
}
4
ответ дан 30 November 2019 в 01:44
поделиться

Чтобы сделать арифметику с датами на UNIX, Вы получаете дату как число секунды с эпохи UNIX, сделайте некоторое вычисление, затем преобразуйте назад в свой печатаемый формат даты. Команда даты должна смочь и дать Вам секунды с эпохи и преобразовать из того числа назад на печатаемую дату. Моя локальная команда даты делает это,

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

Видит Ваше локальное date(1) страница справочника. Для постепенного увеличения дня добавляют 86 400 секунд.

6
ответ дан 30 November 2019 в 01:44
поделиться
Другие вопросы по тегам:

Похожие вопросы: