{"id":1316,"date":"2016-03-22T15:18:38","date_gmt":"2016-03-22T13:18:38","guid":{"rendered":"http:\/\/www.ludovicocaldara.net\/dba\/?p=1316"},"modified":"2016-03-22T15:18:38","modified_gmt":"2016-03-22T13:18:38","slug":"bash-tips-5-output-logfile","status":"publish","type":"post","link":"https:\/\/www.ludovicocaldara.net\/dba\/bash-tips-5-output-logfile\/","title":{"rendered":"Bash tips &#038; tricks [ep. 5]: Write the output to a logfile"},"content":{"rendered":"<p>This is the fifth epidose of a <a href=\"https:\/\/www.ludovicocaldara.net\/dba\/bash-tips-1-personal-accounts-permissions\/\">small series<\/a>.<\/p>\n<p><strong>Description:<\/strong><\/p>\n<p>Logging the output of the scripts to a file is very important. There are several ways to achieve it, I will just show one of my favorites.<\/p>\n<p><strong>BAD:<\/strong><\/p>\n<p>You can log badly either from the script to a log file:<\/p>\n<pre class=\"lang:sh decode:true\">#!\/bin\/bash -l\r\n\r\nTODAY=`date +\"%Y%m%d\"\r\nLOGDIR='\/path\/to\/log'\r\nOUTPUT=\"${LOGDIR}\/output_${TODAY}.log\"\r\n\r\n# create the empty file or overwrite the existing one\r\n&gt; $OUTPUT\r\n\r\necho \"Writing to the logfile\" | tee -a $OUTPUT\r\ncommand | tee -a $OUTPUT\r\n\r\necho \"ops, this message and command will not be logged\"\r\ncommand\r\nexit $?<\/pre>\n<p>or by redirecting badly the standard output of the script:<\/p>\n<pre class=\"lang:sh decode:true \">$ crontab -l\r\n0 * * * * \/path\/to\/script.sh &gt; \/path\/to\/always_the_same_log.out 2&gt;&amp;1<\/pre>\n<p><strong>\u00a0GOOD:<\/strong><\/p>\n<p>My favorite solution is to automatically open a pipe that will receive from the standard output and redirect to the logfile. With this solution, I can programmatically define my logfile name inside the script (based on the script name and input parameters for example) and forget about redirecting the output everytime that I run a command.<\/p>\n<pre class=\"lang:sh decode:true\">export LOGDIR=\/path\/to\/logfiles\r\nexport DATE=`date +\"%Y%m%d\"`\r\nexport DATETIME=`date +\"%Y%m%d_%H%M%S\"`\r\n\r\nScriptName=`basename $0`\r\nJob=`basename $0 .sh`\"_whatever_I_want\"\r\nJobClass=`basename $0 .sh`\r\n\r\nfunction Log_Open() {\r\n        if [ $NO_JOB_LOGGING ] ; then\r\n                einfo \"Not logging to a logfile because -Z option specified.\" #(*)\r\n        else\r\n                [[ -d $LOGDIR\/$JobClass ]] || mkdir -p $LOGDIR\/$JobClass\r\n                Pipe=${LOGDIR}\/$JobClass\/${Job}_${DATETIME}.pipe\r\n                mkfifo -m 700 $Pipe\r\n                LOGFILE=${LOGDIR}\/$JobClass\/${Job}_${DATETIME}.log\r\n                exec 3&gt;&amp;1\r\n                tee ${LOGFILE} &lt;$Pipe &gt;&amp;3 &amp;\r\n                teepid=$!\r\n                exec 1&gt;$Pipe\r\n                PIPE_OPENED=1\r\n                enotify Logging to $LOGFILE  # (*)\r\n                [ $SUDO_USER ] &amp;&amp; enotify \"Sudo user: $SUDO_USER\" #(*)\r\n        fi\r\n}\r\n\r\nfunction Log_Close() {\r\n        if [ ${PIPE_OPENED} ] ; then\r\n                exec 1&lt;&amp;3\r\n                sleep 0.2\r\n                ps --pid $teepid &gt;\/dev\/null\r\n                if [ $? -eq 0 ] ; then\r\n                        # a wait $teepid whould be better but some\r\n                        # commands leave file descriptors open\r\n                        sleep 1\r\n                        kill  $teepid\r\n                fi\r\n                rm $Pipe\r\n                unset PIPE_OPENED\r\n        fi\r\n}\r\n\r\nOPTIND=1\r\nwhile getopts \":Z\" opt ; do\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 case $opt in\r\n                Z)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 NO_JOB_LOGGING=\"true\"\r\n                        ;;\r\n        esac\r\ndone\r\n\r\nLog_Open\r\necho \"whatever I execute here will be logged to $LOGFILE\"\r\ncommand\r\nLog_Close<\/pre>\n<p>(*) the functions edebug, einfo, etc, have to be created using the guidelines I have used in this post: <a href=\"https:\/\/www.ludovicocaldara.net\/dba\/bash-tips-4-use-logging-levels\/\">Bash tips &amp; tricks [ep. 4]: Use logging levels<\/a><\/p>\n<p>The -Z parameter can be used to intentionally avoid logging.<\/p>\n<p>Again, all this stuff (function definitions and variables) should be put in a global include file.<\/p>\n<p>If I execute it:<\/p>\n<pre class=\"lang:sh decode:true \"># [ ludo@testsrv:\/scripts [21:10:17] [not set env:\"not set\"] 0 ] #\r\n# sudo -u oracle .\/myscript.sh\r\n2016-03-16 21:10:20 - Logging to \/path\/to\/logfiles\/myscript\/myscript_whatever_I_want_20160316_211020.log\r\n2016-03-16 21:10:20 - Sudo user: ludo\r\nwhatever I execute here will be logged to \/path\/to\/logfiles\/myscript\/myscript_whatever_I_want_20160316_211020.log\r\n\r\n# [ ludo@testsrv:\/scripts [21:10:20] [not set env:\"not set\"] 0 ] #\r\n# sudo -u oracle .\/myscript.sh -Z\r\n2016-03-16 21:15:18 - INFO ---- Not logging to a logfile because -Z option specified.\r\nwhatever I execute here will be logged to\r\n\r\n# [ ludo@testsrv:\/scripts [21:10:20] [not set env:\"not set\"] 0 ] #\r\n# cat \/path\/to\/logfiles\/myscript\/myscript_whatever_I_want_20160316_211020.log\r\n2016-03-16 21:10:20 - Logging to \/path\/to\/logfiles\/myscript\/myscript_whatever_I_want_20160316_211020.log\r\n2016-03-16 21:10:20 - Sudo user: ludo\r\nwhatever I execute here will be logged to \/path\/to\/logfiles\/myscript\/myscript_whatever_I_want_20160316_211020.log\r\n<\/pre>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is the fifth epidose of a small series. Description: Logging the output of the scripts to a file is very important. There are several ways to achieve it, I will just show one of my favorites. BAD: You can &hellip; <a href=\"https:\/\/www.ludovicocaldara.net\/dba\/bash-tips-5-output-logfile\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5,132],"tags":[],"class_list":["post-1316","post","type-post","status-publish","format-standard","hentry","category-linux","category-triblog"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.ludovicocaldara.net\/dba\/wp-json\/wp\/v2\/posts\/1316","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.ludovicocaldara.net\/dba\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.ludovicocaldara.net\/dba\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.ludovicocaldara.net\/dba\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ludovicocaldara.net\/dba\/wp-json\/wp\/v2\/comments?post=1316"}],"version-history":[{"count":5,"href":"https:\/\/www.ludovicocaldara.net\/dba\/wp-json\/wp\/v2\/posts\/1316\/revisions"}],"predecessor-version":[{"id":1338,"href":"https:\/\/www.ludovicocaldara.net\/dba\/wp-json\/wp\/v2\/posts\/1316\/revisions\/1338"}],"wp:attachment":[{"href":"https:\/\/www.ludovicocaldara.net\/dba\/wp-json\/wp\/v2\/media?parent=1316"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ludovicocaldara.net\/dba\/wp-json\/wp\/v2\/categories?post=1316"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ludovicocaldara.net\/dba\/wp-json\/wp\/v2\/tags?post=1316"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}