Supress stdout except for echo commands but show stderr

I have put a very large bash script together that installs packages and configures a LAMP stack etc. With any scripting I usually do, I like to tidy it up by using echo commands to let the user running the script know what's happening on screen and suppressing all output from executed commands using > /dev/null 2>&1. Currently my scripts take the following form: (N.B the use of /dev/null 2>&1 is what I want to avoid using)

echo -n "Starting foo... "
command 1 > /dev/null 2>&1
command 2 > /dev/null 2>&1
echo "Done"

Now I could carry on doing this with all Unix based scripts, but with the amount of lines of code I have it will certainly be a tedious job. Luckily enough I have plenty of comments for command in hand so these can be easily converted to echo commands. However, I would still only like to pass echo commands through and hide the output of every other command without the need of > /dev/null 2>&1. However, if an error does occur I would still like to these to show.

Is there a global parameter that disables stdout except from certain commands such as echo?

UPDATE 2

I found a similar post to mine on stackoverflow here

exec 3>&1 &>/dev/null
some_command
another_command
command_you_want_to_see >&3
command3

3>&1 redirects any command suffixed with >&3 to a new file descriptor which redirects out &1 (STDOUT). In regards to &>/dev/null I assume this this suppresses all output to /dev/null?

In my example this script below:

#!/bin/bash
# Hide all output by default
exec 3>&1 &>/dev/null
echo -n "outputting text to file... " >&3
echo "hello world" > ~/"hello_world_output.txt"
echo "I am hidden from output"
echo "Done" >&3

produces the following output:

user@ubuntu:~$ ./test.sh
outputting text to file... Done
user@ubuntu:~$

UPDATE 3

Could you place the below snippets into an example script so that I can see it as a whole with some example commands please? The first example says it can be defined at the beginning of the script. Do I place the rest of my script under the closed bracket?

echo() ( if { true >&3; } 2>/dev/null; then exec >&3 fi command echo "$@"
)

Again do I place the rest of my script under this snippet?

exec 3>&1 >/dev/null
echo() ( >&3 command echo "$@"
)
3

1 Answer

tl;dr

Define a function

report() { echo "$@" >&2; }

and use it like echo, i.e. report -n "Starting foo... " or report "Done". Run your script with stdout redirected to /dev/null:

/path/to/the_script >/dev/null

Or let the script redirect its stdout to /dev/null by itself by running exec >/dev/null at the beginning of the script. This way you won't need to type >/dev/null while invoking the script; but if you ever want not to redirect then you will need to modify the script (remove exec >/dev/null).

Not suppressing errors is achieved by leaving stderr alone. The function prints to stderr because messages like Starting foo... can be seen as diagnostic, stderr is not the worst place for them.


Full answer

Is there a global parameter that disables stdout except from certain commands such as echo?

I don't think there is. However you can craft a custom echo and achieve what you want this way. Define this function at the beginning of the script:

echo() ( if { true >&3; } 2>/dev/null; then exec >&3 fi command echo "$@"
)

This will cause every echo in the script run the function instead of the regular echo. The function checks if file descriptor 3 is available. If it is then the function will redirect its own stdout to it. Because the function is defined to run in a subshell, this will not affect the main script; but this will affect command echo run from the function eventually.

So the function is like echo that prefers the file descriptor 3, if only it's available. Now if you run the script like this:

/path/to/the_script 3>&1 >/dev/null

then echos within will use the stdin the_script would use without redirections (probably the terminal) while other commands will print to /dev/null. Note you can still run

/path/to/the_script

and it will behave normally.

The definition of the function can be placed in a separate file. Every script that needs the function can then source the file.

If you don't like typing /path/to/the_script 3>&1 >/dev/null and you want sole /path/to/the_script to behave in the new way then let the_script do proper redirections for itself:

exec 3>&1 >/dev/null

So the whole snippet to be sourced can be like this:

exec 3>&1 >/dev/null
echo() ( >&3 command echo "$@"
)

Note there's no need for if because we've just opened a file descriptor 3.

On the other hand you may not want to change the script. If the interpreter is Bash then you can pass a function to it via the environment. This means you can do this:

# in Bash
( echo() ( >&3 command echo "$@" ) export -f echo /path/to/the_script 3>&1 >/dev/null # where the_script is the original (unmodified) script
)

I put the whole snippet in a subshell, so if you run it in an interactive Bash, the function will not survive, it will not affect later commands.

This leads us to a Bash script that can silence other Bash scripts, except echos in them:

#!/bin/bash
echo() ( >&3 command echo "$@"
)
export -f echo
exec 3>&1 >/dev/null "$@"

Save it as silence somewhere in PATH, make it executable. Then use it like this:

silence /path/to/the_script

Notes:

  • Not suppressing errors is achieved by leaving stderr alone.

  • If the_script runs other Bash scripts then passing the function in the environment will affect them as well. Note you may not be aware some tools you're using are Bash scripts. If you define the function in the_script (directly or by sourcing) without exporting it then other scripts will not be affected.

  • The function will redirect (and thus break) every case of echo designed to print to somewhere else than the stdout of the script. Examples:

    echo Message >>log
    echo 1 2 3 | pipeline

The last point is a severe disadvantage. For this reason consider leaving echo alone. Rebuild the script and don't use echo directly for reporting progress. Instead of echo "Done" run report "Done", where report is a function similar to one of the echo functions introduced above. This way the function will not affect random echos. If I were you I wouldn't even use the trick with file descriptor 3. I would make report simply print to stderr. Messages like Starting foo... can be seen as diagnostic, stderr is not the worst place for them. The function I would use:

report() { echo "$@" >&2; }
1

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

You Might Also Like