When you are up to your neck in alligators,
it’s easy to forget that the goal was to drain the swamp.
— popular saying
The question is about echo
,
and yet the majority of the answers so far
have focused on how to sneak a set +x
command in.
There’s a much simpler, more direct solution:
{ echo "Message"; } 2> /dev/null
(I acknowledge that I might not have thought of the { …; } 2> /dev/null
if I hadn’t seen it in the earlier answers.)
This is somewhat cumbersome, but,
if you have a block of consecutive echo
commands,
you don’t need to do it on each one individually:
{
echo "The quick brown fox"
echo "jumps over the lazy dog."
} 2> /dev/null
Note that you don’t need semicolons when you have newlines.
You can reduce the typing burden by using kenorb’s idea
of opening /dev/null
permanently
on a non-standard file descriptor (e.g., 3)
and then saying 2>&3
instead of 2> /dev/null
all the time.
The first four answers at the time of this writing
require doing something special (and, in most cases, cumbersome)
every time you do an echo
.
If you really want all echo
commands
to suppress the execution trace (and why wouldn’t you?),
you can do so globally, without munging a lot of code.
First, I noticed that aliases aren’t traced:
$ myfunc()
> {
> date
> }
$ alias myalias="date"
$ set -x
$ date
+ date
Mon, Oct 31, 2016 0:00:00 AM # Happy Halloween!
$ myfunc
+ myfunc # Note that function call is traced.
+ date
Mon, Oct 31, 2016 0:00:01 AM
$ myalias
+ date # Note that it doesn’t say + myalias
Mon, Oct 31, 2016 0:00:02 AM
(Note that the following script snippets
work if the shebang is #!/bin/sh
, even if /bin/sh
is a link to bash.
But, if the shebang is #!/bin/bash
,
you need to add a shopt -s expand_aliases
command
to get aliases to work in a script.)
So, for my first trick:
alias echo='{ set +x; } 2> /dev/null; builtin echo'
Now, when we say echo "Message"
,
we’re calling the alias, which doesn’t get traced.
The alias turns off the trace option,
while suppressing the trace message from the set
command
(using the technique presented first in user5071535’s answer),
and then executes the actual echo
command.
This lets us get an effect similar to that of user5071535’s answer
without needing to edit the code at every echo
command.
However, this leaves trace mode turned off.
We can’t put a set -x
into the alias (or at least not easily)
because an alias only allows a string to be substituted for a word;
no part of the alias string can be injected into the command
after the arguments (e.g., "Message"
).
So, for example, if the script contains
date
echo "The quick brown fox"
echo "jumps over the lazy dog."
date
the output would be
+ date
Mon, Oct 31, 2016 0:00:03 AM
The quick brown fox
jumps over the lazy dog.
Mon, Oct 31, 2016 0:00:04 AM # Note that it doesn’t say + date
so you still need to turn the trace option back on
after displaying message(s) —
but only once after every block of consecutive echo
commands:
date
echo "The quick brown fox"
echo "jumps over the lazy dog."
set -x
date
It would be nice if we could make the set -x
automatic
after an echo
— and we can, with a bit more trickery.
But before I present that, consider this.
The OP is starting with scripts that use a #!/bin/sh -ex
shebang.
Implicitly the user could remove the x
from the shebang
and have a script that works normally, without execution tracing.
It would be nice if we could develop a solution that retains that property.
The first few answers here fail that property
because they turn tracing “back” on after echo
statements,
unconditionally, without regard to whether it was already on.
This answer conspicuously fails to recognize that issue,
as it replaces echo
output with trace output;
therefore, all the messages vanish if tracing is turned off.
I will now present a solution that turns tracing back on
after an echo
statement conditionally — only if it was already on.
Downgrading this to a solution that turns tracing “back” on
unconditionally is trivial and is left as an exercise.
alias echo='{ save_flags="$-"; set +x;} 2> /dev/null; echo_and_restore'
echo_and_restore() {
builtin echo "$*"
case "$save_flags" in
(*x*) set -x
esac
}
$-
is the options list; a concatenation of the letters
corresponding to all the options that are set.
For example, if the e
and x
options are set,
then $-
will be a jumble of letters that includes e
and x
.
My new alias (above) saves the value of $-
before turning tracing off.
Then, with tracing turned off,
it throws control over into a shell function.
That function does the actual echo
and then checks to see whether the x
option was turned on
when the alias was invoked.
If the option was on, the function turns it back on;
if it was off, the function leaves it off.
You can insert the above seven lines (eight, if you include an shopt
)
at the beginning of the script
and leave the rest alone.
This would allow you
- to use any of the following shebang lines:
#!/bin/sh -ex
#!/bin/sh -e
#!/bin/sh –x
or just plain#!/bin/sh
and it should work as expected.
- to have code like
(shebang)
command1
command2
command3
set -x
command4
command5
command6
set +x
command7
command8
command9
and
- Commands 4, 5, and 6 will be traced — unless one of them is an
echo
,
in which case it will be executed but not traced.
(But even if command 5 is an echo
, command 6 still will be traced.)
- Commands 7, 8, and 9 will not be traced.
Even if command 8 is an
echo
, command 9 still will not be traced.
- Commands 1, 2, and 3 will be traced (like 4, 5, and 6)
or not (like 7, 8, and 9) depending on whether the shebang includes
x
.
P.S. I have discovered that, on my system,
I can leave out the builtin
keyword in my middle answer
(the one that’s just an alias for echo
).
This is not surprising; bash(1) says that, during alias expansion, …
… a word that is identical to an alias being expanded
is not expanded a second time.
This means that one may alias ls
to ls -F
, for instance,
and bash does not try to recursively expand the replacement text.
Not too surprisingly, the last answer (the one with echo_and_restore
)
fails if the builtin
keyword is omitted1.
But, oddly it works if I delete the builtin
and switch the order:
echo_and_restore() {
echo "$*"
case "$save_flags" in
(*x*) set -x
esac
}
alias echo='{ save_flags="$-"; set +x;} 2> /dev/null; echo_and_restore'
__________
1 It seems to give rise to undefined behavior.
I’ve seen
- an infinite loop (probably because of unbounded recursion),
- a
/dev/null: Bad address
error message, and
- a core dump.
2I have seen some amazing magic tricks done with aliases, so I know my knowledge thereof is incomplete. If anybody can present a way to do the equivalent of
echo +x; echo "$*"; echo -x
in an alias, I’d like to see it. – G-Man Says 'Reinstate Monica' – 2016-11-01T03:20:55.310dude, thanks for going deep! – Jan-Philip Gehrcke – 2019-12-13T14:39:58.620