How to call a function in shell Scripting? - shell

I have a shell script which conditionally calls a function.
For Example:-
if [ "$choice" = "true" ]
then
process_install
elif [ "$choice" = "false" ]
then
process_exit
fi
process_install()
{
commands...
commands...
}
process_exit()
{
commands...
commands...
}
Please let me know how to accomplish this.

You don't specify which shell (there are many), so I am assuming Bourne Shell, that is I think your script starts with:
#!/bin/sh
Please remember to tag future questions with the shell type, as this will help the community answer your question.
You need to define your functions before you call them. Using ():
process_install()
{
echo "Performing process_install() commands, using arguments [${*}]..."
}
process_exit()
{
echo "Performing process_exit() commands, using arguments [${*}]..."
}
Then you can call your functions, just as if you were calling any command:
if [ "$choice" = "true" ]
then
process_install foo bar
elif [ "$choice" = "false" ]
then
process_exit baz qux
You may also wish to check for invalid choices at this juncture...
else
echo "Invalid choice [${choice}]..."
fi
See it run with three different values of ${choice}.
Good luck!

#!/bin/bash
process_install()
{
commands...
commands...
}
process_exit()
{
commands...
commands...
}
if [ "$choice" = "true" ] then
process_install
else
process_exit
fi

Example of using a function() in bash:
#!/bin/bash
# file.sh: a sample shell script to demonstrate the concept of Bash shell functions
# define usage function
usage(){
echo "Usage: $0 filename"
exit 1
}
# define is_file_exists function
# $f -> store argument passed to the script
is_file_exists(){
local f="$1"
[[ -f "$f" ]] && return 0 || return 1
}
# invoke usage
# call usage() function if filename not supplied
[[ $# -eq 0 ]] && usage
# Invoke is_file_exits
if ( is_file_exists "$1" )
then
echo "File found: $1"
else
echo "File not found: $1"
fi

The functions need to be defined before being used. There is no mechanism is sh to pre-declare functions, but a common technique is to do something like:
main() {
case "$choice" in
true) process_install;;
false) process_exit;;
esac
}
process_install()
{
commands...
commands...
}
process_exit()
{
commands...
commands...
}
main()

