exit of csh shell do not abort the execution immediately - shell

The manual of (t)csh say:
exit [expr]
The shell exits either with the value of the specified expr (an expression, as described under Expressions) or,
without expr, with the value 0.
But if run tcsh -c 'exit 5; echo after exit'; echo $?, we get the following output (test on tcsh of ubuntu/centos and freebsd 10.3):
after exit
0
It seems like the exit command is skipped. How to get the same action like the POSIX/bash shell?

What are you trying to do in a POSIX shell? If you want to print something and exit normally just do printf "%s\n" 'my message'; exit 0. (See https://unix.stackexchange.com/questions/65803/why-is-printf-better-than-echo for reasons not to use echo)
This is probably a bug in csh that's kept in tcsh for backwards compatibility reasons.
(t)csh is line-oriented in a way that other shells are not and generally processes input a line at a time, for instance
This script:
#!/bin/tcsh -f
exit 5
echo 'after exit'
exits with status 5 when executed.
This script, on the other hand, prints after exit.
#!/bin/tcsh -f
exit 5; echo 'after exit'
Somewhat more surprisingly, a string containing a newline passed to /bin/tcsh is treated like the single-line script, not the multi-line one.
% perl -e 'system("/bin/tcsh -f -c \"exit 5\necho after\"")'

Related

"set -e" and "test" in shell

In shell scripts set -e is often used to make them more robust by stopping the script when some of the commands executed from the script exits with non-zero exit code. I am confused about "set -e" and "test" commands, the result is contrary to what I want.
#!/bin/bash
set -e
a=10
b=9
#test $a -lt $b && false
#test $a -gt $b && false
echo "111"
The real result is:
if a > b, print nothing
if a < b, print 111.
but i don't think so, i think the result is nothing whatever happened.
If you want to exit when a is greater than b, just write
#!/bin/bash
set -e
a=10
b=9
test $a -le $b
echo "111"
If test fails, set -e will cause the script to exit; if it succeeds, the script continues to echo. However, there are many pitfalls to using set -e; I recommend just doing your own error handling.
#!/bin/bash
abort () { printf '%s\n' >&2; exit 1; }
a=10
b=9
test "$a" -le "$b" || abort "$a is greater than $b"
echo "111"
The way you wrote your script means, it will exit without printing anything in all situation BUT if a and b are equals.
If you try with a = 9 and b = 9, you will see the 111 output.
You can easily all the behaviour, using the -x option of GNU/Bash.
The explanation of the exit, is your use of -e option; you can read this in GNU/Bash manual:
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 !. 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. A trap on ERR, if set, is executed before the shell exits.
This option applies to the shell environment and each subshell environment separately (see Command Execution Environment), and may cause subshells to exit before executing all the commands in the subshell.
If a compound command or shell function executes in a context where -e is being ignored, none of the commands executed within the compound command or function body will be affected by the -e setting, even if -e is set and a command returns a failure status. If a compound command or shell function sets -e while executing in a context where -e is ignored, that setting will not have any effect until the compound command or the command containing the function call completes.
Thanks everyone, i find the answer which is
SHELL Exit immediately if a pipeline (which may consist of a single simple com-mand), a list, or a compound command (see SHELL GRAMMAR above), exits with 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 following the if or elif reserved words, 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 value is being inverted with !.

Why is my output going to stdout and not to file?

I have a script like this:
#!/usr/bin/bash
COMMAND='some_command >> some_log_file 2>&1'
until $COMMAND; do
echo "some_command crashed with exit code $?. Respawning.." >&2
sleep 1
done
(I got the until ... done bit from https://stackoverflow.com/a/697064/88821 , FWIW, but changed it a bit.)
While some_command is being run, the problem is that the output is not going to some_log_file. Instead, it goes to the stdout of the shell from which I ran this wrapper script. How can I get the output to go to some_log_file while keeping the entire command (including redirects) in the variable COMMAND? The idea is to use this script as a more general wrapper script that can be used with other programs. (COMMAND is actually being passed as an argument into the script).
You're passing >> some_log_file 2>&1 as arguments to some_command, rather than honoring them as redirections. This happens because parsing shell syntax (such as redirections) happens before parameter expansions are performed (the point in processing where $foo is replaced with the contents of the relevant variable). That's actually desirable behavior -- it would be impossible to write code in shell handling untrusted data otherwise.
Don't store code in strings. You can include it literally:
until some_command >> some_log_file 2>&1; do
echo "some_command crashed with exit code $?. Respawning.." >&2
sleep 1
done
...or you can store it in a function:
mycode() { some_command >> some_log_file 2>&1; }
until mycode; do
echo "some_command crashed with exit code $?. Respawning.." >&2
sleep 1
done
One way is
#!/usr/bin/bash
COMMAND='some_command >> some_log_file 2>&1'
until bash -c "$COMMAND"; do
echo "some_command crashed with exit code $?. Respawning.." >&2
sleep 1
done
The problem with your approach is that it does not evaluate the variable as bash code, but just as a series of literal strings. It's like how putting "1+1" in a Java string will never cause 2 to be printed without invoking a Java interpreter:
String cmd="1+1";
System.out.println(cmd); // Prints 1+1
System.out.println(1+1); // Prints 2

Why does bash eval return zero when backgrounded command fails?

Editing this post, original is at bottom beneath the "Thanks!"
command='a.out arg1 arg2 &'
eval ${command}
if [ $? -ne 0 ]; then
printf "Command \'${command}\' failed\n"
exit 1
fi
wait
Here is a test script that demonstrates the problem, which I oversimplified
in the original post. Notice the ampersand in line 2 and the wait command.
These more faithfully represent my script. In case it matters, the ampersand
is sometimes there and sometimes not, its presence is determined by a user-
specified flag that indicates whether or not to background a long arithmetic
calculation. And, also in case it matters, I'm actually backgrounding many
(twelve) processes, i.e., ${command[0..11]}. I want the script to die if any
fail. I use 'wait' to synchronize the successful return of all processes.
Happy (sort of) to use another approach but this almost works.
The ampersand (for backgrounding) seems to cause the problem.
When ${command} omits the ampersand, the script runs as expected:
The executable a.out is not found, a complaint to that effect is issued,
and $? is non-zero so the host script exits. When ${command} includes
the ampersand, the same complaint is issued but $? is zero so the
script continues to execute. I want the script to die immediately when
a.out fails but how do I obtain the non-zero return value from a
backgrounded process?
Thanks!
(original post):
I have a bash script that uses commands of the form
eval ${command}
if [ $? -ne 0 ]; then
printf "Command ${command} failed"
exit 1
fi
where ${command} is a string of words, e.g., "a.out arg1 ... argn".
The problem is that the return code from eval (i.e., $?) is always
zero even when ${command} fails. Removing the "eval" from the above
snippet allows the correct return code ($?) to be returned and thus
halt the script. I need to keep my command string in a variable
(${command}) in order to manipulate it elsewhere, and simply running
${command} without the eval doesn't work well for other reasons. How do I catch the
correct return code when using eval?
Thanks!
Charlie
The ampersand (for backgrounding) seems to cause the problem.
That is correct.
The shell cannot know a command's exit code until the command completes. When you put a command in background, the shell does not wait for completion. Hence, it cannot know the (future) return status of the command in background.
This is documented in man bash:
If a command is terminated by the control operator &, the shell
executes the command in the background in a subshell. The shell does
not wait for the command to finish, and the return status is 0.
In other words, the return code after putting a command in background is always 0 because the shell cannot predict the future return code of a command that has not yet completed.
If you want to find the return status of commands in the background, you need to use the wait command.
Examples
The command false always sets a return status of 1:
$ false ; echo status=$?
status=1
Observe, though, what happens if we background it:
$ false & echo status=$?
[1] 4051
status=0
The status is 0 because the command was put in background and the shell cannot predict its future exit code. If we wait a few moments, we will see:
$
[1]+ Exit 1 false
Here, the shell is notifying us that the brackground task completed and its return status was just as it should be: 1.
In the above, we did not use eval. If we do, nothing changes:
$ eval 'false &' ; echo status=$?
[1] 4094
status=0
$
[1]+ Exit 1 false
If you do want the return status of a backgrounded command, use wait. For example, this shows how to capture the return status of false:
$ false & wait $!; echo status=$?
[1] 4613
[1]+ Exit 1 false
status=1
From the man page on my system:
eval [arg ...] The args are read and concatenated together into a single command. This command is then read and executed by the shell, and its exit status is returned as the value of eval. If there are no args, or only null arguments, eval returns 0.
If your system documentation is 'the same', then, most likely, whatever commands you are running are the problem, i.e. 'a.out' is returning '0' on exit instead of a non-zero value. Add appropriate 'exit return code' to your compiled program.
You might also try using $() which will 'run' your binary instead of 'evaluating' it..., i.e.
STATUS=$(a.out var var var)
As long on only one 'command' is in the stream, then the value of $? is the 'exit code'; otherwise, $? is the return code for the last command in a multi-command 'pipe'...
:)
Dale

Unix return value "-bash"

I do not know what the return value means
grep abc Letters
echo $?
0
echo $0
gives "-bash"
what does "-bash" return value mean
The question should be moved into https://unix.stackexchange.com/.
But anyway, $0 is the value of the variable that holds the name of an application that's executing the command. It's like argv[0] in C.
Example:
cdshines#v3700:~|⇒ echo $0 # I'm in zsh now
/bin/zsh
cdshines#v3700:~|⇒ bash # let's run bash:
cdshines#v3700:~$ echo $0
bash
cdshines#v3700:~$ sh # we need to go deeper:
$ echo $0
sh
$ zsh # deeper!
cdshines#v3700:~|⇒ echo $0
zsh
cdshines#v3700:~|⇒ # hit Ctrl-D (pop one level):
$ echo $0
sh
$ # pop again:
cdshines#v3700:~$ echo $0
bash
cdshines#v3700:~$ exit # again:
cdshines#v3700:~|⇒ echo $0
/bin/zsh # we're at the starting point now
The meaning of $0 is explained in the Bash manual (here, near the bottom of the page):
Expands to the name of the shell or shell script. This is set at shell
initialization. If Bash is invoked with a file of commands (see Shell
Scripts), $0 is set to the name of that file. If Bash is started with
the -c option (see Invoking Bash), then $0 is set to the first
argument after the string to be executed, if one is present.
Otherwise, it is set to the filename used to invoke Bash, as given by
argument zero.
(Unfortunately, it's difficult to search for $0 in the bash manual, since it's listed as just 0.)
By convention, if the current bash process is a login shell, $0 (argv[0] in C terms) is modified, either by bash itself or by the login process, adding a - character to the beginning.
On some systems, /bin/sh is a symbolic link to /bin/bash. If so, and if bash is invoked via the /bin/sh symlink, then $0 will be sh or -sh.
$?, also explained in the Bash manual, "Expands to the exit status of the most recently executed foreground pipeline". More simply, it's the status of the most recently executed command (in your case, grep abc Letters), typically 0 if that command succeeded, or some non-zero value (often, but not always, 1) if it failed.
Return status of the latest shell command is represented by $?:
date
echo $?
0
Here 0 means successful return from previously completed command(date) and any non-zero value means failure status.
When you access $0 is is actually name of the executable in shell and in your case it is -bash since you're running it on the shell.

Using command substitution or similar, but still having script exit (using set -e)

Bash doesn't seem to pass the "exit on error" environment flag into command substitution shells.
I am using a large number of command substitutions (to get around bash's lack of return values), but I'd still like the whole script to go down if something in the subshell fails.
So, for example:
set -e
function do_internet {
curl not.valid.address
}
answer=$(do_internet)
I'd like the script to stop there and then, and not continue.
(I hoped that setting -e would stop from having to put '|| die' on everything.
Am I doing something wrong; and/or is there any way around this?
Here's a little example:
#!/bin/bash
set -e
echo "You should only see this line, and not any other line."
function foo {
false
echo "The above line is false. Figure that one out, Plato."
}
bar=$(foo)
echo $bar
It prints both lines.
(Using GNU bash, version 4.2.25(1)-release (x86_64-pc-linux-gnu))
There is a difference in handling of -e between subshells created with (...), as in Why doesn't bash flag -e exit when a subshell fails?, and subshells created with command substitution $(...), as in the OP.
According to the section COMMAND EXECUTION ENVIRONMENT in the bash manual (and slightly confusingly):
Subshells spawned to execute command substitutions inherit the value of the -e option from the parent shell. When not in posix mode, bash clears the -e option in such subshells.
Regardless of the posix setting, the -e only applies to the subshell created for the purposes of command substitution. So:
$ set -e
# The subshell has -e cleared
$ echo $(false; echo foo)
foo
$ set -o posix
# Now the subshell has -e, so it terminates at `false`
$ echo $(false; echo foo)
$
Nonetheless, -e does apply to the execution of a command which only sets a variable. So
set -e
a=$(false)
will terminate the shell.
However, -e does not apply to individual commands in a function. In the case of
fail() {
false
echo "failed"
}
The return value of fail is 0 (i.e. success) because the echo (which was the last command executed) succeeded. Consequently
a=$(fail) && echo ok
will set a to failed and then print ok

Resources