Getting exit code of last shell command in another script - bash

I am trying to beef up my notify script. The way the script works is that I put it behind a long running shell command and then all sorts of notifications get invoked after the long running script finished.
For example:
sleep 100; my_notify
It would be nice to get the exit code of the long running script. The problem is that calling my_notify creates a new process that does not have access to the $? variable.
Compare:
~ $: ls nonexisting_file; echo "exit code: $?"; echo "PPID: $PPID"
ls: nonexisting_file: No such file or directory
exit code: 1
PPID: 6203
vs.
~ $: ls nonexisting_file; my_notify
ls: nonexisting_file: No such file or directory
exit code: 0
PPID: 6205
The my_notify script has the following in it:
#!/bin/sh
echo "exit code: $?"
echo "PPID: $PPID"
I am looking for a way to get the exit code of the previous command without changing the structure of the command too much. I am aware of the fact that if I change it to work more like time, e.g. my_notify longrunning_command... my problem would be solved, but I actually like that I can tack it at the end of a command and I fear complications of this second solution.
Can this be done or is it fundamentally incompatible with the way that shells work?
My shell is Z shell (zsh), but I would like it to work with Bash as well.

You'd really need to use a shell function in order to accomplish that. For a simple script like that it should be pretty easy to have it working in both zsh and bash. Just place the following in a file:
my_notify() {
echo "exit code: $?"
echo "PPID: $PPID"
}
Then source that file from your shell startup files. Although since that would be run from within your interactive shell, you may want to use $$ rather than $PPID.

It is incompatible. $? only exists within the current shell; if you want it available in subprocesses then you must copy it to an environment variable.
The alternative is to write a shell function that uses it in some way instead.

One method to implement this could be to use EOF tag and a master script which will create your my_notify script.
#!/bin/bash
if [ -f my_notify ] ; then
rm -rf my_notify
fi
if [ -f my_temp ] ; then
rm -rf my_temp
fi
retval=`ls non_existent_file &> /dev/null ; echo $?`
ppid=$PPID
echo "retval=$retval"
echo "ppid=$ppid"
cat >> my_notify << 'EOF'
#!/bin/bash
echo "exit code: $retval"
echo " PPID =$ppid"
EOF
sh my_notify
You can refine this script for your purpose.

Related

What is the fish equivalent to a bash if/then/else/fi block?

I am using termux and it has no init system, i found a script to start crond when i start the app
if ! pgrep -f "crond" >/dev/null; then
echo "[Starting crond...]" && crond && echo "[OK]"
else
echo "[crond is running]"
fi
this code worked perfectly for bash shell.
I am currently using fish shell and tried using the same code in the fish equivalent of bash_profile AKA config.fish however, i got the error message
Missing end to balance this if statement
if ! pgrep -f "crond" >/dev/null; then
^
from sourcing file ~/.config/fish/config.fish
called during startup
Please help me with translations, I'm reading through fish docs however it will take me a long time to get it right.
Honestly, you haven't made any effort to learn anything about the fish shell. You should start with the tutorial. There you will learn that if blocks look like this:
if pgrep -f "crond" >/dev/null
do_something
end
This answer by glenn-jackman is very helpful https://stackoverflow.com/a/29671880/5257034
i am able to run the code in config.fish without issues
my code
if ! pgrep -f $crond >/dev/null
echo "[Starting crond...]"; and crond; and echo "[OK]"
else
echo "[crond is running]"
end

How to check the current shell and change it to bash via script?

