Bash - optionally set env var in command substitution - bash

The following works without issue:
timestamp=$(TZ=America/New_York date)
echo $timestamp
which (if saved in a file called /tmp/foo) results in:
$ /tmp/foo
Thu Dec 23 21:03:41 EST 2021
This code also works:
timezone=$1
timestamp=$(TZ=$timezone date)
echo "$timestamp"
well...sort of; when run with an argument, it does what I want:
$ date
Thu Dec 23 21:05:03 EST 2021
$ /tmp/foo Asia/Calcutta
Fri Dec 24 07:35:11 IST 2021
but when run without an argument, it reverts to UTC (because TZ becomes set to an empty value, which executes different code than if TZ is just not set at all):
$ /tmp/foo Asia/Calcutta
Fri Dec 24 07:37:16 IST 2021
$ /tmp/foo
Fri Dec 24 02:07:19 UTC 2021
So I should only set the TZ variable in the subshell if it's provided, right? This does not work:
timezone=$1
if [[ -n "$timezone" ]]; then
tzstring="TZ=$timezone"
fi
timestamp=$($tzstring date)
echo "$timestamp"
Without an argument, it's fine:
$ /tmp/foo
Thu Dec 23 21:09:07 EST 2021
but with an argument, it fails:
$ /tmp/foo Asia/Calcutta
/tmp/foo: line 12: TZ=Asia/Calcutta: No such file or directory
It's trying to execute that first element.
I can get it to work exactly as I'd like to with this code:
if [[ -n "$timezone" ]]; then
timestamp=$(TZ=$timezone date)
else
timestamp=$(date)
fi
which results in:
$ /tmp/foo
Thu Dec 23 21:13:19 EST 2021
$ /tmp/foo Asia/Calcutta
Fri Dec 24 07:43:21 IST 2021
but surely there's a better way to do this that reduces the code duplication. I'd rather not use a function, but if I didn't have that stipulation, I could maybe make TZ local. The only way I can think of to do all of what I want involves using eval, which I'm not really willing to do (and I'm not really sure I know how to do it even then).

Set the timezone only when there's a parameter. You can do that by exporting $TZ inside the subshell whose output you're capturing.
timestamp=$(if [ "$1" ] ; then export TZ=$1 ; fi; date)
echo $timestamp

Related

How to compare dates formatted as `Tue Aug 30 12:01:37 GMT 2022` in BusyBox/Alpine

In shell (no bash because of Alpine) using BusyBox, how can I compare two dates both formatted as Tue Aug 30 12:01:37 GMT 2022?
I want to know which one comes first. date doesn't support this input format. I'm only interested in whole days. The time isn't interesting for me. So two dates on the same day but a different time are equal to me.
Of course I could put all the names of the months in a lookup table and use the index of the month as its integer value (to be able to compare) but I have the feeling I shouldn't be the one programming that out...
Update:
/opt/scripts $ a="Tue Aug 30 12:01:37 GMT 2022"
/opt/scripts $ date -d "$a" +%s
date: invalid date 'Tue Aug 30 12:01:37 GMT 2022'
/opt/scripts $ date --help
BusyBox v1.34.1 (2022-04-04 10:19:27 UTC) multi-call binary.
Usage: date [OPTIONS] [+FMT] [[-s] TIME]
Display time (using +FMT), or set time
-u Work in UTC (don't convert to local time)
[-s] TIME Set time to TIME
-d TIME Display TIME, not 'now'
-D FMT FMT (strptime format) for -s/-d TIME conversion
-r FILE Display last modification time of FILE
-R Output RFC-2822 date
-I[SPEC] Output ISO-8601 date
SPEC=date (default), hours, minutes, seconds or ns
Recognized TIME formats:
#seconds_since_1970
hh:mm[:ss]
[YYYY.]MM.DD-hh:mm[:ss]
YYYY-MM-DD hh:mm[:ss]
[[[[[YY]YY]MM]DD]hh]mm[.ss]
'date TIME' form accepts MMDDhhmm[[YY]YY][.ss] instead
/opt/scripts $
Install dateutils https://pkgs.alpinelinux.org/package/edge/community/x86/dateutils . Use strptime to convert the date to seconds. Compare seconds.
apk add dateutils
a=$(strptime -f %s -i "%a %b %d %T %Z %Y" "Tue Aug 30 12:01:37 GMT 2022")
b=$(strptime -f %s -i "%a %b %d %T %Z %Y" "Tue Aug 30 12:01:38 GMT 2022")
[ "$a" -lt "$b")
You may have to rely on awk:
/ # cat /etc/alpine-release
3.16.0
/ # echo $a
Tue Aug 30 12:01:37 GMT 2022
/ # TZ=GMT awk -v a="$a" 'BEGIN {
> split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec", months)
> split(a, date)
> gsub(/:/, " ", date[4])
>
> for (i=1; i<=12; i++) {
> if (date[2] == months[i]) {
> timestamp = date[6] " " i " " date[3] " " date[4]
> print mktime(timestamp)
> exit
> }
> }
>
> print "hmm, " date[2] " is an unknown month"
> exit 1
> }'
1661860897
Ok, my alpine busybox copy of date doesn't recognize strings as month either.
You want "slick", stick with Glenn's awk solution, so long as the time functions work for you. I hacked out the least-slick kluge using just echo, date, read, if's, and a lot of tempfiles - it's an ugly mess, but it works, and it was a fun exercise in using only the most basic stuff.
/tmp $ ./script
#! /bin/sh
cat "$0"
cd /tmp
echo "01">Jan
echo "02">Feb
echo "03">Mar
echo "04">Apr
echo "05">May
echo "06">Jun
echo "07">Jul
echo "08">Aug
echo "09">Sep
echo "10">Oct
echo "11">Nov
echo "12">Dec
echo "Tue Aug 30 12:01:37 GMT 2022">a_raw
read -r a_raw<a_raw
echo "Fri Jun 3 09:26:55 CDT 2022">b_raw
read -r b_raw<b_raw
read -r _ Mon DD tim z YYYY<a_raw
read -r MM<"$Mon"
date -d "$YYYY-$MM-$DD" +"%s">a_epoch
read -r a_epoch<a_epoch
read -r _ Mon DD tim z YYYY<b_raw
read -r MM<"$Mon"
date -d "$YYYY-$MM-$DD" +"%s">b_epoch
read -r b_epoch<b_epoch
if [ "$a_epoch" -lt "$b_epoch" ]
then echo "$a_raw ($a_epoch) is before $b_raw ($b_epoch)"
else if [ "$a_epoch" -gt "$b_epoch" ]
then echo "$a_raw ($a_epoch) is after $b_raw ($b_epoch)"
else if [ "$a_epoch" -eq "$b_epoch" ]
then echo "$a_raw ($a_epoch) is same as $b_raw ($b_epoch)"
fi
fi
fi
rm Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec a_raw b_raw a_epoch b_epoch
Tue Aug 30 12:01:37 GMT 2022 (1661817600) is after Fri Jun 3 09:26:55 CDT 2022 (1654214400)
original
What do you mean "date doesn't support this input format"?
Something like this ought to work in sh, though I confess I don't have an alpine handy...
a="Tue Aug 30 12:01:37 GMT 2022"
b="Fri Jun 3 09:26:55 CDT 2022"
a_epoch=`date -d "$a" +%s`
b_epoch=`date -d "$b" +%s`
echo "A: [$a] ($a_epoch)"
echo "B: [$b] ($b_epoch)"
if [ "$a_epoch" -lt "$b_epoch" ]; then echo "$a is before $b"; fi
if [ "$a_epoch" -gt "$b_epoch" ]; then echo "$a is after $b"; fi
if [ "$a_epoch" -eq "$b_epoch" ]; then echo "$a is same as $b"; fi
Should say something like
A: [Tue Aug 30 12:01:37 GMT 2022] (1661860897)
B: [Fri Jun 3 09:26:55 CDT 2022] (1654266415)
Tue Aug 30 12:01:37 GMT 2022 is after Fri Jun 3 09:26:55 CDT 2022
There are cleaner ways, but this should get you started.
Lemme spin up a container and try there, brb...

How can I change command and option about 'date' command in bash?

I want to convert this bash command to shell script.
BASH
Input:
date --date="Wed Aug 25 22:37:44 +0900 2021" +"%s"
Output:
1629898664
SHELL
tmp.sh:
function time(a, b, c, d, e) { return date --date="a b c d +0900 e" +"%s" }
{print time($1, $2, $3, $4, $5}
timeline:
Wed Aug 25 22:37:44 2021
Command:
awk -f tmp.sh timeline
Output:
awk: tmp.sh:1: function cvtTime(w) { return date --date="Thu May 14 23:40:52 +0900 2020" +"%s" }
awk: tmp.sh:1: ^ syntax error
What about timeline file has multiple lines? Like:
Wed Aug 25 22:37:44 2021 JACK
Wed Aug 26 22:37:44 2021 EMILY
Wed Aug 27 22:37:44 2021 SAM
I tried:
#!/bin/bash
while read -r line; do
date --date="${1} ${2} ${3} ${4} +0900 ${5}" +"%s"
done
Want:
1629898664 JACK
1629985064 EMILY
1630071464 SAM
But it doesn't work :(
It seems that you want a shell script that is invoked with five command line parameters:
A weekday (in a three-letter format)
A month (in a three-letter format)
Day-of-month
A time expression (HH:MM:SS)
A year (four digits)
(Note that 1. is redundant, it is implied by 2., 3., and 5.)
Hence a somewhat minimal shell script would look sth. like this:
#!/bin/bash
date --date="${1} ${2} ${3} ${4} +0900 ${5}" +"%s"
Of course, this can be greatly improved, e.g., by adding sanity checks for the passed parameters.
In case you want to store the date information in a file so that you can pass a single filename parameter to the script instead (allowing for multiple such lines), the following variation will do:
#!/bin/bash
while read -a i; do
echo $(date --date="${i[0]} ${i[1]} ${i[2]} ${i[3]} +0900 ${i[4]}" +"%s") ${i[5]}
done < ${1}
Note, however, that this version expects an additional name parameter after the date information in each line.
In any event, no need for awk here.

How to pass the result of a function as argument into a Bash function?

I have this function:
test() {
echo "$1"
}
$1 can receive an argument. This works:
test "i am here"
i am here
Now I want to receive the result of date.
date
Tue Jan 10 10:36:10 CST 2017
test `date`
Tue
How to make Jan 10 10:36:10 CST 2017 not be omitted?
You need to enclose the result of date in double quotes for the entire date string to be sent as a single argument to your function:
test "`date`"
or, more preferably:
test "$(date)"
Here is an example:
$ test "$(date)"
Tue Jan 10 03:17:26 UTC 2017

I want to convert 18-Aug-2015 date format to '2015-08-18' using shell script

I want to convert 18-Aug-2015 date format to '2015-08-18' using shell script
Try this formatting:
$ date +"%Y-%m-%d"
http://www.cyberciti.biz/faq/linux-unix-formatting-dates-for-display/
The -d option is GNU specific.
Here, you don't need to do date calculation, just rewrite the string which already contains all the information:
a=$(printf '%s\n' "$Prev_date" | awk '{
printf "%04d-%02d-%02d\n", $6, \
(index("JanFebMarAprMayJunJulAugSepOctNovDec",$2)+2)/3,$3}')
Without awk, assuming your initial date is in $mydate:
IFS=- d=($mydate)
months=(Zer Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
z=1
while [[ ${months[$z]} != ${d[1]} ]]; do z=$((z+1)); done
printf "%s-%02d-%s\n" ${d[2]} $z ${d[0]}

Bash script/command to print out date 5 min before/after

I need to somehow use the date command in bash or another utility to print out the date and time, 5 minutes before and 5 minutes after a given value.
For example:
input:
Thu Dec 19 14:10
output:
Thu Dec 19 14:05 Thu Dec 19 14:10 Thu Dec 19 14:15
I see that the date command can be used to do this on the current date/time, can it be used with a passed value, i.e. not the current date/time?
You can achieve this, for the current time, by typing.
$ date --date='5 minutes ago'; date; date --date='5 minutes'
Qui Dez 19 16:09:17 BRST 2013
Qui Dez 19 16:14:17 BRST 2013
Qui Dez 19 16:19:17 BRST 2013
To use a specific date (ex 1978/01/10).
$ date --date='1978-01-10 + 5 minutes'
Ter Jan 10 00:05:00 BRT 1978
With GNU date, you can do a simple form of date/time arithmetic with the argument to the --date option:
$ date --date 'now + 5 minutes'
With BSD date (at least, the version that ships with Mac OS X), the -v option allows you to do something similar:
$ date -v +5M
$ date -v -5M
If you're using bash under linux, you can use the -d parameter to perform date manipulation on an existing date:
Get the EPOCH time for the date in question:
EPOCH=$(date -d 'Thu Dec 19 14:10' '+%s')
This gives you the time, in seconds, since the EPOCH (typically 01/01/1970)
Now you can use simple math to subtract or add 5 minutes (in seconds) to the EPOCH time
NEW_EPOCH=$(($EPOCH - 300))
obviously, there are 300 seconds in 5 minutes
Now convert this NEW_EPOCH back into a human readable date
NEW_DATE=$(date -d "1970-01-01 ${NEW_EPOCH} sec")
NOTE that this only works on unix systems which support the date -d option (i.e. Linux)
If you want to do this for the current time +/-5 minutes and you use Bash 4.2 or newer, you can do it without external tools:
$ printf -v now '%(%s)T'
$ echo "$now"
1516161094
$ f='%a %b %d %R'
$ printf "%($f)T %($f)T %($f)T\n" "$((now-300))" "$now" "$((now+300))"
Tue Jan 16 22:46 Tue Jan 16 22:51 Tue Jan 16 22:56
The %(datefmt)T formatting string of printf allows to print date-time strings. If the argument is skipped (like here) or is -1, the current time is used.
%s formats the time in seconds since the epoch, and -v now stores the output in now instead of printing it.
f is just a convenience variable so I don't have to repeat the formatting string for the output three times.
Since the argument for this usage of printf has to be in seconds since the epoch, you're stuck with external tools to convert an input string like Thu Dec 19 14:10 into that format and you'd replace
printf -v now '%(%s)T'
with, for example, any of
now=$(date '+%s' -d 'Thu Dec 19 14:10') # GNU date
now=$(date -j -f '%a %b %d %T' 'Thu Dec 19 14:10' '+%s') # macOS date

Resources