I'm in the process (v.painful) of learning the bash language and I'm fighting with the code below. Any explanation for it's current behaviour would be greatly appreciated.
code
#!/usr/bin/env bash
function useless
{
echo "$1"
return 0
}
function call-and-report
{
TEST_CRITERIA="$1"
if $TEST_CRITERIA
then
echo "passed"
else
echo "failed"
fi
}
call-and-report $(useless "Hello World")
issue
I'm expecting the following console output:
Hello World
passed
Instead I'm seeing the following output:
./../test.sh: line 13: Hello: command not found
failed
$(command) executes command and then substitutes its standard output into the command line. So whatever useless prints is not printed on the terminal, it's substituted back into the calling command line.
So your last command becomes:
call-and-report Hello World
Then inside call-and-report, $1 is Hello, and you assign that to TEST_CRITERIA. When you do
if $TEST_CRITERIA
is equivalent to
if Hello
This tries to execute Hello as a command. Since there's no such command, you get an error.
If you want to test if there's anything in $TEST_CRITERIA, you need to use the test command, also available as [.
if [ -n "$TEST_CRITERIA" ]
Note that since you didn't quote $(useless "Hello World") each word that it outputs becomes a separate argument to call-and-report.
If you want the value of $TEST_CRITERIA printed on the terminal, you need to do that explicitly in call-and-report.
function useless
{
echo "$1"
return 0
}
function call-and-report
{
TEST_CRITERIA="$1"
echo "$TEST_CRITERIA"
if [ -n "$TEST_CRITERIA" ]
then
echo "passed"
else
echo "failed"
fi
}
call-and-report "$(useless "Hello World")"
In your example, $(useless "Hello World") will return
Hello World
So you are calling your call-and-report function with 2 arguments:
call-and-report Hello World
And in that function, you take the first one, Hello into your variable.
Then you do if Hello, which generates the error because indeed, Hello is not a command, so it neither succeeds nor fails.
Related
I have a bash script that calls a function which returns a value. I have included the scripts below:
Script
source ./utilities/function1.sh
result=$(Function1)
echo "Result: $result"
Function1
function Function1 {
echo "Inside Function: Function1"
cat <<EOF
this is the result
EOF
}
I want to be able to echo to the console within the function and return only the value I want, not including the messages that were echoed to the console, but when I run the script the following is returned:
Result: Inside Function: Func1
this is the result
Is this the best way to return a value from a bash function or is there a way I can echo to the console and return a value without the content of the echo commands from the function?
Thanks in advance
There are a few ways to do what you want. two simple ones are:
Use STDERR to echo to the console and capture STDOUT in your script. By default, STDOUT is on File Descriptor 1 and STDERR is on File Descriptor 2:
function myFunction() {
echo "This goes to STDOUT" >&1 # '>&1' is the default, so can be left out.
echo "This goes to STDERR" >&2
}
result=$(myFunction)
echo ${result}
Use a variable to return a string to the caller:
function myFunction() {
echo "This goes to STDOUT"
result="This goes into the variable"
}
declare result="" # Has global scope. Can be modified from anywhere.
myFunction
echo ${result}
Global scope variables are not good programming practice, but are a necessary evil in bash scripting.
Assuming I have the following script:
#!/bin/bash
function hello (){
echo hello,
}
function world (){
echo world!
}
Is it possible to select the functions to run while I start the script?
For exmaple:
./test.sh hello world
output:
hello,world!
Just iterate over the arguments and run them.
for i in "$#"; do
"$i"
done
Do not use function name(), just name(). See https://wiki.bash-hackers.org/scripting/obsolete . Check your scripts with shellcheck .
You can use eval command to execute first argument.
#!/bin/bash
function hello (){
echo hello,
}
function world (){
echo world!
}
eval $1
I use Cmder, I declared bash aliases in Cmder\config\user_profile.sh and I can use them in bash console mode. But in the same file declared functions don't work in bash console mode.
function hello {
echo "Hello $1 !"
}
Wrong file, wrong syntax, not possible ???
Ok, I found the pb !
We need to respect the syntax like this :
function hello {
echo "Hello $1 !"
}
and never like this :
function hello { echo "Hello $1 !" }
because of a problem of unexpected end file.
Indeed we are not under Windows shell ! o:)
UPDATE : functional syntax on one line
hello() { echo "Hello $1 !"; }
I am trying to call a function in a loop and gracefully handle and continue when it throws.
If I omit the || handle_error it just stops the entire script as one would expect.
If I leave || handle_error there it will print foo is fine after the error and will not execute handle_error at all. This is also an expected behavior, it's just how it works.
#!/bin/bash
set -e
things=(foo bar)
function do_something {
echo "param: $1"
# just throw on first loop run
# this statement is just a way to selectively throw
# not part of a real use case scenario where the command(s)
# may or may not throw
if [[ $1 == "foo" ]]; then
throw_error
fi
# this line should not be executed when $1 is "foo"
echo "$1 is fine."
}
function handle_error {
echo "$1 failed."
}
for thing in ${things[#]}; do
do_something $thing || handle_error $thing
done
echo "done"
yields
param: foo
./test.sh: line 12: throw_error: command not found
foo is fine.
param: bar
bar is fine.
done
what I would like to have is
param: foo
./test.sh: line 12: throw_error: command not found
foo failed.
param: bar
bar is fine.
done
Edit:
do_something doesn't really have to return anything. It's just an example of a function that throws, I could potentially remove it from the example source code because I will have no control over its content nor I want to, and testing each command in it for failure is not viable.
Edit:
You are not allowed to touch do_something logic. I stated this before, it's just a function containing a set of instructions that may throw an error. It may be a typo, it may be make failing in a CI environment, it may be a network error.
The solution I found is to save the function in a separate file and execute it in a sub-shell. The downside is that we lose all locals.
do-something.sh
#!/bin/bash
set -e
echo "param: $1"
if [[ $1 == "foo" ]]; then
throw_error
fi
echo "$1 is fine."
my-script.sh
#!/bin/bash
set -e
things=(foo bar)
function handle_error {
echo "$1 failed."
}
for thing in "${things[#]}"; do
./do-something.sh "$thing" || handle_error "$thing"
done
echo "done"
yields
param: foo
./do-something.sh: line 8: throw_error: command not found
foo failed.
param: bar
bar is fine.
done
If there is a more elegant way I will mark that as correct answer. Will check again in 48h.
Edit
Thanks to #PeterCordes comment and this other answer I found another solution that doesn't require to have separate files.
#!/bin/bash
set -e
things=(foo bar)
function do_something {
echo "param: $1"
if [[ $1 == "foo" ]]; then
throw_error
fi
echo "$1 is fine."
}
function handle_error {
echo "$1 failed with code: $2"
}
for thing in "${things[#]}"; do
set +e; (set -e; do_something "$thing"); error=$?; set -e
((error)) && handle_error "$thing" $error
done
echo "done"
correctly yields
param: foo
./test.sh: line 11: throw_error: command not found
foo failed with code: 127
param: bar
bar is fine.
done
#!/bin/bash
set -e
things=(foo bar)
function do_something() {
echo "param: $1"
ret_value=0
if [[ $1 == "foo" ]]; then
ret_value=1
elif [[ $1 == "fred" ]]; then
ret_value=2
fi
echo "$1 is fine."
return $ret_value
}
function handle_error() {
echo "$1 failed."
}
for thing in ${things[#]}; do
do_something $thing || handle_error $thing
done
echo "done"
See my comment above for the explanation. You can't test for a return value without creating a return value, which should be somewhat obvious. And || tests for a return value, basically, one greater than 0. Like && tests for 0. I think that's more or less right. I believe the bash return value limit is 254? I want to say. Must be integer between 0 and 254. Can't be a string, a float, etc.
http://tldp.org/LDP/abs/html/complexfunct.html
Functions return a value, called an exit status. This is analogous to
the exit status returned by a command. The exit status may be
explicitly specified by a return statement, otherwise it is the exit
status of the last command in the function (0 if successful, and a
non-zero error code if not). This exit status may be used in the
script by referencing it as $?. This mechanism effectively permits
script functions to have a "return value" similar to C functions.
So actually, foo there would have returned a 127 command not found error. I think. I'd have to test to see for sure.
[updated}
No, echo is the last command, as you can see from your output. And the outcome of the last echo is 0, so the function returns 0. So you want to dump this notion and go to something like trap, that's assuming you can't touch the internals of the function, which is odd.
echo fred; echo reval: $?
fred
reval: 0
What does set -e mean in a bash script?
-e Exit immediately if a command exits with a non-zero status.
But it's not very reliable and considered as a bad practice, better use :
trap 'do_something' ERR
http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_02.html see trap, that may do what you want. But not ||, unless you add returns.
try
if [[ "$1" == "foo" ]]; then
foo
fi
I wonder if it was trying to execute the command foo within the condition test?
from bash reference:
-e
Exit immediately if a pipeline (see Pipelines), which may consist of a single simple command (see Simple Commands), a list (see Lists),
or a compound command (see Compound Commands) returns a non-zero
status. The shell does not exit if the command that fails is part of
the command list immediately following a while or until keyword, part
of the test in an if statement, part of any command executed in a &&
or || list except the command following the final && or ||, any
command in a pipeline but the last, or if the command’s return status
is being inverted with !.
as you can see, if the error occurs within the test condition, then the script will continue oblivious and return 0.
--
Edit
So in response, I note that the docs continue:
If a compound command other than a subshell returns a non-zero status
because a command failed while -e was being ignored, the shell does
not exit.
Well because your for is succeeded by the echo, there's no reason for an error to be thrown!
I am writing a script in BASH. I have a function within the script that I want to provide progress feedback to the user. Only problem is that the echo command does not print to the terminal. Instead all echos are concatenated together and returned at the end.
Considering the following simplified code how do I get the first echo to print in the users terminal and have the second echo as the return value?
function test_function {
echo "Echo value to terminal"
echo "return value"
}
return_val=$(test_function)
Yet a solution other than sending to STDERR (it may be preferred if your STDERR has other uses, or possibly be redirected by the caller)
This solution direct prints to the terminal tty:
function test_function {
echo "Echo value to terminal" > /dev/tty
echo "return value"
}
-- update --
If your system support the tty command, you could obtain your tty device from the tty command, and thus you may:
echo "this prints to the terminal" > `tty`
send terminal output to stderr:
function test_function {
echo "Echo value to terminal" >&2
echo "return value"
}
Dont use command substitution to obtain the return value from the function
The return value is always available at the $? variable. You can use the variable rather than using command substitution
Test
$ function test_function {
> return_val=10;
> echo "Echo value to terminal $return_val";
> return $return_val;
> }
$ test_function
Echo value to terminal 10
$ return_value=$?
$ echo $return_value
10
If you don't know in which terminal/device you are:
function print_to_terminal(){
echo "Value" >$(tty)
}