Add debug mode to bash script - bash

first of all, sorry for my bad english.
I want to enbale debug mode, with date. I was thinkging in a variable that use one type of redirection, or another, like this:
#!/bin/bash
DEBUG_MODE=1
if [ $CHAMAC_DEBUG = 0 ]; then
output=/dev/null
elif [ $CHAMAC_DEBUG = 1 ]; then
output=>( while read line; do echo "$(date): ${line}"; done >> ~/output.log )
fi
echo "hi, im a echo" &> $output
But it dont work for me.... how can do it¿?¿?

I'm not quite sure what you mean by "debug mode", but if you want to print additional output only when a particular variable is defined you could do something like this:
#!/bin/bash
function print_debug {
[ $DEBUG_MODE -eq 1 ] && echo "$(date) $1"
}
DEBUG_MODE=0
print_debug "Test 1"
DEBUG_MODE=1
print_debug "Test 2"
Output:
$ ./test.sh
Test 2
You could also separate debug output from regular output by echoing debug messages to a different file descriptor:
function print_debug {
[ $DEBUG_MODE -eq 1 ] && echo "$1" 1>&3
}
That way you can redirect STDOUT, STDERR and debug output to different files if needed.

Your question is hard to understand, but perhaps you are looking for something like this?
debug () {
date +"[%C] $*" >&2
}
if ... whatever ...; then
dbg=debug
else
dbg=:
fi
$dbg "script started"
:
Wherever you want to output a diagnostic, use $dbg instead of echo.
(You have to double any literal percent sign in your debug prints, or make the function slightly more complex.)

You can use an alternate file descriptor for this (along with a few other fixes):
if [[ "${CHAMAC_DEBUG}" == "1" ]]
then
exec 3>> ~/output.log
else
exec 3>> /dev/null
fi
{ echo "$(date) : hi, im a echo"; } >&3
The process substitution syntax wants to see the name of a program, not a bash internal command like while. You could maybe get it to work using something like >(bash -c 'some stuff here'), but the above is simpler, I think. The only downside is you have to do your own date-stamping... If that's a problem, you could look into submitting your debug messages to syslog (via logger) and configure that to dump those specific messages in a file of their own...

Related

How to process basic commandline arguments in Bash?

