1. Overview
In Linux, running a background job can be useful when we want to execute a task without interrupting our current work. Moreover, sometimes we want the task to start after a delay or at a specific time.
In this tutorial, we’ll discuss some of the most commonly used methods to introduce a delay to a background job.
Notably, if we want to completely prevent notifications, we can redirect stderr to the /dev/null device with 2>/dev/null for any command, which is usually best done near the & ampersand operator.
2. Using sleep
Running a background job with delay can be achieved using the sleep command along with the & operator.
2.1. Delay via sleep
We can use the sleep command to delay the execution of a subsequent command by a specified number of seconds. For example, let’s delay the ls command by 10 seconds:
$ sleep 10; ls
One problem here is that both commands are foreground processes attached to the current shell. Thus, we won’t be able to run other commands in the meantime until these two commands complete their execution.
2.2. Sending Commands to Background
Instead of running the sleep and subsequent commands in the foreground, we can free up the shell by sending these processes to the background via &:
$ ( sleep 10; ls ) &
Here, we’ve grouped both commands by spawning a subshell indicated by parentheses and then backgrounding that. It’s important to note that if we send each of the two commands to the background separately, we won’t achieve the desired result. This is because the sleep command should execute sequentially before the subsequent command and not in parallel with it to introduce a delay.
3. Separate Shell
We can spawn a bash subshell using a different syntax. For example, we can use the -c flag, which accepts commands within quotes. After that, it runs these commands within a new shell:
$ bash -c 'sleep 10; ls' &
If we don’t background the entire process with &, the new shell replaces the current.
Moreover, we might choose to send stdout and stderr to the /dev/null device so as not to interrupt foreground work:
$ bash -c 'sleep 10; ls' > /dev/null 2>&1 &
Finally, we can use nohup to ensure that the process continues to run even after we log out:
$ nohup bash -c 'sleep 10; ls' > /dev/null 2>&1 &
The nohup command enables us to run a command in the background even after we log out or close the terminal.
4. Using a Function
We can also set up a function to send any desired command to the background with a specified delay:
$ run_with_delay() { sleep $1; "${@:2}"; }
The amount of delay in seconds is passed as the first argument to the function. The function then executes a sleep command with the specified delay followed by the subsequent command.
All that’s required is to call the function with desired arguments and send it to the background:
$ run_with_delay 10 ls &
This way, we can call the function with a specified delay to a required command whenever needed. While it doesn’t save much typing, it makes the idea clearer.
5. Using at
Another method for running a background job with delay is via the at command. We can use the at command to schedule a background job that will run only once and at a specified time or after a delay:
$ echo 'ls' | at now + 1 minute
In this example, the echo command sends the command string ls as input to the at command. After that, at schedules the command to run in a minute relative to the current time. The ls command then runs in the background, so we can continue to use the terminal for other tasks.
The output is mailed to the user and can be read for example at /var/spool/mail/sysadmin assuming the user name is sysadmin:
$ cat /var/spool/mail/sysadmin
Using cat, we can read the mail sent by the at command.
6. Using cron
We can also schedule a command to run in the background at a specific time and date via cron. The job we schedule can even be set as a recurrent task instead of a one-time task.
First, we copy the current cron jobs into a temporary file:
$ crontab -l > /tmp/crontab
The crontab command with the -l option lists all cron jobs. We redirect the content to a temporary file under /tmp.
After that, we append the task we wish to run by adding a line to the temporary file:
$ echo "30 10 * * * ls" >> /tmp/crontab
This entry runs the ls command at 10.30 of every day.
Finally, we update the crontab using the temporary file:
$ crontab /tmp/crontab
Alternatively, we can add the required task by editing the cron table with the -e option:
$ crontab -e
As before, if we wish to run ls at 10.30 every day, we simply add a line:
30 10 * * * ls
Once we’ve added the required line, we save and exit the file.
7. Using read
Since the sleep command isn’t always a good way to delay execution, we can opt for yet another option in the face of read.
In particular, the read command has a -t timeout option. It gets triggered when a complete input line isn’t received within the specified number of seconds.
Thus, we can leverage this feature for a background task delay:
$ (coproc read -t 10 && wait "$!" || true; ls) &
Let’s break down the command:
- the () syntax spawns a subshell
- & backgrounds the subshell
- coproc starts a job in the background that can get input and produce output without named pipes
- read -t 10 timeouts after 10 seconds since it doesn’t get any input
- wait “$!” ensures we wait for the last spawned process
- || true prevents signals from wait to kill the shell
Thus, we don’t use sleep, but ensure a given delay of an ls command run in the background.
8. Conclusion
In this article, we’ve explored several methods to run a background job with delay in Linux.
One approach is to create a subshell using parentheses or bash -c. The subshell runs a sleep command followed by the required command to execute. Then, the & symbol puts the subshell in the background, enabling us to continue using the terminal for other tasks.
We’ve also seen how to write a custom function for executing a delayed command in the background. Another approach is to use the at command to run a one-time task after a specific delay. Alternatively, we can schedule a possibly recurrent background job to run at a specific time via crontab.