bash get an exit code from a subshell and pipe [duplicate] - bash

This question already has answers here:
Get exit code from subshell through the pipes
(3 answers)
Closed 8 years ago.
I have the following bash script java_debug to log all java executions (standard and error console):
#! /bin/bash
echo param1: $1
echo param2: $2
(java HelloWorld "$#" 2>&1 ) | tee /tmp/log.txt
I run it:
$ java_debug v1 "v2 with space"
param1: v1
param2: v2 wirh space
Error: Could not find or load main class HelloWorld
$ echo $?
0
in this example, java cannot find the HelloWorld class, and so it shows an error.
However, the error is lost in $? (we get 0 instead of 1) because of the subshell and/or the pipe.
I need that java_debug returns the same exit code as the java execution
How to fix this script?
note: I could use the script command instead of 2>&1 | tee, but unfortunately the implementation of the script command changes in different systems (the script parameters are not the same in redhat than in OSX).
note: I am aware that bash is an horrible language and it should not be used; but I have no choice in this case.

found answer here: Get exit code from subshell through the pipes
in my case:
#! /bin/bash
echo param1: $1
echo param2: $2
(java HelloWorld "$#" 2>&1 ) | tee /tmp/log.txt
exit ${PIPESTATUS[0]}

Related

Exit Code of command is 0, but binaries exit code is 3 [duplicate]

This question already has answers here:
Pipe output and capture exit status in Bash
(16 answers)
Closed 8 years ago.
I made a simple script:
$ more test.bash
#!/bin/bash
echo test
exit 1
When I run the script , the exit status should be 1
$ /tmp/test.bash
echo $?
1
But when I run this as the following
/tmp/test.bash | tr -d '\r' 1>>$LOG 2>>$LOG
echo $?
0
The exit status is 0, (not as expected 1)
It seems that the exit status comes from tr command.
But I what I want is to get the exit status from the script - test.bash.
What do I need to add/change in my syntax in order to get the right exit status from the script, and not from the command after the pipe line?
Use the PIPESTATUS array:
$ ls foo | cat
ls: foo: No such file or directory
$ echo ${PIPESTATUS[0]} ${PIPESTATUS[1]}
2 0
Note: PIPESTATUS is a bashism (i.e. not POSIX).

Copy *unbuffered* stdout to file from within bash script itself

I want to copy stdout to a log file from within a bash script, meaning I don't want to call the script with output piped to tee, I want the script itself to handle it. I've successfully used this answer to accomplish this, using the following code:
#!/bin/bash
exec > >(sed "s/^/[${1}] /" | tee -a myscript.log)
exec 2>&1
# <rest of script>
echo "hello"
sleep 10
echo "world"
This works, but has the downside of output being buffered until the script is completed, as is also discussed in the linked answer. In the above example, both "hello" and "world" will show up in the log only after the 10 seconds have passed.
I am aware of the stdbuf command, and if running the script with
stdbuf -oL ./myscript.sh
then stdout is indeed continuously printed both to the file and the terminal.
However, I'd like this to be handled from within the script as well. Is there any way to combine these two solutions? I'd rather not resort to a wrapper script that simply calls the original script enclosed with "stdbuf -oL".
You can use a workaround and make the script execute itself with stdbuf, if a special argument is present:
#!/bin/bash
if [[ "$1" != __BUFFERED__ ]]; then
prog="$0"
stdbuf -oL "$prog" __BUFFERED__ "$#"
else
shift #discard __BUFFERED__
exec > >(sed "s/^/[${1}] /" | tee -a myscript.log)
exec 2>&1
# <rest of script>
echo "hello"
sleep 1
echo "world"
fi
This will mostly work:
if you run the script with ./test, it shows unbuffered [] hello\n[] world.
if you run the script with ./test 123 456, it shows [123] hello\n[123] world like you want.
it won't work, however, if you run it with bash test - $0 is set to test which is not your script. Fixing this is not in the scope of this question though.
The delay in your first solution is caused by sed, not by tee. Try this instead:
#!/bin/bash
exec 6>&1 2>&1>&>(tee -a myscript.log)
To "undo" the tee effect:
exec 1>&6 2>&6 6>&-

Bash script: how to get the whole command line which ran the script

I would like to run a bash script and be able to see the command line used to launch it:
sh myscript.sh arg1 arg2 1> output 2> error
in order to know if the user used the "std redirection" '1>' and '2>', and therefore adapt the output of my script.
Is it possible with built-in variables ??
Thanks.
On Linux and some unix-like systems, /proc/self/fd/1 and /proc/self/fd/2 are symlinks to where your std redirections are pointing to. Using readlink, we can query if they were redirected or not by comparing them to the parent process' file descriptor.
We will however not use self but $$ because $(readlink /proc/"$$"/fd/1) spawns a new shell so self would no longer refer to the current bash script but to a subshell.
$ cat test.sh
#!/usr/bin/env bash
#errRedirected=false
#outRedirected=false
parentStderr=$(readlink /proc/"$PPID"/fd/2)
currentStderr=$(readlink /proc/"$$"/fd/2)
parentStdout=$(readlink /proc/"$PPID"/fd/1)
currentStdout=$(readlink /proc/"$$"/fd/1)
[[ "$parentStderr" == "$currentStderr" ]] || errRedirected=true
[[ "$parentStdout" == "$currentStdout" ]] || outRedirected=true
echo "$0 ${outRedirected:+>$currentStdout }${errRedirected:+2>$currentStderr }$#"
$ ./test.sh
./test.sh
$ ./test.sh 2>/dev/null
./test.sh 2>/dev/null
$ ./test.sh arg1 2>/dev/null # You will lose the argument order!
./test.sh 2>/dev/null arg1
$ ./test.sh arg1 2>/dev/null >file ; cat file
./test.sh >/home/camusensei/file 2>/dev/null arg1
$
Do not forget that the user can also redirect to a 3rd file descriptor which is open on something else...!
Not really possible. You can check whether stdout and stderr are pointing to a terminal: [ -t 1 -a -t 2 ]. But if they do, it doesn't necessarily mean they weren't redirected (think >/dev/tty5). And if they don't, you can't distinguish between stdout and stderr being closed and them being redirected. And even if you know for sure they are redirected, you can't tell from the script itself where they point after redirection.

