start-stop-daemon alway returns success - bash

I have the following piece of code in a BASH script:
start-stop-daemon --start --quiet --background \
--startas /bin/bash -- -c "$CMD; exec echo $?> $FILE"
where CMD is some command string and FILE is an output file location. For some reason though, the output in FILE is always 0, even when it should give a different number (1 for error, etc).
Why is that, and how do I fix it?

Your $? gets expanded in the context of the current shell together with $CMD and $FILE.
To fix this you'll have to escape it like \$?, i.e.
... /bin/bash -c "$CMD; exec echo \$? >$FILE"

exec echo $? this will print 0 if your last command was run successfully. and that is why it prints 0 in file. what are you trying to achieve.

Related

bash get exitcode of su script execution

I have a shell script when need to run as a particular user. So I call that script as below,
su - testuser -c "/root/check_package.sh | tee -a /var/log/check_package.log"
So after this when I check the last execution exitcode it returns always 0 only even if that script fails.
I tried something below also which didn't help,
su - testuser -c "/root/check_package.sh | tee -a /var/log/check_package.log && echo $? || echo $?"
Is there way to get the exitcode of command whatever running through su.
The problem here is not su, but tee: By default, the shell exits with the exit status of the last pipeline component; in your code, that component is not check_package.sh, but instead is tee.
If your /bin/sh is provided by bash (as opposed to ash, dash, or another POSIX-baseline shell), use set -o pipefail to cause the entirely pipeline to fail if any component of it does:
su - testuser -c "set -o pipefail; /root/check_package.sh | tee -a /var/log/check_package.log"
Alternately, you can do the tee out-of-band with redirection to a process substitution (though this requires your current user to have permission to write to check_package.log):
su - testuser -c "/root/check_package.sh" > >(tee -a /var/log/check_package.log
Both su and sudo exit with the exit status of the command they execute (if authentication succeeded):
$ sudo false; echo $?
1
$ su -c false; echo $?
1
Your problem is that the command pipeline that su runs is a pipeline. The exit status of your pipeline is that of the tee command (which succeeds), but what you really want is that of the first command in the pipeline.
If your shell is bash, you have a couple of options:
set -o pipefail before your pipeline, which will make it return the rightmost failure value of all the commands if any of them fail
Examine the specific member of the PIPESTATUS array variable - this can give you the exit status of the first command whether or not tee succeeds.
Examples:
$ sudo bash -c "false | tee -a /dev/null"; echo $?
0
$ sudo bash -c "set -o pipefail; false | tee -a /dev/null"; echo $?
1
$ sudo bash -c 'false | tee -a /dev/null; exit ${PIPESTATUS[0]}'; echo $?
1
You will get similar results using su -c, if your system shell (in /bin/sh) is Bash. If not, then you'd need to explicitly invoke bash, at which point sudo is clearly simpler.
I was facing a similar issue today, in case the topic is still open here my solution, otherwise just ignore it...
I wrote a bash script (let's say my_script.sh) which looks more or less like this:
### FUNCTIONS ###
<all functions listed in the main script which do what I want...>
### MAIN SCRIPT ### calls the functions defined in the section above
main_script() {
log_message "START" 0
check_env
check_data
create_package
tar_package
zip_package
log_message "END" 0
}
main_script |tee -a ${var_log} # executes script and writes info into log file
var_sta=${PIPESTATUS[0]} # captures status of pipeline
exit ${var_sta} # exits with value of status
It works when you call the script directly or in sudo mode

Why does bash -c "false; echo $?" print 0?

I'm building a script that tries to run some commands on a server (over SSH), and writes on the screen whether they were successful.
I noticed a strange behaviour for $?, namely not being 0 when the previous command failed.
Initially, I had:
ssh <user>#<server> <<EOF
false
if [ $? -ne 0 ]; then
echo "It failed"
else
echo "It worked"
fi
EOF
If I copy and paste the script inside <<EOF and EOF, it prints It failed. If I run it with the ssh part, it prints It worked. To simplify, I then tried:
ssh <user>#<server> <<EOF
false
echo $?
EOF
Same thing happened. If I copy-paste or type the commands inside, it prints 1, but if I run all of it (including the ssh), it prints 0.
The same error happens if I directly use bash this way
bash <<EOF
false
echo $?
EOF
or
bash -c "false; echo $?"
Why does this happen? How can I check if the previous command failed in this context?
This is due to variable expansion. When you write bash -c "false; echo $?" the variable is expanded before the commands are ran. So your command is exactly like bash -c "false; echo 0;" if your previous command was successful.
To have the right result try bash -c 'false; echo $?'. This prevents variable expansion, it will be expanded when interpreted.
For the here document version do:
bash << 'EOF'
false
echo $?
'EOF'
In this case you need to quote the delimiter of the here document. But beware that the syntax you must use is the syntax for the shell you use to type the command. In the example, I was in tcsh , and it requires to use the exact same opening and closing delimiter. Under bash, the closing delimiter must be the opening one after quote removal.

Simple bash script for starting application silently

Here I am again. Today I wrote a little script that is supposed to start an application silently in my debian env.
Easy as
silent "npm search 1234556"
This works but not at all.
As you can see, I commented the section where I have some troubles.
This line:
$($cmdLine) &
doesn't hide application output but this one
$($1 >/dev/null 2>/dev/null) &
works perfectly. What am I missing? Many thanks.
#!/bin/sh
# Daniele Brugnara
# October, 2013
# Silently exec a command line passed as argument
errorsRedirect=""
if [ -z "$1" ]; then
echo "Please, don't joke me..."
exit 1
fi
cmdLine="$1 >/dev/null"
# if passed a second parameter, errors will be hidden
if [ -n "$2" ]; then
cmdLine="$cmdLine 2>/dev/null"
fi
# not working
$($cmdLine) &
# works perfectly
#$($1 >/dev/null 2>/dev/null) &
With the use of evil eval following script will work:
#!/bin/sh
# Silently exec a command line passed as argument
errorsRedirect=""
if [ -z "$1" ]; then
echo "Please, don't joke me..."
exit 1
fi
cmdLine="$1 >/dev/null"
# if passed a second parameter, errors will be hidden
if [ -n "$2" ]; then
cmdLine="$cmdLine 2>&1"
fi
eval "$cmdLine &"
Rather than building up a command with redirection tacked on the end, you can incrementally apply it:
#!/bin/sh
if [ -z "$1" ]; then
exit
fi
exec >/dev/null
if [ -n "$2" ]; then
exec 2>&1
fi
exec $1
This first redirects stdout of the shell script to /dev/null. If the second argument is given, it redirects stderr of the shell script too. Then it runs the command which will inherit stdout and stderr from the script.
I removed the ampersand (&) since being silent has nothing to do with running in the background. You can add it back (and remove the exec on the last line) if it is what you want.
I added exec at the end as it is slightly more efficient. Since it is the end of the shell script, there is nothing left to do, so you may as well be done with it, hence exec.
& means that you're doing sort of multitask whereas
1 >/dev/null 2>/dev/null
means that you redirect the output to a sort of garbage and that's why you don't see anything.
Furthermore cmdLine="$1 >/dev/null" is incorrect, you should use ' instead of " :
cmdLine='$1 >/dev/null'
you can build your command line in a var and run a bash with it in background:
bash -c "$cmdLine"&
Note that it might be useful to store the output (out/err) of the program, instead of trow them in null.
In addition, why do you need errorsRedirect??
You can even add a wait at the end, just to be safe...if you want...
#!/bin/sh
# Daniele Brugnara
# October, 2013
# Silently exec a command line passed as argument
[ ! $1 ] && echo "Please, don't joke me..." && exit 1
cmdLine="$1>/dev/null"
# if passed a second parameter, errors will be hidden
[ $2 ] && cmdLine+=" 2>/dev/null"
# not working
echo "Running \"$cmdLine\""
bash -c "$cmdLine" &
wait

Execute script inside another exits outer script

I try to launch another app inside a bash script, but the app seems to exit my script so that the line exec $HOME/bin/sync-iosbeta; does not get executed. I have tried to put it outside the if as well.
if $HOME/bin/BetaBuilder.app/Contents/MacOS/BetaBuilder --args -i "${zip}" -o "${odir}" -u "${ourl}" -r "$PROJECT_FOLDER/README.txt" ; then
echo "Wil sync"
exec $HOME/bin/sync-iosbeta;
fi
echo "This text does not get printed either..";
I have also tried to use open to kick off the app, but then I have issues with passing the arguments, even with --args set.
I am running on Mac OS.
From the exec manual:
If command is specified, it replaces the shell. No new process is created.
Just remove exec and the ";":
if $HOME/bin/BetaBuilder.app/Contents/MacOS/BetaBuilder --args -i "${zip}" -o "${odir}" -u "${ourl}" -r "$PROJECT_FOLDER/README.txt" ; then
echo "Wil sync"
$HOME/bin/sync-iosbeta
fi
echo "This text does not get printed either..";
If sync-iosbeta is not being executed, then may be it hasn't the right permissions. Try:
if $HOME/bin/BetaBuilder.app/Contents/MacOS/BetaBuilder --args -i "${zip}" -o "${odir}" -u "${ourl}" -r "$PROJECT_FOLDER/README.txt" ; then
echo "Wil sync"
/bin/sh $HOME/bin/sync-iosbeta
fi
echo "This text does not get printed either..";
That's the whole point of exec. man bash: If command is specified, it replaces the shell.
just remove exec.

Difference between "echo 'hello'; ls" vs "echo 'hello' && ls"?

I wonder what the difference between
"echo 'hello'; ls"
and
"echo 'hello' && ls"
is? they both do the same thing
"echo 'hello' && ls" means : execute "ls" if "echo 'hello'" runs successfully. To understand what is "successful" in bash. Try this :
bash> cd /
bash> echo $?
if the previous command runs successfully, you should see 0
After that, try this :
bash> asdfdf
bash> echo $?
You should see a non-zero value between 1 and 255. This means previous command didn't run successfully
On the other hand, "echo 'hello'; ls" means execute "ls" whether "echo 'hello'" runs successfully or not.
The && is the logical AND operator. The idea in its use in command1 && command2 is that command2 is only evaluated/run if command1 was successful. So here ls will only be run if the echo command returned successful (which will always be the case here, but you never know ;-). You could also write this as:
if echo 'hello'
then
ls
fi
The semicolon just delimits two commands. So you could also write echo 'hello' ; ls as:
echo 'hello'
ls
Thus ls will also be executed even when echo fails.
BTW, successful in this context means that the program was exited with something like exit(0) and thus returned 0 as a return code (the $? shell variable tells you the return status of the last executed command).
To supplement DarkDust's answer:
So here ls will only be run if the echo command returned successful (which will always be the case here, but you never know ;-).
Well, not always. For example, the standard output stream may be unwritable
:; ( echo "foo" && touch /tmp/succeeded) >/dev/full
bash: echo: write error: No space left on device
:; ls -l /tmp/succeeded
ls: cannot access /tmp/succeeded: No such file or directory
Here, they do the same.
But take an other example:
cp file target && ls
cp file target; ls
In the first case, ls would only be executed if cp succeeds.
In the second case, no matter if cp fails or succeeds, ls will always be executed.

Resources