So I started today taking a look at scripting using vim and I'm just so very lost and was looking for some help in a few areas.
For my first project,I want to process a file as a command line argument, and if a file isn't included when the user executes this script, then a usage message should be displayed, followed by exiting the program.
I have no clue where to even start with that, will I need and if ... then statement, or what?
Save vim for later and try to learn one thing at a time. A simpler text editor is called nano.
Now, as far as checking for a file as an argument, and showing a usage message otherwise, this is a typical pattern:
PROGNAME="$0"
function show_usage()
{
echo "Usage: ${PROGNAME} <filename>" >&2
echo "..." >&2
exit 1
}
if [[ $# -lt 1 ]]; then
show_usage
fi
echo "Contents of ${1}:"
cat "$1"
Let's break this down.
PROGNAME="$0"
$0 is the name of the script, as it was called on the command line.
function show_usage()
{
echo "Usage: ${PROGNAME} <filename>" >&2
echo "..." >&2
exit 1
}
This is the function that prints the "usage" message and exits with a failure status code. 0 is success, anything other than 0 is a failure. Note that we redirect our echo to &2--this prints the usage message on Standard Error rather than Standard Output.
if [[ $# -lt 1 ]]; then
show_usage
fi
$# is the number of arguments passed to the script. If that number is less than 1, print the usage message and exit.
echo "Contents of ${1}:"
cat "$1"
$1 is out filename--the first argument of the script. We can do whatever processing we want to here, with $1 being the filename. Hope this helps!
i think you're asking how to write a bash script that requires a file as a command-line argument, and exits with a usage message if there's a problem with that:
#!/bin/bash
# check if user provided exactly one command-line argument:
if [ $# -ne 1 ]; then
echo "Usage: `basename "$0"` file"
exit 1
# now check if the provided argument corresponds to a real file
elif [ ! -f "$1" ]; then
echo "Error: couldn't find $1."
exit 1
fi
# do things with the file...
stat "$1"
head "$1"
tail "$1"
grep 'xyz' "$1"

Loop to ask user via keyboard for filename to be used or q to exit in bash

I'm having issues with a script I'm writing in bash with regards to backing up or restoring. What I'm trying to do is check for parameters and then if none are presented, loop until a name is provided or they quit. I can check for parameters and loop to quit but the problem I am having is getting the user input and then using that for the backup file name. Here's my script so far, can someone advise on how to loop for filename/q and how to get said filename input to work with the rest of the script?
#!/bin/bash
# Purpose - Backup world directory
# Author - Angus
# Version - 1.0
FILENAME=$1.tar.gz
SRCDIR=World
DESDIR=backupfolder
if [ $# -eq 0 ]
then
echo "No filename detected. To use this utility a filename is
required. Usage:tarscript filename"
else
echo "The filename to be used will be $filename"
fi
while [ $# -eq 0 ]
do
echo "Please provide a filename or press q to exit."
read response
if [ $response == 'q' ]
then
exit
else [ $response == '$FILENAME' ]
echo -n 'Would you like to backup or restore? (B/R)'
read response
if [ $response == 'B' ]
then
tar cvpzf $DESDIR/$FILENAME $SRCDIR
echo 'Backup completed'
exit
fi
fi
done
I finally managed to get it working in the end. I realised what my mistakes were thanks to Jens and changed things enough that it now responds to input and supplied parameters. Of course the code is nearly twice as big now with all my changes but hey ho.

SHELL general function for action state

How to make a code bellow as a general function to be used entire script in bash:
if [[ $? = 0 ]]; then
echo "success " >> $log
else echo "failed" >> $log
fi
You might write a wrapper for command execution:
function exec_cmd {
$#
if [[ $? = 0 ]]; then
echo "success " >> $log
else
echo "failed" >> $log
fi
}
And then execute commands in your script using the function:
exec_cmd command1 arg1 arg2 ...
exec_cmd command2 arg1 arg2 ...
...
If you don't want to wrap the original calls you could use an explicit call, like the following
function check_success {
if [[ $? = 0 ]]; then
echo "success " >> $log
else echo "failed" >> $log
fi
}
ls && check_success
ls non-existant
check_success
There's no really clean way to do that. This is clean and might be good enough?
PS4='($?)[$LINENO]'
exec 2>>"$log"
That will show every command run in the log, and each entry will start with the exit code of the previous command...
You could put this in .bashrc and call it whenever
function log_status { [ $? == 0 ] && echo success>>/tmp/status || echo fail>>/tmp/status }
If you want it after every command you could make the prompt write to the log (note the original PS1 value is appended).
export PS1="\$([ \$? == 0 ] && echo success>>/tmp/status || echo fail>>/tmp/status)$PS1"
(I'm not experienced with this, perhaps PROMPT_COMMAND is a more appropriate place to put it)
Or even get more fancy and see the result with colours.
I guess you could also play with getting the last executed command:
How do I get "previous executed command" in a bash script?
Get name of last run program in Bash
BASH: echoing the last command run

writing from a function in a Bash script leaking file descriptors

We have a shell script that is called by cron and runs as root.
This script outputs logging and debug info, and has been failing at one certain point. This point varies based on how much output the script creates (it fails sooner if we enable more debugging output, for example).
However, if the script is called directly, as a user, then it works without a problem.
We have since created a simplified test case which demonstrates the problem.
The script is:
#!/bin/bash
function log_so () {
local msg="$1"
if [ -z "${LOG_FILE}" ] ; then warn_so "It's pointless use log_so() if LOG_FILE variable is undefined!" ; return 1 ; fi
echo -e "${msg}"
echo -e "${msg}" >> ${LOG_FILE}
(
/bin/true
)
}
LOG_FILE="/usr/local/bin/log_bla"
linenum=1
while [[ $linenum -lt 2000 ]] ; do
log_so "short text: $linenum"
let linenum++
done
The highest this has reached is 244 before dying (when called via cron).
Some other searches recommended using a no-op subshell from the function and also calling /bin/true but not only did this not work, the subshell option is not feasible in the main script.
We have also tried changing the file descriptor limit for root, but that did not help, and have tried using both #!/bin/sh and #!/bin/bash for the script.
We are using bash 4.1.5(1)-release on Ubuntu 10.04 LTS.
Any ideas or recommendations for a workaround would be appreciated.
What about opening a fd by hand and cleaning it up afterwards? I don't have a bash 4.1 to test with, but it might help.
LOG_FILE="/usr/local/bin/log_bla"
exec 9<> "$LOG_FILE"
function log_so () {
local msg="$1"
if [ -z "${LOG_FILE}" ] ; then warn_so "It's pointless use log_so() if LOG_FILE variable is undefined!" ; return 1 ; fi
echo -e "${msg}"
echo -e "${msg}" >&9
return 0
}
linenum=1
while [[ $linenum -lt 2000 ]] ; do
log_so "short text: $linenum"
let linenum++
done
exec 9>&-

Can I pass an arbitrary block of commands to a bash function?

I am working on a bash script where I need to conditionally execute some things if a particular file exists. This is happening multiple times, so I abstracted the following function:
function conditional-do {
if [ -f $1 ]
then
echo "Doing stuff"
$2
else
echo "File doesn't exist!"
end
}
Now, when I want to execute this, I do something like:
function exec-stuff {
echo "do some command"
echo "do another command"
}
conditional-do /path/to/file exec-stuff
The problem is, I am bothered that I am defining 2 things: the function of a group of commands to execute, and then invoking my first function.
I would like to pass this block of commands (often 2 or more) directly to "conditional-do" in a clean manner, but I have no idea how this is doable (or if it is even possible)... does anyone have any ideas?
Note, I need it to be a readable solution... otherwise I would rather stick with what I have.
This should be readable to most C programmers:
function file_exists {
if ( [ -e $1 ] ) then
echo "Doing stuff"
else
echo "File $1 doesn't exist"
false
fi
}
file_exists filename && (
echo "Do your stuff..."
)
or the one-liner
file_exists filename && echo "Do your stuff..."
Now, if you really want the code to be run from the function, this is how you can do that:
function file_exists {
if ( [ -e $1 ] ) then
echo "Doing stuff"
shift
$*
else
echo "File $1 doesn't exist"
false
fi
}
file_exists filename echo "Do your stuff..."
I don't like that solution though, because you will eventually end up doing escaping of the command string.
EDIT: Changed "eval $*" to $ *. Eval is not required, actually. As is common with bash scripts, it was written when I had had a couple of beers ;-)
One (possibly-hack) solution is to store the separate functions as separate scripts altogether.
The cannonical answer:
[ -f $filename ] && echo "it has worked!"
or you can wrap it up if you really want to:
function file-exists {
[ "$1" ] && [ -f $1 ]
}
file-exists $filename && echo "It has worked"

Resources