0

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
    "but show stderr"? But your example hides both stdout and stderr. Feb 7, 2021 at 21:57
  • What is the point of the update 2? If it's an acceptable solution then it should be an answer. If it's only a part of your research effort but for some reason it's not enough for you then you should clearly state why it's not enough. I'm asking because my answer is uses the same trick but there is neither acceptance nor feedback from you. Feb 8, 2021 at 21:10
  • About the update 3: Yes, after you choose one of the two snippets you should place the rest of your script under it. The shebang (you're using it, aren't you?) however should always be in the first line because this is how the shebang works. Meta note: any question should make sense for readers before they read answers to it. To request clarification from an answerer you should comment under their answer. If you commented under my answer, then I would be notified. By pure chance I saw your edit where you want my answer to be clarified. Such communication should not belong to the question. Feb 9, 2021 at 7:38

1 Answer 1

1

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
  • 1
    Another thing redefining echo would break is the common idiom var=$(echo "$othervar" | sed or awk or tr or ...). Redefining standard commands always makes my tail go all bushy. Feb 8, 2021 at 2:55

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .