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

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"

Related

Shell Funtion string variables result blank?

I have small script which have a function asking user input (name of user) and then I have echo function
which is running the function and asking for input,
After that I have echo the $User_name in last line (Users_name_is - )which is set in function but its result is black, I want to use $User_name in further script.
what i am doing wrong ?
#!/bin/sh
funtion_one()
{
read varname
if [ $varname == skull ]; then
echo "Nice to meet you $varname"
#User_name=$varname
else
echo "I dont know you $varname"
fi
User_name=$varname
}
echo Hello, who am I talking to?
while :
do
case $(funtion_one) in
"Nice to meet you skull") break
;;
"I dont know you") $(funtion_one)
;;
esac
done
echo "Users_name_is - $User_name"
I want result Users_name_is - skull
When you do $(funtion_one), you are executing the function inside a subshell, so any variables created cease to exist after the function finishes.
An alternative would be this:
function_one()
{
read varname
if [ "$varname" = skull ]; then
echo "Nice to meet you $varname" >&2
else
echo "I dont know you $varname" >&2
fi
echo "$varname"
}
user_name=$(function_one)
Now user_name exists in the parent shell. The messages are sent to standard error, and the name that has been read is sent to standard output so that it can be captured by the command substitution $().
Alternatively, you can simply execute the function in the parent shell:
# change
echo $(funtion_one)
# to
funtion_one
But then all the variables used inside the function will continue to exist after it has been run.
It seems the function is getting in the way of you achieving what you want. I would restructure your code to something much simpler like this:
while read name; do
if [ "$name" = skull ]; then
echo "Nice to meet you $name"
break
fi
echo "I don't know you $name"
done
There are a lot of ways to structure your code, and it seems like you're trying to do something like:
#!/bin/sh
get_user_name() {
local varname
printf 'Hello, who am I talking to? '
read varname
if test "$varname" = skull; then
echo "Nice to meet you $varname"
User_name=$varname
return 0
else
echo "I dont know you $varname" >&2
return 1
fi
}
unset User_name
while ! get_user_name
do
case "$User_name" in
skull) break
;;
esac
done
echo "Users_name_is - $User_name"
It's perfectly valid to use a function to get the input, but if you want that function to set a variable in the caller you cannot call it as a subshell, and it's easiest if the shell returns a value to indicate success or failure.

Test if program exists in Bash Script - abbreviated version

I want to use the abbreviated if then else to determine if ccze exists before using it... and I just cannot get that first part right...
test() {
[ $(hash ccze) 2>/dev/null ] && echo "yes" || echo "no"
}
The above is just test.. what am I doing wrong? It does not matter if ccze exists or not - I'm getting "no"
testcmd () {
command -v "$1" >/dev/null
}
Using it:
if testcmd hello; then
echo 'hello is in the path'
else
echo 'hello is not in the path'
fi
or
testcmd man && echo yes || echo no
or you could put that into a function:
ptestcmd () {
testcmd "$1" && echo 'yes' || echo 'no'
}
This way you'll have one function that does testing and a separate function that does printing dependent on the test result. You may then use the one taht is appropriate for the situation (you may not always want output).
Suggestion #1 - Answer
some_function() {
hash ccze &> /dev/null && echo "yes" || echo "no"
}
Suggestion #2
Rename the function to something else because there is already the command test.
Don't override.
Suggestion #3
Remove the square brackets. Even if, in any case, you want to use square brackets, use double square brackets. Single square brackets are deprecated.
Brain Food
Shellcheck
Check for a command in PATH
Obsolete and Deprecated Bash Syntax
hash
test() #(not a good name)
{
if hash ccze 2>/dev/null; then
echo yes
else
echo no
fi
}
should do it.
command -v ccze >/dev/null is also usable and fairly portable (not POSIX but works with most shells).
Thanks for that but it was the one liner I wanted. I know that version works.
Meanwhile we figured it out...
test() { [ -x "$(command -v cckze)" ] && echo "yes" || echo "no"; }
Thanks anyway.

Bash function arguments not passed as expected

I have an unexpected behaviour in my script:
#!/bin/bash
checkFolders "first folder"
checkFolders() {
checkEmptyVar $1 "folder to be copied"
}
checkEmptyVar() {
echo "0: $0 1: $1 2: $2"
[[ $(isNotEmpty $1) = false ]] && (echo "Specify a path for $2") && exit
echo "post exit"
}
The function checkEmptyVar echoes the following:
0: ./lcp.sh 1: folder to be copied 2:
I expected to have passed "folder-to-be-copied" as $1of checkEmptyVar, what is happening?
You have numerous problems:
$0 is not the first argument to a function; $1 is. $0 is the name of the current script.
You must quote parameter expansions to prevent them from being split into multiple words on embedded whitespace.
Functions must be defined before they are used.
The correct script is
#!/bin/bash
checkFolders() {
checkEmptyVar "$1" "folder to be copied"
}
checkEmptyVar() {
[[ $(isNotEmpty "$1") = false ]] && echo "Specify a path for $2" && exit
echo "post exit"
}
checkFolders "first folder"
Further, it would be better to have isNotEmpty return a non-zero value instead of outputting the string false, so that you could write
checkEmptyVar () {
if isNotEmpty "$1"; then
echo "Specify a path for $2" >&2 # Use standard error, not standard output
exit 1
fi
echo "post exit"
}
(I suspect you could replace isNotEmpty with [[ -n $1 ]] anyway.)
This script isn't doing what you think it is. Your function definitions are happening too late.
When you call checkFolders on the first line you are calling a version of that function from the pre-existing environment and not the one defined later in that script.
If you run command -V checkFolders from the shell you are running this script from I expect you'll get output somewhat like:
checkFolders is a function
checkFolder ()
{
checkEmptyVar "folder to be copied"
}
though anything is possible there.
You should also always quote variables when you use them to prevent the shell from word-splitting their contents.
Without doing that when you call checkFolders "first folder" that ends up calling checkFolders first folder "folder to be copied" which isn't what you want at all.
Invert the order of your functions and calls and fix your variable quoting and you should see what you expect.

a more elegant way to do nested if then conditions

I want to push my input parameters through two sets of checks. Each of these checks is a function. So case would not work because in KSH.Case will "esac" after satisfying the 1st condition.
die () {
echo "ERROR: $*. Aborting." >&2
exit 1
}
var=$1
var1=$2
[ -z "$var1" ] && var1=$var
echo "var is $var . Var1 is $var1 "
# test $var1 != $var2 ||
dtc() {
# small function where I am checking if the input parameter is a valid date
}
vlc(){
# function where I am checking if the input parameters year is after 2012, because earlier years cannot exist
}
if dtc $var && dtc $var1
then
if vlc $var && vlc $var1
then
stuff
else
die "message"
fi
else
die "message"
fi
The nested if looks a bit clumsy. If there is a more elegant way convey this to shell.
Improving your indentation will go a long way toward readable code.
If your die message are different, then you have no choice. If they are the same, then you can combine the conditions:
If you just want to die if not all the commands are successful, you can write
dtc "$var" && dtc "$var1" &&
vlc "$var" && vlc "$var1" || die "message"
# OK, all passed
stuff
One style tip for if-else is to put the shorter block first
if ! { dtc "$var" && dtc "$var1"; }
then
die "message 1"
else
if ! { vlc "$var" && vlc "$var1"; }
then
die "message 2"
else
we will do
lots of stuff
here
fi
fi
and of course, functions to encapsulate code
stuff() {
we will do
lots of stuff
here
}

Add debug mode to bash script

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...

Resources