korn vs bash exit code

I have the following problem:
I have some test scripts that previously were run with ksh (from MKS Toolkit)
The scripts will need to remain unmodified (not a single character will be changed).
I will use bash (from Mingw/Msys) to run these scripts.
The problem is in the following:
This works fine in KornShell (ksh) but doesn't work in bash:
typeset -i errorCode
errorCode=10
exit errorCode
I need to modify the script like this (note the $ sign):
typeset -i errorCode
errorCode=10
exit $errorCode
Is there a way to make the code to be compilable with bash without making the chnage I mentioned?
Thanks
Assuming that your code is in a file named foo.sh, you can wrap it with process substitution. For example:
bash <(sed 's/errorCode$/$&/' foo.sh)
Bash interprets the modified code as read from the file descriptor, and the exit status of the subshell is set as expected.
$ echo $?
10
you can also do this before executing the ksh script in bash
#!/bin/bash
exit ()
{
unset -f exit
if expr match "$1" '^[-|+|0-9|.][.0-9]*$' &> /dev/null ; then
exit "$1"
fi
exit
}
source ./the_ksh_script
to override exit.
This example is compatible with the original code, i.e. gives exit code of 0.
Second example
exit () {
unset -f exit
eval rc2=\$$1
rc1=$1
for rc in "$rc1" "$rc2"
do
if expr match "$rc" '^[-|+|0-9|.][.0-9]*$' &> /dev/null ; then
exit "$rc"
fi
done
exit
}
source ./the_ksh_script
will produce the exit code 10, probably what the original script intends.

Pipe command output, but keep the error code [duplicate]

This question already has answers here:
Pipe output and capture exit status in Bash
(16 answers)
Closed 5 years ago.
How do I get the correct return code from a unix command line application after I've piped it through another command that succeeded?
In detail, here's the situation :
$ tar -cEvhf - -I ${sh_tar_inputlist} | gzip -5 -c > ${sh_tar_file} -- when only the tar command fails $?=0
$ echo $?
0
And, what I'd like to see is:
$ tar -cEvhf - -I ${sh_tar_inputlist} 2>${sh_tar_error_file} | gzip -5 -c > ${sh_tar_file}
$ echo $?
1
Does anyone know how to accomplish this?
Use ${PIPESTATUS[0]} to get the exit status of the first command in the pipe.
For details, see http://tldp.org/LDP/abs/html/internalvariables.html#PIPESTATUSREF
See also http://cfajohnson.com/shell/cus-faq-2.html for other approaches if your shell does not support $PIPESTATUS.
Look at $PIPESTATUS which is an array variable holding exit statuses. So ${PIPESTATUS[0]} holds the exit status of the first command in the pipe, ${PIPESTATUS[1]} the exit status of the second command, and so on.
For example:
$ tar -cEvhf - -I ${sh_tar_inputlist} | gzip -5 -c > ${sh_tar_file}
$ echo ${PIPESTATUS[0]}
To print out all statuses use:
$ echo ${PIPESTATUS[#]}
Here is a general solution using only POSIX shell and no temporary files:
Starting from the pipeline:
foo | bar | baz
exec 4>&1
error_statuses=`((foo || echo "0:$?" >&3) |
(bar || echo "1:$?" >&3) |
(baz || echo "2:$?" >&3)) 3>&1 >&4`
exec 4>&-
$error_statuses contains the status codes of any failed processes, in random order, with indexes to tell which command emitted each status.
# if "bar" failed, output its status:
echo $error_statuses | grep '1:' | cut -d: -f2
# test if all commands succeeded:
test -z "$error_statuses"
# test if the last command succeeded:
echo $error_statuses | grep '2:' >/dev/null
As others have pointed out, some modern shells provide PIPESTATUS to get this info. In classic sh, it's a bit more difficult, and you need to use a fifo:
#!/bin/sh
trap 'rm -rf $TMPDIR' 0
TMPDIR=$( mktemp -d )
mkfifo ${FIFO=$TMPDIR/fifo}
cmd1 > $FIFO &
cmd2 < $FIFO
wait $!
echo The return value of cmd1 is $?
(Well, you don't need to use a fifo. You can have the commands early in the pipe echo a status variable and eval that in the main shell, redirecting file descriptors all over the place and basically bending over backwards to check things, but using a fifo is much, much easier.)

Resources