Silent background jobs in zsh

I run archey in my .zshrc. Problem is, due to displaying the current IP, it can sometimes run like two seconds. To make this less noticeable, I put it in the background like this:

# .zshrc
archey &
# lots of foo
wait

This way, it can fetch the IP while lots of other stuff is done in my zshrc. But now zsh displays the job and process id, and that archey finished every time:

Last login: Mon Jul 10 11:12:07 on ttys002
[1] 40436

# archey output

[1]  + done       archey

~
❯

As the doc states this is being sent to stderr, I tried using 2>/dev/null, without success.

How can I remove these two lines?


Solution 1:

Zsh provides the MONITOR and NOTIFY options, and you can disable them to silence status report of background jobs.

() {
  setopt LOCAL_OPTIONS NO_NOTIFY NO_MONITOR
  archey &
  # ...
  wait
}

Solution 2:

If you don't want to use a subshell, you can use:

# Run the command given by "$@" in the background
silent_background() {
  if [[ -n $ZSH_VERSION ]]; then  # zsh:  https://superuser.com/a/1285272/365890
    setopt local_options no_notify no_monitor
    # We'd use &| to background and disown, but incompatible with bash, so:
    "$@" &
  elif [[ -n $BASH_VERSION ]]; then  # bash: https://stackoverflow.com/a/27340076/5353461
    { 2>&3 "$@"& } 3>&2 2>/dev/null
  else  # Unknownness - just background it
    "$@" &
  fi
  disown &>/dev/null  # Close STD{OUT,ERR} to prevent whine if job has already completed
}

Then:

silent_background archey #  Or whatever command you want to background

Solution 3:

This should work without additional script files:

tmpf=`mktemp`
{ archey ; echo 1 > "$tmpf" ; } &|

# foo

while [ ! -s "$tmpf" ] ; do sleep 0.1 ; done
rm "$tmpf"
unset tmpf

The key is &| operator. It runs the given command in background, but the command is not a job, so your display is not spammed. You cannot use wait though. I used a temporary file to detect if archey has finished.

If your # foo also produces output, you may experience a race condition. In this case it's reasonable to capture archey output to another temporary file (like in this other answer) and print it at the end.

I don't recommend using single temporary file for archey output and as a trigger. If you do this then while test may return true when archey still writes to the file, I think.

Solution 4:

These two lines are output to stderr of the shell, not of the process. You can't make them disappear from zsh.

Realizing from the comments, that you want to see the output displayed, you need to go three steps:

  1. To avoid the noise of starting archey in the background, you must start it from a script
  2. To communicate the results of archey back to the main script, you must use a tempfile or something similar
  3. To understand, when it has finished, you can't use wait as it depends on the noisy background start

wrap-archey.sh:

#/bin/sh
archey >"$1" 2>&1
rm "$2"

start-archey.sh:

/path/to/wrap-archey.sh "$1" "$2" >/dev/null 2>&1 &

.zshrc:

ARCHEYOUT=$(mktemp)
ARCHEYFLAG=$(mktemp)
/path/to/start-archey.sh "$ARCHEYOUT" "$ARCHEYFLAG" >/dev/null 2>&1
#
# lots of foo
#
while test -f "$ARCHEYFLAG"; do sleep 0.1 ; done
cat "$ARCHEYOUT"
rm "$ARCHEYOUT"

wrap-archey.sh will redirect the output into a tempfile, and when done remove a flagfile. This is needed for the back communication.

start-archey.sh will start wrap-archey.sh in the background, eliminating the noise in the main script and returning immediately.

.zshrc will set up flagfile and resultfile, run start-archey.sh silently in the background, then do foo, then wait for archey to complete by polling the flagfile. Then it cleans up.