Skip to content

Instantly share code, notes, and snippets.

@ddanier
Last active February 23, 2026 17:05
Show Gist options
  • Select an option

  • Save ddanier/55e8d5ae88ba3db89e6e99e2d3d6d577 to your computer and use it in GitHub Desktop.

Select an option

Save ddanier/55e8d5ae88ba3db89e6e99e2d3d6d577 to your computer and use it in GitHub Desktop.
nu shell jobd module - slim wrapper to run background jobs and keep a nice output
use jobd.nu
# Start background jobs
jobd spawn "background-job1" { bash -c 'while [ true ]; do echo "background running"; sleep 5; done' }
jobd spawn "background-job2" --restart { bash -c 'sleep 5' }
# Start main job we want to wait for
jobd spawn "main-job" { bash -c 'sleep 20' }
# Wait for main job to be done, end all other jobs when this happens
jobd wait "main-job" --end
use jobd.nu
# Start background jobs
jobd spawn "background-job1" { bash -c 'while [ true ]; do echo "background running"; sleep 5; done' }
jobd spawn "background-job2" --restart { bash -c 'sleep 5' }
# Start main job in forground, but also with name prefix
jobd run "main-job" --color cyan { bash -c 'sleep 20' }
# End all remaining jobs
jobd end
# Print single line with optional color and name prefix
def print-line [
name: string
line: string
--color: string
]: nothing -> nothing {
if $color != null {
print $"(ansi $color)($name):(ansi reset) ($line)"
} else {
print $"($name): ($line)"
}
}
# Job tag will have a "jobd: " prefix
def name-to-job-tag [
name: string
]: nothing -> string {
$"jobd: ($name)"
}
# Remove "jobd: " prefix from job tag
def job-tag-to-name [
tag: string
]: nothing -> string {
if ($tag | str starts-with "jobd: ") {
$tag | str substring 6..
} else {
$tag
}
}
# Run a background job command. The output of the command will be prefixed by
# the provided name and an optional color.
#
# Example usage:
# jobd spawn "background-job1" { bash -c 'sleep 10' }
# jobd spawn "background-job2" { bash -c 'sleep 10' }
# jobd wait
#
# This will start two background jobs and wait for both to be finished.
# See "jobd wait" for more details.
#
# Important: To prefix the command with the name it needs to be external.
export def spawn [
--color: string # Use this color for the name of the job
--restart # Automatically restart the command if it exited
--wait-between-restarts: duration = 1sec # Time in between restarts
name: string # Job name
command: closure # The command as a closure to be run
]: nothing -> int {
job spawn -t (name-to-job-tag $name) {
loop {
print-line $name --color $color "Job starting"
do $command out+err>| lines
| each {
|line|
print-line $name --color $color $line
}
if $restart {
print-line $name --color $color $"(ansi red)Job exited, restarting in ($wait_between_restarts)(ansi reset)"
sleep $wait_between_restarts
} else {
print-line $name --color $color "Job exited"
return
}
}
}
}
# Run single job until it is finished. Will prefix output of command
# with "$name: " so its output can be used in combination with spawned
# jobs, wee "jobd spawn".
#
# Example usage:
# jobd spawn "background-job" { bash -c 'sleep 10' }
# jobd run "run" { bash -c 'sleep 10' }
# jobd end
#
# This will start a background job, then a foreground job which both will have their
# output prefixed by their names. After the foreground job is done it will end all
# jobs. See "jobd spawn" and "jobd end" for more details.
#
# Important: To prefix the command with the name it needs to be external.
export def run [
--color: string # Use this color for the name of the job
name: string # Job name
command: closure # The command as a closure to be run
]: nothing -> nothing {
do $command out+err>| lines
| each {
|line|
print-line $name --color $color $line
}
null
}
# Wait for all or one background job to be finished. It will check whether the
# job(s) are still running by default every second (see --wait).
#
# The two main usages are:
# jobd wait -> Wait for all jobs to be finished (be sure to start no job with --restart)
# jobd wait some-job-name -> Wait for one job to be finished
#
# If you pass a job name and --end all background jobs will be stopped once the named
# background job is finished (see "jobd end").
#
# Example usage:
# jobd spawn "background-job1" { bash -c 'sleep 10' }
# jobd spawn "background-job2" { bash -c 'sleep 10' }
# jobd wait background-job1 --end
export def wait [
name?: string
--end
--wait: duration = 1sec # Time in between checks
]: nothing -> nothing {
loop {
sleep 1sec
let jobs = job list
if ($name | is-empty) {
if ($jobs | length) == 0 {
print "Jobs are all finished"
return
}
} else {
if ($jobs | where tag == (name-to-job-tag $name) | length) == 0 {
print $"Job ($name) finished"
if $end {
end
}
return
}
}
}
}
# Recursively kill a pid, ensures no children remain
def kill-recursive [
pid: int
]: nothing -> nothing {
ps -l | where ppid == $pid | each {
|psitem|
kill-recursive $psitem.pid
}
kill --quiet $pid
}
# Stop all running jobd background jobs. Will also stop all processes started by
# the background tasks (aka external commands) recursively. Be sure to run this
# to clean all running jobs at the end of your script.
#
# Example usage:
# jobd spawn "background-job" { bash -c 'sleep 10' }
# jobd run "run" { bash -c 'sleep 10' }
# jobd end
#
# Note: "jobd wait job-name --end" will automatically run "jobd end" when the
# awaited job finishes.
export def end []: nothing -> nothing {
job list
| where { |job| ($job.tag | str starts-with "jobd: ") }
| each {
|job|
print $"Ending remaining job (job-tag-to-name $job.tag)"
$job.pids | each {
|pid|
print $"-> killing sub-process with pid ($pid)"
kill-recursive $pid
}
try { job kill $job.id } # may already be stopped due to children being killed
}
null
}
# Use one of the available commands:
# - jobd spawn
# - jobd run
# - jobd wait
# - jobd end
# (Call these with '--help' for details)
export def main []: nothing -> nothing {
print "jobd allows you to run commands in the background while retaining"
print "the output of the commands in a nice way. It will prefix every"
print "output line with a provided name (and optional color)."
print ""
print "See the following sub commands:"
print " - jobd spawn"
print " - jobd run"
print " - jobd wait"
print " - jobd end"
print "(Call these with '--help' for details)"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment