Date command as a variable in a bash script. Needs to be invoked each time instead of during variable declaration - linux

I have a bash script and at certain points I am using echo to put some messages in a log file. The problem that I have is related to the DATE variable which will be static throughout the entire execution of the script.
I have this basic script below to illustrate the problem:
#!/bin/bash
DATE=`date +"%Y-%m-%dT%H:%M:%S%:z"`
echo "script started at $DATE"
echo "doing something"
sleep 2
echo "script finished at $DATE"
If I execute this script, the output of the $DATE variable is the same in both lines. Is there some bash magic that could nicely resolve this without having to replace $DATE with the command itself on each line?
Thanks in advance

Newer versions of the bash/printf builtin have support for generating datetime stamps without the need to spawn a subprocess to call date:
$ builtin printf --help
...snip...
Options:
-v var assign the output to shell variable VAR rather than
display it on the standard output
...snip...
In addition to the standard format specifications described in printf(1),
printf interprets:
%b expand backslash escape sequences in the corresponding argument
%q quote the argument in a way that can be reused as shell input
%(fmt)T output the date-time string resulting from using FMT as a format
string for strftime(3)
... snip ...
Instead of spawning a subprocess to call date, eg:
logdt=`date +"%Y-%m-%dT%H:%M:%S:%z"`
The same can be accomplished via printf -v by wrapping the desired format in %(...)T, eg:
printf -v logdt '%(%Y-%m-%dT%H:%M:%S:%z)T'
NOTE: assuming %:z should be :%z
Assuming you'll be tagging a lot of lines with datetime stamps then the savings from eliminating the subproces date calls could be huge.
Running a test of 1000 datetime stamp generations:
$ time for ((i=1;i<=1000;i++)); do { printf -v logdt '%(...)T' | logdate=$(date ...) }; done
Timings for printf -v logdt '%(...)T':
real 0m0.182s # ~130 times faster than $(date ...)
user 0m0.171s
sys 0m0.000s
Timings for logdt=$(date ...):
real 0m24.443s # ~130 times slower than printf -v
user 0m5.533s
sys 0m16.724s

With bash version 4.3+ , you can use the builtin printf to format datetimes. -1 below is a magic value that means "now".
#!/bin/bash
datefmt='%Y-%m-%dT%H:%M:%S%z'
printf "script started at %($datefmt)T\n" -1
echo "doing something"
sleep 2
printf "script finished at %($datefmt)T\n" -1
bash didn't recognize %:z for me.

This can help you:
#!/bin/bash
echo "script started at $(date +'%Y-%m-%dT%H:%M:%S%:z')"
echo "doing something"
sleep 2
echo "script finished at $(date +'%Y-%m-%dT%H:%M:%S%:z')"
You might want to create an alias if calling the full command looks clumsy to you.

Related

How to print a success message after the complete execution of a script?

#! /bin/sh
DB_USER='aaa';
DB_PASSWD='aaa1';
DB_NAME='data';
TABLE='datalog';
mysql --local-infile=1 --user=$DB_USER --password=$DB_PASSWD $DB_NAME -e "load data local infile '/home/demo/data1.csv' into table datalog fields terminated by ',' lines terminated by '\n';" -e echo "script executed successfully " | date "+%h %e"
My aim is to print a success message after the above script executes successfully. I have written the above command to do so but it is printing the date, not the echo statement.
The arguments to mysql -e should be SQL commands, not shell script.
The solution is much simpler than what you are trying.
#!/bin/sh
# Terminate immediately if a command fails
set -e
# Don't put useless semicolons at the end of each assignment
# Use lower case for your private variables
db_user='aaa'
db_passwd='aaa1'
db_name='data'
table='datalog'
# Quote strings
mysql --local-infile=1 --user="$db_user" --password="$db_passwd" "$db_name" \
-e "load data local infile '/home/demo/data1.csv' into table $table fields terminated by ',' lines terminated by '\n';"
# Just use date
# Print diagnostics to standard error, not standard output
date "+%h %e script executed successfully" >&2
The use of set -e is somewhat cumbersome, but looks like the simplest solution to your basic script. For a more complex script, maybe instead use something like
mysql -e "... stuff ..." || exit
to terminate on failure of this individual command, but allow other failures in the script. Perhaps see also What does set -e mean in a bash script?
If you want to preserve the exit code from mysql and always print a message to show what happened, probably take out the set -e and do something like
if mysql -e "... whatever ..."
then
date "+%h %e script completed successfully" >&2
else
rc=$?
date "+%h %e script failed: $rc" >&2
exit $rc
fi
As an aside, date does not read its standard input for anything, so you can't pipe echo to it. The script above simply uses only date, but here are a few different ways you could solve that.
# Merge this output with the next output
date "+%h %e" | tr '\n' ' ' >&2
echo script executed successfully >&2
or
# Use a command substitution to interpolate the output from date into
# the arguments for echo
echo "$(date "+%h %e") script executed successfully >&2
For more complex situations, maybe also look into xargs, though it's absolutely horribly overkill here.
date "+%h %e" | xargs -I {} echo script executed successfully {} >&2
If you use only date, you will need to be mindful of your use of % in any literal message; to print a literal per-cent sign from date, use %%.
As a further stylistic aside, you should avoid upper case for your private variables.

How to store elapsed time of a variable assignment to another variable in bash script?