#!/bin/bash
if [ ! -f readexportfile ]; then
echo "readexportfile does not exist"
exit 0
fi
The above is part of my script. When the current shell is /bin/csh my script fails with the following error:
If: Expression Syntax
Then: Command not found
If I run bash and then run my script, it runs fine(as expected).
So the question is: If there is any way that myscript can change the current shell and then interpretate rest of the code.
PS: If i keep bash in my script, it changes the current shell and rest of the code in script doesn't get executed.
The other replies are correct, however, to answer your question, this should do the trick:
[[ $(basename $SHELL) = 'bash' ]] || exec /bin/bash
The exec builtin replaces the current shell with the given command (in this case, /bin/bash).
You can use SHEBANG(#!) to overcome your issue.
In your code you are already using she-bang but make sure it is first and foremost line.
$ cat test.sh
#!/bin/bash
if [ ! -f readexportfile ]; then
echo "readexportfile does not exist"
exit 0
else
echo "No File"
fi
$ ./test.sh
readexportfile does not exist
$ echo $SHELL
/bin/tcsh
In the above code even though I am using CSH that code executed as we mentioned shebang in the code. In case if there is no shebang then it will take the help of shell in which you are already logged in.
In you case you also check the location of bash interpreter using
$ which bash
or
$ cat /etc/shells |grep bash

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.

Determining whether shell script was executed "sourcing" it

Is it possible for a shell script to test whether it was executed through source? That is, for example,
$ source myscript.sh
$ ./myscript.sh
Can myscript.sh distinguish from these different shell environments?
I think, what Sam wants to do may be not possible.
To what degree a half-baken workaround is possible, depends on...
...the default shell of users, and
...which alternative shells they are allowed to use.
If I understand Sam's requirement correctly, he wants to have a 'script',
myscript, that is...
...not directly executable via invoking it by its name myscript
(i.e. that has chmod a-x);
...not indirectly executable for users by invoking sh myscript or
invoking bash myscript
...only running its contained functions and commands if invoked by
sourcing it: . myscript
The first things to consider are these
Invoking a script directly by its name (myscript) requires a first line in
the script like #!/bin/bash or similar. This will directly determine which
installed instance of the bash executable (or symlink) will be invoked to run
the script's content. This will be a new shell process. It requires the
scriptfile itself to have the executable flag set.
Running a script by invoking a shell binary with the script's (path+)name as
an argument (sh myscript), is the same as '1.' -- except that the
executable flag does not need to be set, and said first line with the
hashbang isn't required either. The only thing needed is that the invoking
user needs read access to the scriptfile.
Invoking a script by sourcing its filename (. myscript) is very much the
same as '1.' -- exept that it isn't a new shell that is invoked. All the
script's commands are executed in the current shell, using its environment
(and also "polluting" its environment with any (new) variables it may set or
change. (Usually this is a very dangerous thing to do: but here it could be
used to execute exit $RETURNVALUE under certain conditions....)
For '1.':
Easy to achieve: chmod a-x myscript will prevent myscript from being
directly executable. But this will not fullfill requirements '2.' and '3.'.
For '2.' and '3.':
Much harder to achieve. Invokations by sh myscript require reading
privileges for the file. So an obvious way out would seem to chmod a-r
myscript. However, this will also dis-allow '3.': you will not be able to
source the script either.
So what about writting the script in a way that uses a Bashism? A Bashism is a
specific way to do something which other shells do not understand: using
specific variables, commands etc. This could be used inside the script to
discover this condition and "do something" about it (like "display warning.txt",
"mailto admin" etc.). But there is no way in hell that this will prevent sh or
bash or any other shell from reading and trying to execute all the following
commands/lines written into the script unless you kill the shell by invoking
exit.
Examples: in Bash, the environment seen by the script knows of $BASH,
$BASH_ARGV, $BASH_COMMAND, $BASH_SUBSHELL, BASH_EXECUTION_STRING... . If
invoked by sh (also if sourced inside a sh), the executing shell will see
all these $BASH_* as empty environment variables. Again, this could be used
inside the script to discover this condition and "do something"... but not
prevent the following commands from being invoked!
I'm now assuming that...
...the script is using #!/bin/bash as its first line,
...users have set Bash as their shell and are invoking commands in the
following table from Bash and it is their login shell,
...sh is available and it is a symlink to bash or dash.
This will mean the following invokations are possible, with the listed values
for environment variables
vars+invok's | ./scriptname | sh scriptname | bash scriptname | . scriptname
---------------+--------------+---------------+-----------------+-------------
$0 | ./scriptname | ./scriptname | ./scriptname | -bash
$SHLVL | 2 | 1 | 2 | 1
$SHELLOPTS | braceexpand: | (empty) | braceexpand:.. | braceexpand:
$BASH | /bin/bash | (empty) | /bin/bash | /bin/bash
$BASH_ARGV | (empty) | (empty) | (empty) | scriptname
$BASH_SUBSHELL | 0 | (empty) | 0 | 0
$SHELL | /bin/bash | /bin/bash | /bin/bash | /bin/bash
$OPTARG | (empty) | (empty) | (emtpy) | (emtpy)
Now you could put a logic into your text script:
If $0 is not equal to -bash, then do an exit $SOMERETURNVALUE.
In case the script was called via sh myscript or bash myscript, then it will
exit the calling shell. In case it was run in the current shell, it will
continue to run. (Warning: in case the script has any other exit statements,
your current shell will be 'killed'...)
So put into your non-executable myscript.txt near its beginning something like
this may do something close to your goal:
echo BASH=$BASH
test x${BASH} = x/bin/bash && echo "$? : FINE.... You're using 'bash ...'"
test x${BASH} = x/bin/bash || echo "$? : RATS !!! -- You're not using BASH and I will kick you out!"
test x${BASH} = x/bin/bash || exit 42
test x"${0}" = x"-bash" && echo "$? : FINE.... You've sourced me, and I'm your login shell."
test x"${0}" = x"-bash" || echo "$? : RATS !!! -- You've not sourced me (or I'm not your bash login shell) and I will kick you out!"
test x"${0}" = x"-bash" || exit 33
This may or may not be what the asker wanted but, on a similar situation, I wanted a script to indicate that it is meant to be sourced and not directly run.
To achieve this effect my script reads:
#!/bin/echo Should be run as: source
export SOMEPATH="/some/path/on/my/system"
echo "Your environment has been set up"
So when I run it either as a command or sourced I get:
$ ./myscript.sh
Should be run as: source ./myscript.sh
$ source ./myscript.sh
Your environment has been set up
You can of course fool the script by running it as sh ./myscript.sh, but at least it gives the correct expected behaviour on 2 out of 3 cases.
This is what I was looking for:
[[ ${BASH_SOURCE[0]} = $0 ]] && main "$#"
I cannot add comment yet (stackexchange policies) so I add my own answer:
This one may works regardless if we do:
bash scriptname
scriptname
./scriptname.
on both bash and mksh.
if [ "${0##/*}" == scriptname ] # if the current name is our script
then
echo run
else
echo sourced
fi
If you have a non-altering file path for regular users, then:
if [ "$(/bin/readlink -f "$0")" = "$KNOWN_PATH_OF_THIS_FILE" ]; then
# the file was executed
else
# the file was sourced
fi
(it can also easily be loosened to only check for the filename or whatever).
But your users need to have read permission to be able to source the file, so absolutely nothing can stop them from doing what they want with the file. But it might help them out to not use it in the wrong way.
This solution is not dependent on Bashisms.
Yes it is possible. In general you can do the following:
#! /bin/bash
sourced () {
echo Sourced
}
executed () {
echo Executed
}
if [[ ${0##*/} == -* ]]; then
sourced
else
executed $#
fi
Giving the following output:
$ ./myscript
Executed
$ . ./myscript
Sourced
Based on Kurt Pfeifle’s answer, this works for me
if [ $SHLVL = 1 ]
then
echo 'script was sourced'
fi
Example
Since all of our machines have history, I did this:
check_script_call=$(history |tail -1|grep myscript.sh )
if [ -z "$check_script_call" ];then
echo "This file should be called as a source."
echo "Please, try again this way:"
echo "$ source /path/to/myscript.sh"
exit 1
fi
Everytime you run a script (without source), your shell creates a new env without history.
If you want to care about performance you can try this:
if ! history |tail -1|grep set_vars ;then
echo -e "This file should be called as a source.\n"
echo "Please, try again this way:"
echo -e "$ source /path/to/set_vars\n"
exit 1
fi
PS: I think Kurt's answer is much more complete but I think this could help.
In the first case, $0 will be "myscript.sh". In the second case, it will be "./myscript". But, in general, there's no way to tell source was used.
If you tell us what you're trying to do, instead of how you want to do it, a better answer might be forthcoming.

Shell scripting: die on any error

Suppose a shell script (/bin/sh or /bin/bash) contained several commands. How can I cleanly make the script terminate if any of the commands has a failing exit status? Obviously, one can use if blocks and/or callbacks, but is there a cleaner, more concise way? Using && is not really an option either, because the commands can be long, or the script could have non-trivial things like loops and conditionals.
With standard sh and bash, you can
set -e
It will
$ help set
...
-e Exit immediately if a command exits with a non-zero status.
It also works (from what I could gather) with zsh. It also should work for any Bourne shell descendant.
With csh/tcsh, you have to launch your script with #!/bin/csh -e
May be you could use:
$ <any_command> || exit 1
You can check $? to see what the most recent exit code is..
e.g
#!/bin/sh
# A Tidier approach
check_errs()
{
# Function. Parameter 1 is the return code
# Para. 2 is text to display on failure.
if [ "${1}" -ne "0" ]; then
echo "ERROR # ${1} : ${2}"
# as a bonus, make our script exit with the right error code.
exit ${1}
fi
}
### main script starts here ###
grep "^${1}:" /etc/passwd > /dev/null 2>&1
check_errs $? "User ${1} not found in /etc/passwd"
USERNAME=`grep "^${1}:" /etc/passwd|cut -d":" -f1`
check_errs $? "Cut returned an error"
echo "USERNAME: $USERNAME"
check_errs $? "echo returned an error - very strange!"

Resources