97

To run a process in the background in bash is fairly easy.

$ echo "Hello I'm a background task" &
[1] 2076
Hello I'm a background task
[1]+  Done                    echo "Hello I'm a background task"

However the output is verbose. On the first line is printed the job id and process id of the background task, then we have the output of the command, finally we have the job id, its status and the command which triggered the job.

Is there a way to suppress the output of running a background task such that the output looks exactly as it would without the ampersand at the end? I.e:

$ echo "Hello I'm a background task" &
Hello I'm a background task

The reason I ask is that I want to run a background process as part of a tab-completion command so the output of that command must be uninterrupted to make any sense.

Alex Spurling
  • 54,094
  • 23
  • 70
  • 76
  • I suppose you should add 2>/dev/null to anything you do inside bash-completion scripts – sehe Oct 07 '11 at 12:10
  • 1
    X-Ref: [Answer from "Is there a way to make bash job control quiet?"](https://stackoverflow.com/a/38278291/367456) – hakre Oct 30 '18 at 09:20

8 Answers8

117

Not related to completion, but you could supress that output by putting the call in a subshell:

(echo "Hello I'm a background task" &)
Dimitre Radoulov
  • 27,252
  • 4
  • 40
  • 48
  • 3
    Thanks, that definitely works. Unfortunately, when running a script like this from a bash-completion function, it appears that bash waits until the subprocess is complete before giving completion results. This is unfortunate as it means it doesn't appear to be possible to make background workers be triggered by bash-completion. – Alex Spurling Oct 10 '11 at 16:14
  • 20
    You can't use this if you want to `wait` for the background job to finish. – arekolek Mar 31 '17 at 13:28
  • 1
    You can place the rest of the code and the wait builtin in the same subshell though: `(echo "Hello I'm a background task" & .... other code ... wait)`. – Dimitre Radoulov Jun 30 '22 at 16:06
14

Building off of @shellter's answer, this worked for me:

tyler@Tyler-Linux:~$ { echo "Hello I'm a background task" & disown; } 2>/dev/null; sleep .1;
Hello I'm a background task
tyler@Tyler-Linux:~$

I don't know the reasoning behind this, but I remembered from an old post that disown prevents bash from outputting the process ids.

Tyzoid
  • 1,072
  • 13
  • 31
  • @Tyzoid . I tried your solution in a bash shell (v 4.2.37) and had interesting results. 1. I got same results just adding `;` at the end. (without sleep) . 2. BUT noticed that when I added `echo STDERR 2>&1` on the "next" line, then the `[1]+ Done ...` thing appeared! . 3. My solution (as I have now noted) works in `ksh` and `echo STDERR 2>&1` only produces the output STDERR. Well, Live and learn for both of us. Good luck to all. – shellter Jan 03 '17 at 14:23
12

In some newer versions of bash and in ksh93 you can surround it with a sub-shell or process group (i.e. { ... }).

/home/shellter $ { echo "Hello I'm a background task" & } 2>/dev/null
Hello I'm a background task
/home/shellter $
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
shellter
  • 36,525
  • 7
  • 83
  • 90
  • 5
    When I use curly braces, the last line of output still shows (I'm not sure why you don't see it). – Alex Spurling Oct 10 '11 at 16:16
  • I copied this exactly but it doesn't work for me, it still shows output as @AlexSpurling says. Maybe I'm doing something wrong since others upvoted it, but I'm afraid I have to give it -1. – Mark Jan 03 '17 at 09:41
  • 2
    @Mark : I just did some quick tests with this and I see the problem is that I continue to use `ksh93+` as my primariy shell. This code works as promised in ksh. but as the OP is tagged with `bash` I understand your dv. See the results of the bash testing on @Tyzoid's answer. Good luck to all! – shellter Jan 03 '17 at 14:14
  • Since you've made it clear that this doesn't work in bash I'll remove the -1. – Mark Jan 04 '17 at 19:37
  • 1
    For me this works great in a Bash function, also the process can be waited for with `wait $!` which doesn't seem to be the case for the accepted answer. – Jonatan Öström Jul 24 '17 at 11:51
  • @JonatanÖström : Tnx for the careful reading on this topic (and the upvote ;-) ). I indicated my testing in `bash` was with an old-ish version. Let us know what version of `bash` you have used for your successful test. Tnx and good luck to all! – shellter Jul 24 '17 at 16:27
  • Note that `2>/dev/null` will hide the error stream so if someone wants to hide the output stream, they will have to use `1>/dev/null`. I also noticed that it is possible to use parentheses instead of braces. By the way, is it possible to hide the output stream of a program that was already started and set in background with Ctrl+Z and `bg` command? – baptx Oct 03 '18 at 06:52
  • @baptx : Good note on using parenthesis. Sorry, but my experience would say it is not possible to redirect once you have started a program, BUT with newer tools (`stdbuf` (`iobuf`?)), such a thing may be possible. That is a Q worth posting separately here on S.O. See https://stackoverflow.com/questions/45460998/how-to-unbuffer-stdout-of-legacy-running-binary-without-stdbuf-and-similar-tools for info on `stdbuf`. Good luck! – shellter Oct 03 '18 at 11:33
10

Based on this answer, I came up with the more concise and correct:

silent_background() {
    { 2>&3 "$@"& } 3>&2 2>/dev/null
    disown &>/dev/null  # Prevent whine if job has already completed
}
silent_background date
Tom Hale
  • 40,825
  • 36
  • 187
  • 242
7

Building on the above answer, if you need to allow stderr to come through from the command:

f() { echo "Hello I'm a background task" >&2; }
{ f 2>&3 &} 3>&2 2>/dev/null
pajamian
  • 501
  • 5
  • 2
  • Beautiful use of redirections. Still, [you'll need a `disown` in some circumstances](https://stackoverflow.com/a/51061046/5353461). – Tom Hale Jun 27 '18 at 10:58
5

Try:

user@host:~$ read < <( echo "Hello I'm a background task" & echo $! )
user@host:~$ echo $REPLY
28677

And you have hidden both the output and the PID. Note that you can still retrieve the PID from $REPLY

mark_infinite
  • 383
  • 1
  • 7
  • 13
4

Sorry for the response to an old post, but I figure this is useful to others, and it's the first response on Google.

I was having an issue with this method (subshells) and using 'wait'. However, as I was running it inside a function, I was able to do this:

function a {
    echo "I'm background task $1"
    sleep 5
}

function b {
    for i in {1..10}; do
        a $i &
    done
    wait
} 2>/dev/null

And when I run it:

$ b
I'm background task 1
I'm background task 3
I'm background task 2
I'm background task 4
I'm background task 6
I'm background task 7
I'm background task 5
I'm background task 9
I'm background task 8
I'm background task 10

And there's a delay of 5 seconds before I get my prompt back.

The Tomahawk
  • 153
  • 1
  • 5
2

The subshell solution works, but I also wanted to be able to wait on the background jobs (and not have the "Done" message at the end). $! from a subshell is not "waitable" in the current interactive shell. The only solution that worked for me was to use my own wait function, which is very simple:

myWait() {
  while true; do
    sleep 1; STOP=1
    for p in $*; do
      ps -p $p >/dev/null && STOP=0 && break
    done
    ((STOP==1)) && return 0
  done
}

i=0
((i++)); p[$i]=$(do_whatever1 & echo $!)
((i++)); p[$i]=$(do_whatever2 & echo $!)
..
myWait ${p[*]}

Easy enough.

ExpertNoob1
  • 900
  • 1
  • 8
  • 17