I wanted to run a maven command and store the console output to a variable and in turn, store the real time of the mentioned operation in another variable. I wrote the following command -
x1=`( time t1=$( mvn test -Drat.skip)) 2>&1 | grep real`
When I echo variable x1 I get 0m17.430s which is the desired output but when I echo variable t1 it prints nothing! How can I store the console output of mvn test -Drat.skip in t1?
Everything inside of () or backticks happens in a subshell. Variable values aren't exported from a subshell back to the parent shell.
You can assign both the output of the command and output of time into a variable and then extract it from there:
#!/bin/bash
all=$((time mvn test -Drat.skip )2>&1)
time=$(tail -n3 <<< "$all" | grep real)
output=$(head -n-3 <<< "$all")
As #choroba said t1 is created in different subshell and can't be exported back.
You cat test it like this:
t1=test
x1=`(time t1=$(echo ok); echo $t1) 2>&1`
echo $t1
echo $x1
The output will be:
$ echo $t1
test
$ echo $x1
real 0m0,001s user 0m0,001s sys 0m0,001s ok
But this litle hack may help
fun () { t1=$(mvn test -Drat.skip); }
x1=$((time fun) 2>&1 | grep real)

How to output the start and stop datetime of shell script (but no other log)?

I am still very new to shell scripting (bash)...but I have written my first one and it is running as expected.
What I am currently doing is writing to the log with sh name-of-script.sh >> /cron.log 2>&1. However this writes everything out. It was great for debugging but now I don't need that.
I now only want to see the start date and time along with the end date and time
I would still like to write to cron.log but just the dates as mentioned above But I can't seem to figure out how to do that. Can someone point me in the right direction to do this...either from within the script or similar to what I've done above?
A simple approach would be to add something like:
echo `date`: Myscript starts
to the top of your script and
echo `date`: Myscript ends
to the bottom and
echo `date`: Myscript exited because ...
wherever it exits with an error.
The backticks around date (not normal quotes) cause the output of the date command to be interpolated into the echo statement.
You could wrap this in functions and so forth to make it neater, or use date -u to print in UTC, but this should get you going.
You ask in the comments how you would avoid the rest of the output appearing.
One option would be to redirect the output and error of everything else in the script to /dev/null, by adding '>/dev/null 2>&1' to every line that output something, or otherwise silence them. EG
if fgrep myuser /etc/password ; then
dosomething
fi
could be written:
if fgrep myuser /etc/password >/dev/null 2>&1 ; then
dosomething
fi
though
if fgrep -q myuser /etc/password ; then
dosomething
fi
is more efficient in this case.
Another option would be to put the date wrapper in the crontab entry. Something like:
0 * * * * sh -c 'echo `date`: myscript starting ; /path/to/myscript >/dev/null 2>&1; echo `date`: myscript finished'
Lastly, you could use a subshell. Put the body of your script into a function, and then call that in a subshell with output redirected.
#!/bin/bash
do_it ()
{
... your script here ...
}
echo `date`: myscript starting
( do_it ) >/dev/null 2>&1
echo `date`: myscript finished
Try the following:
TMP=$(date); name-of-scipt.sh; echo "$TMP-$(date)"
or with formatted date
TMP=$(date +%Y%m%d.%H%M%S); name-of-scipt.sh; echo "$TMP-$(date +%Y%m%d.%H%M%S)"

Redirect output to filename given by result of command

Suppose I have a file format I want to save database backups to given as such:
echo "~/backups/$(date +'%Y-%m-%d_%H-%M-%S').sql"
Now how can I specify this result as a filename for output in shell?
mysqldump my_db > ....?
By the way: shell interprets the result of a nested echo command as an executable command/file. So It would seem that:
mysqldump my_db > $(echo "something")
does NOT work. Instead, shell looks for a file called something and tries executing it?
There is no need to use nested echo. You can avoid it:
mysqldump my_db > ~/backups/$(date +'%Y-%m-%d_%H-%M-%S').sql
$(echo "something") is not the problem, while ~ is.
It works fine if you use full path:
echo 'hello world' > $(echo "/home/root/backups/$(date +'%Y-%m-%d_%H-%M-%S').sql")
In case you're interested in how to use ~:
eval "echo 'hello world' > $(echo "~/backups/$(date +'%Y-%m-%d_%H-%M-%S').sql")"
I think there are more possibilities to answer this question:
How to name a file with current time? [duplicate]
--
You can create a file in bash by using different utilities
Using touch
root#ubuntu:~/T/e/s/t# touch $(date +"%T") //you can format time as per your need
Using editor
root#ubuntu:~/T/e/s/t# vi $(date +"%T")
Using redirection
root#ubuntu:~/T/e/s/t# echo "Input for file" > $(date +"%T")
Time format
In place of %T you can use your own time format
Refer: http://man7.org/linux/man-pages/man1/time.1.html

Better quote to execute command on shell script

I'm in doubt of the diference and which one is the better quote to execute a command in shell script.
For example, I have this two examples:
echo "The name of the computer is `uname -n`"
echo "The name of the computer is $(uname -n)"
Which one is better? Or there is no diference?
The $(...) one is generally recommended because it nests easier. Compare:
date -d "1970-01-01 $(echo "$(date +%s)-3600"|bc) sec UTC"
date -d "1970-01-01 `echo \"\`date +%s\`-3600\"|bc` sec UTC "

Resources