Last active
February 23, 2026 17:05
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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