Summary:
Define functions before using them.
Once defined, treat them as commands.
Consider this script, called funcdemo:
#!/bin/bash
[ $# = 0 ] && exhort "write nastygram"
exhort(){
echo "Please, please do not forget to $*"
}
[ $# != 0 ] && exhort "write begging letter"
In use:
$ funcdemo
./funcdemo: line 3: exhort: command not found
$ funcdemo 1
Please, please do not forget to write begging letter
$
Note the potential for a missing function to lie undiscovered for a long time (think 'by a customer at the most critical wrong moment'). It only matters whether the function exists when it is executed, the same as it only matters whether any other command exists when you try to execute it. Indeed, until it goes to execute the command, the shell neither knows nor cares whether it is an external command or a function.

You can create another script file separately for the functions and invoke the script file whenever you want to call the function. This will help you to keep your code clean.
Function Definition : Create a new script file
Function Call : Invoke the script file

#!/bin/bash
# functiontest.sh a sample to call the function in the shell script
choice="true"
function process_install
{
commands...
}
function process_exit
{
commands...
}
function main
{
if [[ "$choice" == "true" ]]; then
process_install
elif [[ "$choice" == "false" ]]; then
process_exit
fi
}
main "$#"
it will start from the main function

Related

Equal and not equal operators not working in bash script

I have this function inside a .sh script :
prepare_for_test(){
stopresources;
initresources;
if [ "$1" = "--gf" ]; then
startglassfish;
fi
docker ps;
notify-send "Done!" "You can now test" -t 10000;
};
The script's name's preparefortests.sh. When I run it on bash, passing --gf or "--gf":
preparefortests.sh --gf
it does not run alias startglassfish, as if that if statement was false.
I even tried to add a check on the parameter:
if [ "$1" ] && [ "$1" != "--gf" ]; then
echo "uknown parameter $1"
fi
but it's not working neither, when e.g. I try to run it like:
preparefortests.sh hello
I'd expect "unknown parameter hello".
What am I doing wrong?
The comparison statement is correct:
if [ "$1" = "--gf" ]; then
startglassfish;
fi
There can be other issue like:
Make sure you pass $1 argument, while calling function:
Write prepare_for_test $1
The problem might be the alias used. For almost every purpose, aliases are superseded by shell functions. So either you need to make alias as function and export it or instead use special variable BASH_ALIASES. In your case:
if [ "$1" = "--gf" ];then
${BASH_ALIASES[startglassfish]};
fi

complicated bash functions returning boolean

I want to do something like this:
function one {
if [ "$1" == "hello" ]; then
return true
fi
return false
}
if [ one hello -o one goodbye ]; then
echo "Well, that's weird."
fi
As written, I know this doesn't work. Bash won't let you return boolean values. You can return 0 or 1. But bash doesn't seem to like using 0 or 1 as boolean expressions. So instead I have to return 0 instead of true and 1 for false. I can live with that. But then my if becomes:
if [ one hello -eq 0 -o one goodbye -eq 0 ] ...
Which I can do, but it's awkward. But THAT doesn't work, either, because test doesn't want to call my function with an argument.
if [ `one hello` -eq 0 -o `one goodbye` -eq 0 ] ...
Finally, I think that version works. But it's fugly.
Is there an elegant way to:
Have a bash function that returns true/false
And it takes arguments
And then it gets used in a complex if-statement (there's some combination of -o / -a between the [] )
I'd really like to have some way of writing:
if [ myfunc1 $somearg -a $myfunc2 $someotherarg ]; then ...
For now, I'm doing it the fugly way.
You don't really write predicates like this. Instead, you set the exit status to 0 if a function succeeds, or non-zero value if it fails. The if statement then checks the exit status directly.
one () {
if [ "$1" = "hello" ]; then
return 0
fi
return 1
}
if one hello || one goodbye; then
echo "Well, that's weird."
fi
Since [ itself is a command that has a 0 exit status when the test is true and 1 if false, you can define one as simply
one () {
[ "$1" = "hello" ]
}
If no return statement is encountered, the exit status of a function is the exit status of the last command to execute.
Functions don't return true or false. Instead, they succeed or fail. if checks to see if a command succeeded or not. [ is a very common command that is used in shell scripts, but it misleads many into believing it is part of the grammar. It is not. You can simply write:
if one hello || one goodbye; then
echo "Well, that's weird."
fi
You don't want to use -a or -o. (Those are typically passed as arguments to [, but are effectively deprecated in that usage.) Instead, use the shell operators && and ||.
The fun thing about the strings "true" and "false" is that they are also shell builtin commands that have the expected exit status:
function one {
[[ "$1" == "hello" ]] && echo true || echo false
}
a=$(one hello)
b=$(one goodbye)
if $a || $b; then ...
But, don't do this. Keep it simple as other answers advice.
Like this?
one () {
if [ "$1" = "hello" ]; then
echo true; return 0
fi
echo false; return 1
}
$ one hello && one bye || one hello
true
false
true
Use the builtin commands, /bin/true and /bin/false:
/bin/true always returns 0.
/bin/false always returns 1.
function one {
[[ "$1" == "hello" ]] && /bin/true || /bin/false
}
if one hello || one goodbye; then
# do stuff…
fi;
Because they’re builtins under the /bin (/usr/bin) directory, which will likely be in your $PATH, you can simply call true or false:
function one {
[[ "$1" == "hello" ]] && true || false
}
if one hello || one goodbye; then
# do stuff…
fi;
Some of the answers above almost got it. Though, you wouldn’t want to echo the words, “true” or “false,” as strings—that does nothing in relation to the question. You want to call the commands that were made for this very purpose.

How can I ensure the relevant command is run based on arguments passed in BASH?

I'm writing my first bash script that will do some visual testing using wraith. I've stripped down the code to make it easier to read.
What I'm trying to write:
- The BASH command accepts an argument - 1, 2 or 3. i.e. regressiontest 1
- server will be assigned the argument passed
- alias config will be assigned to wraith capture configs/capture-staging-1.yaml, wraith capture configs/capture-staging-2.yaml or
wraith capture configs/capture-staging-3.yaml
- The echo statement is written to a txt file.
The script works as expected. The only issue is:
If run regressiontest 1, all good, runs wraith capture configs/capture-staging-1.yaml as expected.
I run regressiontest 2, I would expect it to run wraith capture configs/capture-staging-2.yaml but seems to run wraith capture configs/capture-staging-1.yaml again.
It seems to be running using the previous config file. If I close and open terminal again, it works as expected but if I run the same command with a different argument consecutively it seems to always run the first command I use.
What am I doing wrong?
I'm new to BASH scripts and am having trouble googling to find an answer
function regressiontest {
regressionfolder=~/path/to/folder
cd $regressionfolder
alias config
if [ $# -eq 0 ]; then
echo "No arguments provided - USAGE: regressiontest <server>"
return 0
else
server=$1
fi
if [ $server != 1 ] && [ $server != 2 ] && [ $server != 3 ]; then
echo "Visual Regression Testing argument invalid - USAGE: regressiontest <server>"
return 0
elif [ $server == 1 ]; then
server="1"
alias config='wraith capture configs/capture-staging-1.yaml'
elif [ $server == 2 ]; then
server="2"
alias config='wraith capture configs/capture-staging-2.yaml'
elif [ $server == 3 ]; then
server="3"
alias config='wraith capture configs/capture-staging-3.yaml'
fi
echo "https://website-staging-$server/" > data/server.txt
config
}
Any help would be much appreciated.
Thanks, All
Moe
You are thinking right, but making things harder than need be. Your initial part of the script is fine, though I would validate that the cd succeeds, e.g.
regressionfolder=~/path/to/folder
cd "$regressionfolder" || {
printf "error: unable to change to %s\n" "$regressionfolder" >&2
return 1
}
(note: a return of 1 generally indicates error and always double-quote your variables)
After your check on "$server" != 1 ... all you need to do is set your alias with $server as the number. No additional if ... elif ... are required, e.g.
if [ "$server" != 1 ] && [ "$server" != 2 ] && [ "$server" != 3 ]; then
echo "Visual Regression Testing argument invalid - USAGE: regressiontest <server>"
return 1
fi
alias config="wraith capture configs/capture-staging-$server.yaml"
config
}
(note: always double-quote variables withing [...])
Eliminate the alias
There is no need for the alias, you can simply run:
wraith capture configs/capture-staging-$server.yaml
Putting it altogether, you could do something similar to:
function regressiontest {
regressionfolder="$HOME/path/to/folder"
cd "$regressionfolder" || {
printf "error: unable to change to %s\n" "$regressionfolder" >&2
return 1
}
if [ $# -eq 0 ]; then
echo "No arguments provided - USAGE: regressiontest <server>"
return 1
else
server=$1
fi
if [ "$server" != 1 ] && [ "$server" != 2 ] && [ "$server" != 3 ]; then
printf "Visual Regression Testing argument invalid - "
printf "USAGE: regressiontest <server>\n"
return 1
fi
wraith capture "configs/capture-staging-$server.yaml"
}
(note: also the use of "$HOME" instead of ~. While ~ will expand on the command line, you will quickly run into problems using it within scripts)
Use a case Statement
A shorter more condensed version of your function using case ... esac would probably be a bit better, e.g.
function regressiontest {
regressionfolder="$HOME/path/to/folder"
cd "$regressionfolder" || {
printf "error: unable to change to %s\n" "$regressionfolder" >&2
return 1
}
case "$server" in
[123] ) wraith capture "configs/capture-staging-$server.yaml";;
* ) printf "Visual Regression Testing argument invalid - "
printf "USAGE: regressiontest <server>\n"
return 1;;
esac
}
I don't think you want to declare aliases, but store commands for later execution; just remove the "alias" from alias config='…' and at the end call it via $config.

In bash, is there an equivalent of die "error msg"

In perl, you can exit with an error msg with die "some msg". Is there an equivalent single command in bash? Right now, I'm achieving this using commands: echo "some msg" && exit 1
You can roll your own easily enough:
die() { echo "$*" 1>&2 ; exit 1; }
...
die "Kaboom"
Here's what I'm using. It's too small to put in a library so I must have typed it hundreds of times ...
warn () {
echo "$0:" "$#" >&2
}
die () {
rc=$1
shift
warn "$#"
exit $rc
}
Usage: die 127 "Syntax error"
This is a very close function to perl's "die" (but with function name):
function die
{
local message=$1
[ -z "$message" ] && message="Died"
echo "$message at ${BASH_SOURCE[1]}:${FUNCNAME[1]} line ${BASH_LINENO[0]}." >&2
exit 1
}
And bash way of dying if built-in function is failed (with function name)
function die
{
local message=$1
[ -z "$message" ] && message="Died"
echo "${BASH_SOURCE[1]}: line ${BASH_LINENO[0]}: ${FUNCNAME[1]}: $message." >&2
exit 1
}
So, Bash is keeping all needed info in several environment variables:
LINENO - current executed line number
FUNCNAME - call stack of functions, first element (index 0) is current function, second (index 1) is function that called current function
BASH_LINENO - call stack of line numbers, where corresponding FUNCNAME was called
BASH_SOURCE - array of source file, where corresponfing FUNCNAME is stored
Yep, that's pretty much how you do it.
You might use a semicolon or newline instead of &&, since you want to exit whether or not echo succeeds (though I'm not sure what would make it fail).
Programming in a shell means using lots of little commands (some built-in commands, some tiny programs) that do one thing well and connecting them with file redirection, exit code logic and other glue.
It may seem weird if you're used to languages where everything is done using functions or methods, but you get used to it.
# echo pass params and print them to a log file
wlog(){
# check terminal if exists echo
test -t 1 && echo "`date +%Y.%m.%d-%H:%M:%S` [$$] $*"
# check LogFile and
test -z $LogFile || {
echo "`date +%Y.%m.%d-%H:%M:%S` [$$] $*" >> $LogFile
} #eof test
}
# eof function wlog
# exit with passed status and message
Exit(){
ExitStatus=0
case $1 in
[0-9]) ExitStatus="$1"; shift 1;;
esac
Msg="$*"
test "$ExitStatus" = "0" || Msg=" ERROR: $Msg : $#"
wlog " $Msg"
exit $ExitStatus
}
#eof function Exit

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