Validate date format in a shell script
$ date "+%Y%m%d" -d "20200204" > /dev/null 2>&1 ; echo $?
0
$ date "+%Y%m%d" -d "202002/04" > /dev/null 2>&1; echo $?
1
$ date "+%Y%m%d" -d "20200204a" > /dev/null 2>&1 ; echo $?
0
As you can see the invalid input datetime string "20200204a" passed the test.
Question> Is there a way that I can add more strict testing on this input?
Thank you
The +% format controls the output, not input.
Perl to the rescue!
perl -MTime::Piece -e '
$d = shift;
Time::Piece->strptime($d, "%Y%m%d")->strftime("%Y%m%d") eq $d
or exit 1
' "20210229" 2>/dev/null
In Python 3 you can do this :
echo "20200204" | python3 -c '
import sys
import datetime
try:
datetime.datetime.strptime(sys.stdin.read().rstrip(), "%Y%m%d")
except ValueError:
exit(1)'
It will return 1 when there are errors.
Related
I'm creating a list of directories with the requested date range in their name.
Directories are all labeled other_2019-07-18T00-00-00. The T is messing me up!
Copied this loop from somewhere.
#!/bin/bash
curdate=$(date +%Y-%m-%d%H-%M-%S)
#
for o in other_*; do
tmp=${o##other_}
tmp=$(echo "$tmp" | sed 's/T//') # clean up prefixes
fdate=$(date -d "${tmp}")
(( curdate < fdate )) && echo "$o"
done
I expect the final echo to include the path of all dir that match.
Unlike AWK, Bash comparison operator < works only on numerical values.
Please try instead:
#!/bin/bash
curdate=$(date +%Y%m%d%H%M%S)
for o in other_*; do
tmp=${o##other_}
fdate=$(echo "$tmp" | sed 's/[-T]//g') # numeralization
(( curdate < fdate )) && echo "$o"
done
As an alternative, you can compare epoch times:
#!/bin/bash
curdate=$(date +%s)
for o in other_*; do
tmp=${o##other_}
tmp=$(echo "$tmp" | sed 's/T/ /' | sed 's/\([0-9][0-9]\)-\([0-9][0-9]\)-\([0-9][0-9]\)$/\1:\2:\3/')
fdate=$(date -d "$tmp" +%s)
(( curdate < fdate )) && echo "$o"
done
Hope this helps.
Instead of dropping T...
date -d 2019-03-23T00-06-28
date: invalid date '2019-03-23T00-06-28'
ok, but:
date -d 2019-03-23T00:06:28
Sat Mar 23 00:06:28 UTC 2019
So we have to replace last two dashes by ::
As your question is tagged bash:
file="somepath/other_2019-07-18T00-00-00.extension"
time=${file#*other_} # suppress from left until 'other_'
time=${time%.*} # suppress extension
time=${time//-/:} # replace all dash by a `:`
time=${time/:/-} # replace 1nd `:` by a dash
time=${time/:/-} # replace 1nd `:` by a dash (again)
date -d $time
Thu Jul 18 00:00:00 UTC 2019
This could by written:
printf -v now "%(%s)T" -1 # bashism for current date to variable $now
for file in somepath/other_*.ext ;do
time=${file#*other_} time=${time%.*} time=${time//-/:}
time=${time/:/-} time=${time/:/-}
read fdate < <(date +%s -d $time)
((fdate > now)) && { echo $file: $((fdate - now)) ; }
done
Reducing forks (to date) improve quickness:
for matching your sample, you could replace for file in somepath/other_*.ext ;do by for file in other_*; do. This must work quite same.
fifo=/tmp/fifo-date-$$
mkfifo $fifo
exec 5> >(exec stdbuf -o0 date -f - +%s >$fifo 2>&1)
echo now 1>&5
exec 6< $fifo
read -t 1 -u 6 now
rm $fifo
for file in otherdates/*.ext ; do
time=${file#*other_} time=${time%.*} time=${time//-/:}
time=${time/:/-} time=${time/:/-}
echo $time 1>&5 && read -t 1 -u 6 fdate
((fdate > now)) && {
echo $file: $((fdate - now))
}
done
exec 6>&-
exec 5>&-
In this, I run date +%s in background, with -f argument, date will interpret each incoming line, then answer UNIX_TIME. So $now is first read from date process, by:
echo now >&5 && # now, the string
read -u 6 now # read will populate `$now` variable
Nota, once fifo opened by both input and output, they could be deleted. It will remain for process until process close them.
there is no space between day and hour, which causes date not to be able to read the date. Try:
sed 's/T/ /'
I have a string '2015_11_01_00_00' I want this convert into epoch time.
I have tried the below code, but could not found it worth.
#!/bin/bash
echo "mohit"
date_var="2015_11_01_00_00"
echo $date_var
arr=$(echo $date_var | tr "_" "\n")
IFS='_ ' read -r -a arr <<< "$date_var"
#echo $arr
echo ${arr[0]}
echo ${arr[1]}
echo ${arr[2]}
echo ${arr[3]}
echo ${arr[4]}
updated_date="${arr[0]}"-"${arr[1]}"-"${arr[2]}"-"${arr[3]}"-"${arr[4]}"
echo $updated_date
TMRW_DATE=`date --date="$updated_date" +"%s"`
echo "$TMRW_DATE"
This one should do the trick:
#!/bin/bash
echo "mohit"
date_var="2015_11_01_00_00"
updated_date=`echo ${date_var} | sed -e 's,_,/,;s,_,/,;s,_, ,;s/_/:/'`
echo ${updated_date}
TMRW_DATE=`date -d "$updated_date" +"%s"`
echo "$TMRW_DATE"
Here is one way to do it, using GNU date syntax:
#!/bin/bash
echo "mohit"
date_var="2015_11_01_00_00"
TMRW_DATE=$(
set -- $(echo $date_var | tr "_" " ")
date --date="$1-$2-$3 $4:$5" +"%s"
)
echo "$TMRW_DATE"
and this should work with OS X date implementation :
#!/bin/bash
echo "mohit"
date_var="2015_11_01_00_00"
TMRW_DATE=$(date -j -f "%Y_%m_%d_%H_%M" "$date_var" +%s)
echo "$TMRW_DATE"
i was trying to do the same for postgres sql by following which produce error
SELECT *, DATEDIFF(ss, '1970-01-01T00:00:00.000', punch_time) as EpochSeconds
FROM attendance
but following code did work for me
SELECT EXTRACT(EPOCH FROM punch_time) as EpochTime
FROM attendance;
I'm trying to do a script which downloads a file and if it takes less than 'x' seconds it's a pass but from some reason I can't get it to work properly, i've tried '<=' '-lt' in the below example it's always 'FAST' i'e 2.09 FAST
#!/bin/bash
file=&(time -p wget -O /dev/null -q http://site/file.iso ) 2>&1 | grep real | sed -e s/real//g -e s/' '//g
if [ $file <= 1 ]
then
echo "FAST"
else
echo "SLOW"
fi
The bash builtin time outputs directly to your terminal, not to a stdio channel.
You'll need to use /bin/time which uses stderr:
$ time -p sleep 1 >/dev/null 2>&1
real 1.00
user 0.00
sys 0.00
$ /bin/time -p sleep 1
real 1.00
user 0.00
sys 0.00
$ /bin/time -p sleep 1 2>/dev/null
$
So:
$ command time -p sleep 1 2>&1 | awk -v limit=0.5 '$1 == "real" {exit ($2 <= limit)}'
$ echo $?
0
$ command time -p sleep 1 2>&1 | awk -v limit=1.5 '$1 == "real" {exit ($2 <= limit)}'
$ echo $?
1
and then
limit=1 # 1 second
if command time -p wget -O /dev/null -q http://site/file.iso |
awk -v lmt=$limit '$1 == "real" {exit ($2 <= lmt)}'
then
echo "FAST"
else
echo "SLOW"
fi
To capture the output of time you need curly braces:
file=$( { time -p wget -O /dev/null -q http://site/file.iso ; } 2>&1 | grep real | sed -e s/real//g -e s/' '//g )
Bash can't do floating-point calculations, so you can use bc for that:
[[ $( echo $file' <= 1.0' | bc ) == 1 ]] && echo FAST || echo SLOW
In the process of writing a bash script to parse a tab-separated file and unfortunately I need the ask the user for a date outside of the contents/creation of the file. I've gotten everything working, except looping through badly entered dates from the user until they enter one matching the desired format.
My debug code-block is as follows...
Code:
#!/bin/bash
USER_INPUT="01-01-2011" # ARBITRARILY ASSIGNING A BAD DATE BECAUSE I'M TOO LAZY TO TYPE ONE IN EACH TIME
EXPERIMENTDATE="$USER_INPUT"
if [[ $OSTYPE == *"linux"* ]]
then
date -d \"$EXPERIMENTDATE\" +%Y-%m-%d > /dev/null 2>&1
else
date -j -f \"%Y-%m-%d\" \"$EXPERIMENTDATE\" +%Y-%m-%d > /dev/null 2>&1
fi
is_valid="$?"
echo -e "$is_valid"
# FYI - $? RETURNS A BINARY FLAG ON THE LAST COMMAND'S EXECUTION. 1 IF ERROR, 0 IF NORMAL EXIT
while [ $is_valid -ne 0 ]; do
echo -e "Invalid date entered. Please enter the day the experiement was conducted on, in exactly the following format. YYYY-MM-DD (e.g. 2011-04-22)"
read USER_INPUT
EXPERIMENTDATE=$USER_INPUT
echo -e "You entered $EXPERIMENTDATE"
if [[ $OSTYPE == *"linux"* ]]
then
echo -e "DEBUG: date -d \"$EXPERIMENTDATE\" +%Y-%m-%d > /dev/null 2>&1"
date -d \"$EXPERIMENTDATE\" +%Y-%m-%d > /dev/null 2>&1
else
echo -e "DEBUG: date -j -f \"%Y-%m-%d\" \"$EXPERIMENTDATE\" +%Y-%m-%d > /dev/null 2>&1"
date -j -f \"%Y-%m-%d\" \"$EXPERIMENTDATE\" +%Y-%m-%d > /dev/null 2>&1
fi
is_valid="$?"
echo -e "DEBUG: $is_valid"
done
echo -e "You entered $EXPERIMENTDATE"
From the above, none of the date commands seem to evaluate correctly within the if's, but executing the debug commands directly on the CLI work.
I'm sure this is going to be a quoting/back-tic deal, but I can't seem to figure it out.
You don't need to escape the quotes that aren't inside similar quotes. For example, not
date -j -f \"%Y-%m-%d\" \"$EXPERIMENTDATE\" +%Y-%m-%d > /dev/null 2>&1
but
date -j -f "%Y-%m-%d" "$EXPERIMENTDATE" +%Y-%m-%d > /dev/null 2>&1
I’m trying to validate input by using egrep and regex.Here is the line from script (c-shell):
echo $1 | egrep '^[0-9]+$'
if ($status == 0) then
set numvar = $1
else
echo "Invalid input"
exit 1
endif
If I pipe echo to egrep it works, but it also prints the variable on the screen, and this is something I don't need.
To simply suppress output you can redirect it to the null device.
echo $1 | egrep '^[0-9]+$' >/dev/null
if ($status == 0) then
set numvar = $1
else
echo "Invalid input"
exit 1
endif
You might also want to consider using the -c option to get the count of matches instead of using using the status.
Also, unless you are using csh, the status is stored in $? not in $status
grep has a -q option that suppresses output
So:
egrep -q '^[0-9]+$'
you can use awk
$ echo "1234" | awk '{print $1+0==$1?"ok":"not ok"}'
ok
$ echo "123d4" | awk '{print $1+0==$1?"ok":"not ok"}'
not ok