Date:        Mon, 31 Aug 2026 09:53:50 -0400
    From:        Chet Ramey <[email protected]>
    Message-ID:  <[email protected]>

  | I'm pretty sure kre was using a script.

Indeed.   I have yet to imagine a use for an empty function in an interactive
shell ... if I want to do nothing, I just do nothing!  This is all for scripts.

  | A `false pipeline' (see my message to kre) exits with status 1, but the
  | null command is still having its exit status inverted, so it doesn't
  | cause the shell to exit.

Given that an empty command is allowed there, and that is somehow defined
to have a zero exit status (of course, one could define the exit status of an
empty command in a location a non-empty command is required, to be non-zero,
so just "!" would produce a 0 result, not what bash does, but it could)
that's all correct.   Nb: there is a difference between a command that ends
up being empty, and one which never contained anything at all - the former
is defined as having a 0 exit status, so in

        E=
        $E || echo failed

"failed" can never be printed.   But if a shell were to allow just

        || echo failed

then nothing (except shell specific doc perhaps) specifies whether that
(if not considered a syntax, that is, parse, error - so nothing is executed
at all) would have "failed" be written or not.  Same with "! || echo failed".

  | That's more consistent -- every time you see a
  | `!' it means the exit status is being inverted and `set -e' doesn't
  | apply -- but not exactly in the letter of the interpretation.

No, I think it is.  The exceptions to -e include "any pipeline that
starts with !" (paraphrase) and for that, observe it says "pipeline"
not "pipe_sequence".   The thing most of us think of as being a pipeline
is a pipe_sequence in the standard.  A pipeline is either just a pipe_sequence,
or ! pipe_sequence ... so when the exclusions for -e exclude pipelines that
begin with !, they mean the 2nd form of the pipeline definition, which is
the whole pipeline, ! and all.   -e does not apply to anything preceded by !
or the ! either.  '!' is not a command, but syntax, it is more like "if".

And yes, that is the consistent and sane thing for it to do.   It would
be bizarre indeed if

        ! true                  ( or ! : )

was a way of causing the shell to exit should -e be enabled.

In fact, I sometimes suggest to people, that while the common way
when needing to deal with the possibility that -e may be on, and
a command which might exit != 0 is being run, and is not to cause
the shell to exit is

        command || :

that can get kind of ugly, but

        ! command

achieves the same thing (both cases are assuming that the script doesn't
care to examine the exit status from the command, just prevent the shell
from exiting).   The exit status of command is inverted, cannot cause a -e
shell exit regardless of whether it was 0 or not, and is then just ignored.

If the script needs the exit status, the right way is

        command && X=$? || X=$?

and then use $X (the first of those $? uses (between && and ||) can just
be written X=0 if you prefer, as $? must be 0 at that point).  A command
which is just a var-assign (and contains no command substitutions) can
never have a non-zero exit status, so the final X=$? can never cause -e
problems, everything else is an exception to -e's application.  And note,
the execptions apply "while executing ..." not just to the exit status of
command itself (if command is something complex, like a loop, or if
statement, or a brace-group, or a function, everything that gets executed
from it is executed while "command" is executing, and ex -e exempt).

All this, including that many (most) people don't just know it all,
just gives even more reasons why using -e (usually) is idiotic.

The best thing to do in any script, is to write the whole script inside a
function (often called "main", its name is of no consequence, provided it
is new to the script, but that is what I will assume here), and then after
the definition of:

        main() {
                # the whole script goes here (no need to indent it)
        }

just add

        main "$@" && exit || exit # end of script

as the last line of the file (though it is harmless to have anything else
appropriate after it).  Nothing other than whitespace or comments between
the '}' that ends the main() function definition, and this line.

This serves 2 purposes.   First it ensures that the shell has read & parsed
the entire script before it starts executing any of it, so it can never get
part executed and then discover a syntax error, but more importantly, so
should there be a need, the file containing the script can be modified (with
added, or deleted, sections) without affecting in any way any current instances
of the script that might be still running.   The shell will always run the
"main" function, and then exit with whatever status that function returned.
main() can still contain other explicit "exit" commands of course.

And second, it guarantees that no matter how the script is invoked, or what
might get included in it (sourced from other files, etc), the hideous -e option
can never affect its operation, as aside from executing the final "exit"
command (after the ||) (which hardly matters, the shell is exiting with
non-zero status there anyway) everything executed is executed while executing
the LHS of the && or || operator, and -e does not apply.

  | See the grammar changes from interp 267.

Thanks, but I prefer not to revisit torture chambers, having escaped finally!


Everyone please do note however, the kinds of extensions being discussed
here (allowing empty commands in places the standard does not allow) are
the kind of thing it is OK for a shell to do .. no properly written
script will ever notice, as it won't be attempting to do something the
standard says is not allowed (it would not be properly written if it was).

So, allowing just "!" or an empty command as a function definition, is
a harmless thing to do.   So is "! ! cmd" (also not allowed in the standard)
(which the NetBSD shell allows as well ... earlier versions allowed any
number of !'s. but after 2 it just gets kind of pointless, so no longer.)

kre

ps: while "time" (time nothing) might be another example, it is hardly
useful for the purpose of generating a nothing, as it still (in bash)
writes the (0's) timing results .. having to add a redirect to make that
not appear kind of defeats the purpose (no longer an empty command).

Reply via email to