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)
}
Related
I have a very trivial bash script taking input from the user in the first step and then echo an output. I want to run the same script in different shells and let the first shell take input and echo its output and send it to the input of the other shell, and let the both of shells continue executing normally after that.
I have read many answers about exporting variables from shell to shell, like getting the name of the shell using tty and then redirect the output of the first terminal session to the second terminal session, that works only when executing single commands, but not mid executing of the two scripts.
This is the first script:
answer="n"
while [ "$answer" != 'y' ];do
echo "enter the first value :"
read first
echo "the output is: "
echo 6
echo "enter value of A:"
read A
echo "do you want to exit"
read answer
done
The second script is the same:
answer="n"
while [ "$answer" != 'y' ];do
echo "enter the first value :"
read first
echo "the output is: "
echo 6
echo "enter value of A:"
read A
echo "do you want to exit"
read answer
done
I want the first script running in the first terminal to output the number 6 and then pipe the number to the second script to be placed in the variable first and then let the two scripts continue executing in their respective terminals.
A named pipe is the appropriate tool. Thus, in the first script:
#!/usr/bin/env bash
my_fifo=~/.my_ipc_fifo
mkfifo "$my_fifo" || exit
exec {out_to_fifo}>"$my_fifo" || exit
answer="n"
while [ "$answer" != 'y' ];do
echo "enter the first value :"
read first
echo "the output is: "
echo 6 # one copy for the user
printf '%s\0' 6 >&$out_to_fifo # one copy for the other program
echo "enter value of A:"
read A
printf '%s\0' "$A" >&$out_to_fifo
echo "do you want to exit"
read answer
done
...and in the second:
#!/usr/bin/env bash
my_fifo=~/.my_ipc_fifo
exec {in_from_fifo}<"$my_fifo" || exit # note that the first one needs to be started first!
while IFS= read -r -d '' first <&"$in_from_fifo"; do
echo "Read an input value from the other program of: $first"
read -r -d '' second <&"$in_from_fifo"
echo "Read another value of: $second"
read -p "Asking the user, not the FIFO: Do you want to exit? " exit_answer
case $exit_answer in [Yy]*) exit;; esac
done
With bash 4.1.2 and 4.3.48, the following script gives the expected output:
#!/bin/bash
returnSimple() {
local __resultvar=$1
printf -v "$__resultvar" '%s' "ERROR"
echo "Hello World"
}
returnSimple theResult
echo ${theResult}
echo Done.
Output as expected:
$ ./returnSimple
Hello World
ERROR
Done.
However, when stdout from the function is piped to another process, the assignment of the __resultvar variable does not work anymore:
#!/bin/bash
returnSimple() {
local __resultvar=$1
printf -v "$__resultvar" '%s' "ERROR"
echo "Hello World"
}
returnSimple theResult | cat
echo ${theResult}
echo Done.
Unexpected Output:
$ ./returnSimple
Hello World
Done.
Why does printf -v not work in the second case? Should printf -v not write the value into the result variable independent of whether the output of the function is piped to another process?
See man bash, section on Pipelines:
Each command in a pipeline is executed as a separate process (i.e., in a subshell).
That's why when you write cmd | cat, cmd receives a copy of variable that it can't modify.
A simple demo:
$ test() ((a++))
$ echo $a
$ test
$ echo $a
1
$ test | cat
$ echo $a
1
Interestingly enough, the same also happens when using eval $__resultvar="'ERROR'" instead of the printf -v statement. Thus, this is not a printf related issue.
Instead, adding a echo $BASH_SUBSHELL to both the main script and the function shows that the shell spawns a sub shell in the second case - since it needs to pipe the output from the function to another process. Hence the function runs in a sub shell:
#!/bin/bash
returnSimple() {
local __resultvar=$1
echo "Sub shell level: $BASH_SUBSHELL"
printf -v "$__resultvar" '%s' "ERROR"
}
echo "Sub shell level: $BASH_SUBSHELL"
returnSimple theResult | cat
echo ${theResult}
echo Done.
Output:
% ./returnSimple.sh
Sub shell level: 0
Sub shell level: 1
Done.
This is the reason why any variable assignments from within the function are not passed back to the calling script.
Below is the code of bash:
a=`echo hello`
echo $a
output is :
hello
But I think it should be:
hello
0
You think wrong ;-)
Putting the command in backticks assigns the output (stdout) from the expression on the right to the variable on the left.
$? gives you the "output status" (or return code) of the command - aka the "0" you were expecting.
So:
a=`echo hello`
Runs the command "echo hello" but instead of echoing to stdout, it "echoes" to varaiable a. So a=whatever_the_command_would_have_written_to_stdout (in this case "hello") - nothing is actually written to stdout because it is "captured" by the ``s
You mistakenly think that a=`echo hello`:
executes echo hello and prints its stdout output directly to the caller's stdout,
and then assigns the exit code (return value) of the echo command to variable $a.
Neither is true; instead:
echo hello's stdout output is captured in memory (without printing to the caller's stdout; that's how command substitutions work),
and that captured output is assigned to $a.
A command's exit code (a return value indicating success vs. failure) is never directly returned in POSIX-like shells such as Bash.
The only way to use an exit code is either:
explicitly, by accessing special variable $? immediately after the command ($? contains the most recent command's exit code)
implicitly, in conditionals (a command whose exit code is 0 evaluates to true in a conditional, any other exit code implies false).
Thus, to achieve what you're really trying to do, use:
echo 'hello' # Execute a command directly (its stdout output goes to the caller's stdout)
a=$? # Save the previous command's exit code in var. $a
echo "$a" # Echo the saved exit code.
As this [ this ] answer already mentioned, the return value for the last executed command is stored in
$? # ${?} is sometimes needed
If you wish a to contain 'hello' and the return value of echo hello in separate lines, ie
hello
0
below is one way to do it
$ a=`echo -en "hello\n" && echo -n $?` # here $? is ret val for 1st echo
$ echo -e "$a"
hello
0
Note
-n with echo suppresses the trailing new line
-e with echo interprets escape sequences
Since && is the logical and operator, the second echo wouldn't have been executed had the first echo failed
Another important point to note is that even the assignment ie
a=b
has a return value.
Lets say a program that outputs a zero in case of success, or 1 in case of failure, like this:
main () {
if (task_success())
return 0;
else
return 1;
}
Similar with Python, if you execute exit(0) or exit(1) to indicate the result of running a script. How do you know what the program outputs when you run it in shell. I tried this:
./myprog 2> out
but I do not get the result in the file.
There's a difference between an output of a command, and the exit code of a command.
What you ran ./myprog 2> out captures the stderr of the command and not the exit code as you showed above.
If you want to check the exit code of the a program in bash/shell you need to use the $? operator which captures the last command exit code.
For example:
./myprog 2> out
echo $?
Will give you the exit code of the command.
BTW,
For capturing the output of a command, you may need to use 1 as your redirect where 1 captures stdout and 2 captures stderr.
The returnvalue of a command is stored in $?. When you want to do something with the returncode, it is best to store it in a variable before you call another command. The other command will set a new returncode in $?.
In the next code the echo will reset the value of $?.
rm this_file_doesnt_exist
echo "First time $? displays the rm result"
echo "Second time $? displays the echo result"
rm this_file_doesnt_exist
returnvalue_rm=$?
echo "rm returned with ${returnvalue}"
echo "rm returned with ${returnvalue}"
When you are interested in stdout/stderr as well, you can redirect them to a file. You can also capture them in a shell variable and do something with it:
my_output=$(./myprog 2>&1)
returnvalue_myprog=$?
echo "Use double quotes when you want to show the ${my_output} in an echo."
case ${returnvalue_myprog} in
0) echo "Finally my_prog is working"
;;
1) echo "Retval 1, something you give in your program like input not found"
;;
*) echo "Unexpected returnvalue ${returnvalue_myprog}, errors in output are:"
echo "${my_output}" | grep -i "Error"
;;
esac
I've got two sh files, which are "main.sh" and "sub.sh" i want to return a variable's value inside "sub.sh" and use it inside main.sh .There is so many "echo" command so i can't just return the value from sub.sh file. I need only one variable's value. How can that be possible?
main.sh
echo "start"
//how to get a variable from the sh below?
//dene=$(/root/sub.sh)
echo "finish"
sub.sh
echo "sub function"
a="get me out of there" // i want to return that variable from script
echo "12345"
echo "kdsjfkjs"
To "send" the variable, do this:
echo MAGIC: $a
To "receive" it:
dene=$(./sub.sh | sed -n 's/^MAGIC: //p')
What this does is to discard all lines that don't start with MAGIC: and print the part after that token when a match is found. You can substitute your own special word instead of MAGIC.
Edit: or you could do it by "source"ing the sub-script. That is:
source sub.sh
dene=$a
What that does is to run sub.sh in the context of main.sh, as if the text were just copy-pasted right in. Then you can access the variables and so on.
main.sh
#!/bin/sh
echo "start"
# Optionally > /dev/null to suppress output of script
source /root/sub.sh
# Check if variable a is defined and contains sth. and print it if it does
if [ -n "${a}" ]; then
# Do whatever you want with a at this point
echo $a
fi
echo "finish"
sub.sh
#!/bin/sh
echo "sub function"
a="get me out of there"
echo "12345"
echo -e "kdsjfkjs"
exit 42
You can export variable to shell session in sub.sh and catch it later in main.sh.
sub.sh
#!/usr/bin/sh
export VARIABLE="BLABLABLA"
main.sh
#!/bin/sh
. ./sub.sh
echo $VARIABLE