shenwei356/rush

A cross-platform command-line tool for executing jobs in parallel

Go

1,134

217 commits

updated Sep 21, 2026

See the code

README

rush -- a cross-platform command-line tool for executing jobs in parallel

Built with GoLang Cross-platform Latest Version Github Releases

rush is a tool similar to GNU parallel and gargs. rush borrows some idea from them and has some unique features, e.g., supporting custom defined variables, resuming multi-line commands, more advanced embeded replacement strings.

These features make rush suitable for easily and flexibly parallelizing complex workflows in fields like Bioinformatics (see examples).

Table of Contents

Features

Major:

  • Supporting Linux, OS X and Windows (not CygWin)!
  • Avoid mixed line from multiple processes without loss of performance, e.g. the first half of a line is from one process and the last half of the line is from another process. (--line-buffer in GNU parallel)
  • Timeout (-t), terminating the timed-out command and its child-process tree. (--timeout in GNU parallel)
  • Retry (-r). (--retry-failed --joblog in GNU parallel)
  • Start and resource limits: --delay, --load, and --memfree stagger new jobs and wait for available system capacity.
  • Safe exit after capturing Ctrl-C: Linux uses native SIGINT/SIGKILL signals; Windows uses Ctrl+C/Ctrl+Break followed by taskkill /T /F to terminate the process tree.
  • Continue (-c). (--resume --joblog in GNU parallel, sut it does not support multi-line commands, which are common in workflow)
  • awk -v like custom defined variables (-v). (Using Shell variable in GNU parallel)
  • Keeping output in order of input (-k). (Same -k/--keep-order in GNU parallel)
  • Exit on first error (-e): stop scheduling and clean up active child processes. (--halt 2 in GNU parallel)
  • Settable record delimiter (-D, default \n). (--recstart and --recend in GNU parallel)
  • Settable records sending to every command (-n, default 1). (-n/--max-args in GNU parallel)
  • Send record batches to commands via standard input (--pipe). (--pipe in GNU parallel)
  • Settable field delimiter (-d, default \s+). (Same -d/--delimiter in GNU parallel)
  • Practical replacement strings (like GNU parallel):
    • Input data
      • {}, full data. (Same in GNU parallel)
      • {n}, nth field in delimiter-delimited data. (Same in GNU parallel)
    • Job related
      • {#}, job ID. With --continue, its saved form is stable when input order changes. (Same replacement string as GNU parallel.)
      • {?}, threads per job, computed as max(1, CPUs / jobs). With --continue, its saved form stays stable when the job count changes. (Not directly supported in GNU parallel)
    • Directory and file
      • {/}, dirname. ({//} in GNU parallel)
      • {%}, basename. ({/} in GNU parallel)
      • {.}, remove the last file extension. (Same in GNU parallel)
      • {:}, remove all file extensions (Not directly supported in GNU parallel)
      • {^suffix}, remove suffix (Not directly supported in GNU parallel)
      • {@regexp}, capture submatch using regular expression (Not directly supported in GNU parallel). There's a limitation here: curly brackets can't be used in the regular expression.
    • Combinations
      • {%.}, {%:}, basename without extension
      • {2.}, {2/}, {2%.}, manipulate nth field
      • {file:}, {file:^_1}, remove all extensions of a preset variable (see below)
    • Special symbols
      • {{}}, {} itself
      • {{1,}}, data containing double quotes {1,}.
  • Preset variable (macro), e.g., rush -v p={^suffix} 'echo {p}_new_suffix', where {p} is replaced with {^suffix}. (Using Shell variable in GNU parallel)

Minor:

  • Dry run (--dry-run). (Same in GNU parallel)
  • Trim input data (--trim). (Same in GNU parallel)
  • Verbose output (--verbose). (Same in GNU parallel)

Note that the comparison between rush and GNU parallel might be outdated, as both tools might have improved. See Differences between rush and GNU parallel on GNU parallel site.

Performance

Performance of rush is similar to gargs, and they are both slightly faster than parallel (Perl) and both slower than Rust parallel (discussion).

Note that speed is not the #.1 target, especially for processes that last long.

Installation

rush is implemented in Go programming language, executable binary files for most popular operating systems are freely available in release page.

Method 0: Conda

Install conda, then run

conda install -c conda-forge rush

Or use mamba, which is faster.

mamba install -c conda-forge rush

Windows: Scoop

Add the rush Scoop bucket and install the published Windows build:

scoop bucket add rush https://github.com/shenwei356/rush
scoop install rush/rush

Scoop selects the 32-bit, 64-bit, or ARM64 binary for your system. The bucket manifest is updated automatically after a stable release is published with all three Windows archives. To get the new version, run scoop update followed by scoop update rush.

Method 1: Download binaries

rush v0.11.0 Github Releases (by Release)

Tip: run rush -V to check update !!!

Just download compressed executable file of your operating system, and decompress it with tar -zxvf *.tar.gz command or other tools. And then:

  1. For Linux-like systems

    1. If you have root privilege simply copy it to /usr/local/bin:

       sudo cp rush /usr/local/bin/
      
    2. Or copy to anywhere in the environment variable PATH:

       mkdir -p $HOME/bin/; cp rush $HOME/bin/
      
  2. For windows, just copy rush.exe to C:\WINDOWS\system32.

Method 2: For Go developer

go install github.com/shenwei356/rush@latest

Method 3: Compiling from source

# download Go from https://go.dev/dl
wget https://go.dev/dl/go1.25.14.linux-amd64.tar.gz

tar -zxf go1.25.14.linux-amd64.tar.gz -C $HOME/

# or 
#   echo "export PATH=$PATH:$HOME/go/bin" >> ~/.bashrc
#   source ~/.bashrc
export PATH=$PATH:$HOME/go/bin

git clone https://github.com/shenwei356/rush
cd rush

go build

# or statically-linked binary
CGO_ENABLED=0 go build -tags netgo -ldflags '-w -s'

# or cross compile for other operating systems and architectures
CGO_ENABLED=0 GOOS=openbsd GOARCH=amd64 go build -tags netgo -ldflags '-w -s'

Usage

rush -- a cross-platform command-line tool for executing jobs in parallel

Version: 0.11.0

Author: Wei Shen <shenwei356@gmail.com>

Homepage: https://github.com/shenwei356/rush

Input:
  - Input could be a list of strings or numbers, e.g., file paths.
  - Input can be given either from the STDIN or file(s) via the option -i/--infile.
  - Some options could be used to defined how the input records are parsed:
    -d, --field-delimiter   field delimiter in records (default "\s+")
    -D, --record-delimiter  record delimiter (default "\n")
    -n, --nrecords          number of records sent to a command (default 1)
    -J, --records-join-sep  record separator for joining multi-records (default "\n")
        --pipe              send each group of records to the command's standard input
    -T, --trim              trim white space (" \t\r\n") in input

Output:
  - Outputs of all commands are written to STDOUT by default,
    you can also use -o/--out-file to specify a output file.
  - Outputs of all commands are random, you can use the flag -k/--keep-order
    to keep output in order of input.
  - Outputs of all commands are buffered, you can use the flag -I/--immediate-output
    to print output immediately and interleaved.

Replacement strings in commands:
  {}          full data
  {n}         nth field in delimiter-delimited data
  {/}         dirname
  {%}         basename
  {.}         remove the last file extension
  {:}         remove all file extensions.
  {^suffix}   remove suffix
  {@regexp}   capture submatch using regular expression.
              Limitation: curly brackets can't be used in the regexp.
  {#}         job ID
  {?}         a value computed as $cpus / $jobs, which can be used as the number of
              threads for each command. This value is dynamically adjusted according
              to the number of jobs (-j/--jobs).

  With --continue, {#} and {?} are kept stable in the successful-command file,
  so changing input order or job count does not rerun otherwise unchanged jobs.

  Escaping curly brackets "{}":
    {{}}        {}
    {{1}}       {1}
    {{1,}}      {1,}
    {{a}}       {a}

  Combinations:
    {%.}, {%:}            basename without extension
    {2.}, {2/}, {2%.}     manipulate nth field
    {file:}, {file:^_1}   remove all extensions of a preset variable (see below)

Preset variable (macro):
  1. You can pass variables to the command like awk via the option -v. E.g.,
     $ seq 3 | rush -v p=prefix_ -v s=_suffix 'echo {p}{}{s}'
     prefix_3_suffix
     prefix_1_suffix
     prefix_2_suffix
  2. A variable name should start with a letter and be followed by letters, digits, or underscores.
     A regular expression is used to check them: ^[a-zA-Z][A-Za-z0-9_]*$
  3. The value could also contain replacement strings.
     # {p} will be replaced with {%:}, which computes the basename and remove all file extensions.
     $ echo a/b/c.txt.gz | rush -v 'p={%:}' 'echo {p} {p}.csv'
     c c.csv

Usage:
  rush [flags] [command] 

Examples:
  1. simple run, quoting is not necessary
      $ seq 1 10 | rush echo {}
  2. keep order
      $ seq 1 10 | rush 'echo {}' -k
  3. timeout
      $ seq 1 | rush 'sleep 2; echo {}' -t 1
  4. retry
      $ seq 1 | rush 'python script.py' -r 3
  5. dirname & basename & remove suffix
      $ echo dir/file_1.txt.gz | rush 'echo {/} {%} {^_1.txt.gz}'
      dir file.txt.gz dir/file
  6. basename without the last or any extension
      $ echo dir.d/file.txt.gz | rush 'echo {.} {:} {%.} {%:}'
      dir.d/file.txt dir.d/file file.txt file
  7. job ID, combine fields and other replacement strings
      $ echo 12 file.txt dir/s_1.fq.gz | rush 'echo job {#}: {2} {2.} {3%:^_1}'
      job 1: file.txt file s
  8. capture submatch using regular expression
      $ echo read_1.fq.gz | rush 'echo {@(.+)_\d}'
      read
  9. custom field delimiter
      $ echo a=b=c | rush 'echo {1} {2} {3}' -d =
      a b c
  10. custom record delimiter
      $ echo a=b=c | rush -D "=" -k 'echo {}'
      a
      b
      c
      $ echo abc | rush -D "" -k 'echo {}'
      a
      b
      c
  11. assign value to variable, like "awk -v"
      # seq 1 | rush 'echo Hello, {fname} {lname}!' -v fname=Wei,lname=Shen
      $ seq 1 | rush 'echo Hello, {fname} {lname}!' -v fname=Wei -v lname=Shen
      Hello, Wei Shen!

      # preset variables support extra operations as well.
      echo read_1.fq.gz | ./rush -v 'p={:^_1}' -v 'f=a.s-10.txt' 'echo {} {p} {f:} {f@s\-(\d+)}'
      read_1.fq.gz read a 10
  12. preset variable (Macro)
      # equal to: echo sample_1.fq.gz | rush 'echo {:^_1} {} {:^_1}_2.fq.gz'
      $ echo sample_1.fq.gz | rush -v p={:^_1} 'echo {p} {} {p}_2.fq.gz'
      sample sample_1.fq.gz sample_2.fq.gz
  13. save successful commands to continue in NEXT run
      $ seq 1 3 | rush 'sleep {}; echo {}' -c -t 2
      [ERRO] run cmd #1: sleep 2; echo 2: time out
      [ERRO] run cmd #2: sleep 3; echo 3: time out
  14. escape special symbols
      $ seq 1 | rush 'echo -e "a\tb" | awk "{print $1}"' -q
      a
  15. escape curly brackets "{}"
      $ echo aaa bbb ccc | sed -E "s/(\S){3,}/\1/g"
      a b c
      $ echo 1 | rush 'echo aaa bbb ccc | sed -E "s/(\S){{3,}}/\1/g"' --dry-run
      echo aaa bbb ccc | sed -E "s/(\S){3,}/\1/g"
  16. run a command with relative paths in Windows, please use backslash as the separator.
      # "brename -l -R" is used to search paths recursively
      $ brename -l -q -R -i -p "\.go$" | rush "bin\app.exe {}"
  17. send a fixed number of records to each command's standard input
      $ seq 10000 | rush --pipe -n 1000 -j 4 'wc -l'

  More examples: https://github.com/shenwei356/rush

Flags:
  -v, --assign strings            assign the value val to the variable var (format: var=val, val also
                                  supports replacement strings)
      --cleanup-time int          time to allow child processes to clean up between stop / kill signals
                                  (unit: seconds, 0 for no time) (default 3) (default 3)
  -c, --continue                  continue jobs. NOTES: 1) successful commands are saved in file (given
                                  by flag -C/--succ-cmd-file); 2) if the file does not exist, rush saves
                                  data so we can continue jobs next time; 3) if the file exists, rush
                                  ignores jobs in it and update the file; 4) skipped jobs are silent
                                  unless --verbose is used
      --delay float               minimum seconds between starting jobs (supports fractions)
      --dry-run                   print command but not run
  -q, --escape                    escape special symbols like $ which you can customize by flag
                                  -Q/--escape-symbols
  -Q, --escape-symbols string     symbols to escape (default "$#&`")
      --eta                       show ETA progress bar
  -d, --field-delimiter string    field delimiter in records, support regular expression (default "\\s+")
  -h, --help                      help for rush
  -I, --immediate-output          print output immediately and interleaved, to aid debugging
  -i, --infile strings            input data file, multi-values supported
  -j, --jobs int                  run n jobs in parallel (default value depends on your device) (default 16)
  -k, --keep-order                keep output in order of input
      --load string               start jobs only while system load is below this value (number or
                                  percent of CPUs)
      --memfree string            minimum available memory before starting jobs (bytes or K/M/G/T/P suffix)
      --no-kill-exes strings      exe names to exclude from kill signal, example: mspdbsrv.exe; or use
                                  all for all exes (default none)
      --no-stop-exes strings      exe names to exclude from stop signal, example: mspdbsrv.exe; or use
                                  all for all exes (default none)
  -n, --nrecords int              number of records sent to a command (default 1)
  -o, --out-file string           out file ("-" for stdout) (default "-")
      --pipe                      send each group of records to the command's standard input
      --print-retry-output        print output from retry commands (default true)
      --propagate-exit-status     propagate child exit status up to the exit status of rush (default true)
  -D, --record-delimiter string   record delimiter (default is "\n") (default "\n")
  -J, --records-join-sep string   record separator for joining multi-records (default is "\n") (default "\n")
  -r, --retries int               maximum retries (default 0)
      --retry-interval float      retry interval (unit: second, supports fractions like 0.5) (default 0)
  -e, --stop-on-error             stop scheduling and clean up active child processes on first error
  -C, --succ-cmd-file string      file for saving successful commands (default "successful_cmds.rush")
  -t, --timeout int               timeout of a command (unit: seconds, 0 for no timeout) (default 0)
  -T, --trim string               trim white space (" \t\r\n") in input (available values: "l" for left,
                                  "r" for right, "lr", "rl", "b" for both side)
      --verbose                   print verbose information
  -V, --version                   print version information and check for update

--delay sets the minimum interval between process starts in seconds, for example --delay 0.5. --load 100% allows a new job only when the system's one-minute load average is below the number of CPUs; a number such as --load 4 sets an absolute threshold. --memfree 1G waits until at least 1 GiB of physical memory is available. Uppercase size suffixes use powers of 1024, and lowercase suffixes use powers of 1000. These limits apply to starts and retries; -j still caps the number of concurrent jobs.

If available memory falls below half the --memfree threshold, rush stops the youngest running job and places it back in the queue. Its buffered standard output is discarded; standard error already written may remain visible. This restart does not use one of its -r/--retries attempts. On Windows, the load average is estimated from the processor queue and may initially read as zero. Resource checks use system-wide values, so memory limits imposed on a container may differ from the reported available memory.

seq 10 | rush -j 4 --delay 0.5 --load 100% --memfree 1G 'run-test {}'

Examples

  1. Simple run, quoting is not necessary

     # seq 1 3 | rush 'echo {}'
     $ seq 1 3 | rush echo {}
     3
     1
     2
    
  2. Read data from file (-i)

     $ rush echo {} -i data1.txt -i data2.txt
    
  3. Keep output order (-k)

     $ seq 1 3 | rush 'echo {}' -k
     1
     2
     3
    
  4. Timeout (-t)

     $ time seq 1 | rush 'sleep 2; echo {}' -t 1
     [ERRO] run command #1: sleep 2; echo 1: time out
    
     real    0m1.010s
     user    0m0.005s
     sys     0m0.007s
    
  5. Retry (-r)

     $ seq 1 | rush 'python unexisted_script.py' -r 1
     python: can't open file 'unexisted_script.py': [Errno 2] No such file or directory
     [WARN] wait command: python unexisted_script.py: exit status 2
     python: can't open file 'unexisted_script.py': [Errno 2] No such file or directory
     [ERRO] wait command: python unexisted_script.py: exit status 2
    
  6. Input containing {} (since v0.11.0)

     $ echo "a attr{href}"="h4 text{}" | rush -T b -k -D "=" 'echo "{}"'
     a attr{href}
     h4 text{}
    
     $ echo -ne "a{},b{{}},c{d}" | rush -D , -k "echo {}"
     a{}
     b{{}}
     c{d}
    
  7. Output {} itself (since v0.7.0)

     $ echo abc | rush 'echo "{} {{}}"'
     abc {}
    
  8. Dirname ({/}) and basename ({%}) and remove custom suffix ({^suffix})

     $ echo dir/file_1.txt.gz | rush 'echo {/} {%} {^_1.txt.gz}'
     dir file_1.txt.gz dir/file
    
  9. Get basename, and remove last ({.}) or any ({:}) extension

     $ echo dir.d/file.txt.gz | rush 'echo {.} {:} {%.} {%:}'
     dir.d/file.txt dir.d/file file.txt file
    
  10. Job ID, combine fields index and other replacement strings

     $ echo 12 file.txt dir/s_1.fq.gz | rush 'echo "job {#}: {2} {2.} {3%:^_1}"'
     job 1: file.txt file s
     
    
  11. Combine {#} with -c/--continue.

     $ seq 5 | rush 'timeout 3 sh -c "sleep {}; echo \"job {#}: input {}\""' -c
     job 1: input 1
     job 2: input 2
     15:37:10.744 [ERRO] wait cmd #4: timeout 3 sh -c "sleep 4; echo \"job 4: input 4\"": exit status 124
     15:37:10.744 [ERRO] wait cmd #3: timeout 3 sh -c "sleep 3; echo \"job 3: input 3\"": exit status 124
     15:37:10.744 [ERRO] wait cmd #5: timeout 3 sh -c "sleep 5; echo \"job 5: input 5\"": exit status 124
     
     $ cat successful_cmds.rush 
     timeout 3 sh -c "sleep 1; echo \"job {#}: input 1\""__CMD__
     timeout 3 sh -c "sleep 2; echo \"job {#}: input 2\""__CMD__
     
     $ seq 5 | rush 'timeout 3 sh -c "sleep {}; echo \"job {#}: input {}\""' -c
     15:37:19.186 [ERRO] wait cmd #2: timeout 3 sh -c "sleep 4; echo \"job 4: input 4\"": exit status 124
     15:37:19.186 [ERRO] wait cmd #1: timeout 3 sh -c "sleep 3; echo \"job 3: input 3\"": exit status 124
     15:37:19.186 [ERRO] wait cmd #3: timeout 3 sh -c "sleep 5; echo \"job 5: input 5\"": exit status 124
    
  12. Capture submatch using regular expression ({@regexp})

     $ echo read_1.fq.gz | rush 'echo {@(.+)_\d}'
    
  13. Custom field delimiter (-d)

     $ echo a=b=c | rush 'echo {1} {2} {3}' -d =
     a b c
    
  14. Send multi-lines to every command (-n)

     $ seq 5 | rush -n 2 -k 'echo "{}"; echo'
     1
     2
    
     3
     4
    
     5
    
     # Multiple records are joined with separator `"\n"` (`-J/--records-join-sep`)
     $ seq 5 | rush -n 2 -k 'echo "{}"; echo' -J ' '
     1 2
    
     3 4
    
     5
    
     $ seq 5 | rush -n 2 -k -j 3 'echo {1}'
     1
     3
     5
    
  15. Send record batches to the command's standard input (--pipe)

     $ seq 5 | rush --pipe -n 2 -k 'wc -l'
     2
     2
     1
    

    -n sets the maximum number of records in each batch. -D controls the input record delimiter. A delimiter terminating a non-empty record is preserved, while an unterminated final record remains unterminated. Empty records are ignored, as in normal mode. -J only affects record placeholders and does not change data sent to standard input.

    Retries receive the same batch again. With --continue, a batch is identified by both the expanded command and a digest of its standard input. Replacement strings remain available, but omit record placeholders such as {} when the goal is to avoid shell command-line size limits.

    rush currently reads all input before starting jobs. --pipe avoids command-line size limits, but does not yet provide streaming block processing.

  16. Custom record delimiter (-D), note that empty records are not used.

     $ echo a b c d | rush -D " " -k 'echo {}'
     a
     b
     c
     d
    
     $ echo abcd | rush -D "" -k 'echo {}'
     a
     b
     c
     d
    
     # FASTA format
     $ echo -ne ">seq1\nactg\n>seq2\nAAAA\n>seq3\nCCCC"
     >seq1
     actg
     >seq2
     AAAA
     >seq3
     CCCC
    
     $ echo -ne ">seq1\nactg\n>seq2\nAAAA\n>seq3\nCCCC" | rush -D ">" 'echo FASTA record {#}: name: {1} sequence: {2}' -k -d "\n"
     FASTA record 1: name: seq1 sequence: actg
     FASTA record 2: name: seq2 sequence: AAAA
     FASTA record 3: name: seq3 sequence: CCCC
    
  17. Assign value to variable, like awk -v (-v)

     $ seq 1  | rush 'echo Hello, {fname} {lname}!' -v fname=Wei -v lname=Shen
     Hello, Wei Shen!
    
     $ seq 1  | rush 'echo Hello, {fname} {lname}!' -v fname=Wei,lname=Shen
     Hello, Wei Shen!
    
     $ for var in a b; do \
     $   seq 1 3 | rush -k -v var=$var 'echo var: {var}, data: {}'; \
     $ done
     var: a, data: 1
     var: a, data: 2
     var: a, data: 3
     var: b, data: 1
     var: b, data: 2
     var: b, data: 3
     
    
  18. Preset variables support extra operations as well!!!

     $ echo read_1.fq.gz | ./rush -v 'p={:^_1}' -v 'f=a.s-10.txt' 'echo {} {p} {f:} {f@s\-(\d+)}'
     read_1.fq.gz read a 10
    
  19. Preset variable (-v), avoid repeatedly writing verbose replacement strings

     # naive way
     $ echo read_1.fq.gz | rush 'echo {:^_1} {:^_1}_2.fq.gz'
     read read_2.fq.gz
    
     # macro + removing suffix
     $ echo read_1.fq.gz | rush -v p='{:^_1}' 'echo {p} {p}_2.fq.gz'
    
     # macro + regular expression
     $ echo read_1.fq.gz | rush -v p='{@(.+?)_\d}' 'echo {p} {p}_2.fq.gz'
    
  20. Escape special symbols

     $ seq 1 | rush 'echo "I have $100"'
     I have 00
     $ seq 1 | rush 'echo "I have $100"' -q
     I have $100
     $ seq 1 | rush 'echo "I have $100"' -q --dry-run
     echo "I have \$100"
    
     $ seq 1 | rush 'echo -e "a\tb" | awk "{print $1}"'
     a       b
    
     $ seq 1 | rush 'echo -e "a\tb" | awk "{print $1}"' -q
     a
    
  21. Interrupt jobs by Ctrl-C, rush will stop unfinished commands and exit.

    Process cleanup differs by platform:

    • Linux: rush sends SIGINT to every marked child process, waits up to --cleanup-time, and then sends SIGKILL to any remaining processes using native system calls.
    • Windows: rush starts each command in a new process group and sends that group a directed Ctrl+Break, followed by forced tree cleanup. Windows cannot direct Ctrl+C to a child group; a user Ctrl+C still makes rush exit with status 130.
    • Unix Ctrl+C exits with status 130 and SIGTERM exits with status 143. A command timeout exits with status 124.
    • Cleanup covers ordinary descendants that remain in the Unix process group or Windows parent/child tree. Processes that deliberately detach, daemonize, create a new console/session, or use Windows breakaway are outside this guarantee.

    Press Ctrl-C again to skip the remaining cleanup delay and immediately kill unfinished processes. Commands that have not started are discarded after the interrupt and are not executed.

     $ seq 1 20 | rush -j 4 'sleep 1; echo {}'
     4
     1
     2
     3
     ^C23:16:30.725 [CRIT] received an interrupt, stopping unfinished commands...
     23:16:30.741 [ERRO] cancelled
     23:16:30.741 [ERRO] cancelled
     23:16:30.741 [ERRO] cancelled
     23:16:30.741 [ERRO] cancelled
    
  22. Continue/resume jobs (-c). When some jobs failed (by execution failure, timeout, or cancelling by user with Ctrl + C), please switch flag -c/--continue on and run again, so that rush can save successful commands and ignore them in NEXT run. Skipped commands are silent by default; use --verbose to print each one.

     $ seq 1 3 | rush 'sleep {}; echo {}' -t 3 -c
     1
     2
     [ERRO] run cmd #3: sleep 3; echo 3: time out
    
     # successful commands:
     $ cat successful_cmds.rush
     sleep 1; echo 1__CMD__
     sleep 2; echo 2__CMD__
    
     # run again
     $ seq 1 3 | rush 'sleep {}; echo {}' -t 3 -c
     [ERRO] run cmd #1: sleep 3; echo 3: time out
    

    Commands of multi-lines (Not supported in GNU parallel)

     $ seq 1 3 | rush 'sleep {}; echo {}; \
     echo finish {}' -t 3 -c -C finished.rush
     1
     finish 1
     2
     finish 2
     [ERRO] run cmd #3: sleep 3; echo 3; \
     echo finish 3: time out
    
     $ cat finished.rush
     sleep 1; echo 1; \
     echo finish 1__CMD__
     sleep 2; echo 2; \
     echo finish 2__CMD__
    
     # run again
     $ seq 1 3 | rush 'sleep {}; echo {}; \
     echo finish {}' -t 3 -c -C finished.rush
     [ERRO] run cmd #1: sleep 3; echo 3; \
     echo finish 3: time out
    

    Commands are saved to file (-C) right after it finished, so we can view the check finished jobs:

     grep -c __CMD__ successful_cmds.rush
    
  23. A comprehensive example: downloading 1K+ pages given by three URL list files using phantomjs save_page.js (some page contents are dynamicly generated by Javascript, so wget does not work). Here I set max jobs number (-j) as 20, each job has a max running time (-t) of 60 seconds and 3 retry changes (-r). Continue flag -c is also switched on, so we can continue unfinished jobs. Luckily, it's accomplished in one run :smile:

     $ for f in $(seq 2014 2016); do \
     $    /bin/rm -rf $f; mkdir -p $f; \
     $    cat $f.html.txt | rush -v d=$f -d = 'phantomjs save_page.js "{}" > {d}/{3}.html' -j 20 -t 60 -r 3 -c; \
     $ done
    
  24. A bioinformatics example: mapping with bwa, and processing result with samtools:

     $ tree raw.cluster.clean.mapping
     raw.cluster.clean.mapping
     ├── M1
     │   ├── M1_1.fq.gz -> ../../raw.cluster.clean/M1/M1_1.fq.gz
     │   ├── M1_2.fq.gz -> ../../raw.cluster.clean/M1/M1_2.fq.gz
     ...
    
     $ ref=ref/xxx.fa
     $ threads=25
     $ ls -d raw.cluster.clean.mapping/* \
         | rush -v ref=$ref -v j=$threads \
             'bwa mem -t {j} -M -a {ref} {}/{%}_1.fq.gz {}/{%}_2.fq.gz > {}/{%}.sam; \
             samtools view -bS {}/{%}.sam > {}/{%}.bam; \
             samtools sort -T {}/{%}.tmp -@ {j} {}/{%}.bam -o {}/{%}.sorted.bam; \
             samtools index {}/{%}.sorted.bam; \
             samtools flagstat {}/{%}.sorted.bam > {}/{%}.sorted.bam.flagstat; \
             /bin/rm {}/{%}.bam {}/{%}.sam;' \
             -j 2 --verbose -c -C mapping.rush
    

    Since {}/{%} appears many times, we can use preset variable (macro) to simplify it:

     $ ls -d raw.cluster.clean.mapping/* \
         | rush -v ref=$ref -v j=$threads -v p='{}/{%}' \
             'bwa mem -t {j} -M -a {ref} {p}_1.fq.gz {p}_2.fq.gz > {p}.sam; \
             samtools view -bS {p}.sam > {p}.bam; \
             samtools sort -T {p}.tmp -@ {j} {p}.bam -o {p}.sorted.bam; \
             samtools index {p}.sorted.bam; \
             samtools flagstat {p}.sorted.bam > {p}.sorted.bam.flagstat; \
             /bin/rm {p}.bam {p}.sam;' \
             -j 2 --verbose -c -C mapping.rush
    

Special Cases

  • Shell grep returns exit code 1 when no matches found. rush thinks it failed to run. Please use grep foo bar || true instead of grep foo bar.

      $ seq 1 | rush 'echo abc | grep 123'
      [ERRO] wait cmd #1: echo abc | grep 123: exit status 1
      $ seq 1 | rush 'echo abc | grep 123 || true'
    

Contributors

Main contributors:

Others contributors

Acknowledgements

Specially thank @brentp and his gargs, from which rush borrows some ideas.

Thank @bburgin for his contribution on improvement of child process management.

Contact

Create an issue to report bugs, propose new functions or ask for help.

License

MIT License

bioinformatics
command
cross-platform
execute
golang
parallel
pipeline
shell
windows

Contributors

shenwei356

204 commits

bburgin

3 commits

tekumara

2 commits

shenwei356/rush

A cross-platform command-line tool for executing jobs in parallel

Go

1,134

217 commits

updated Sep 21, 2026

See the code

README

rush -- a cross-platform command-line tool for executing jobs in parallel

Built with GoLang Cross-platform Latest Version Github Releases

rush is a tool similar to GNU parallel and gargs. rush borrows some idea from them and has some unique features, e.g., supporting custom defined variables, resuming multi-line commands, more advanced embeded replacement strings.

These features make rush suitable for easily and flexibly parallelizing complex workflows in fields like Bioinformatics (see examples).

Table of Contents

Features

Major:

  • Supporting Linux, OS X and Windows (not CygWin)!
  • Avoid mixed line from multiple processes without loss of performance, e.g. the first half of a line is from one process and the last half of the line is from another process. (--line-buffer in GNU parallel)
  • Timeout (-t), terminating the timed-out command and its child-process tree. (--timeout in GNU parallel)
  • Retry (-r). (--retry-failed --joblog in GNU parallel)
  • Start and resource limits: --delay, --load, and --memfree stagger new jobs and wait for available system capacity.
  • Safe exit after capturing Ctrl-C: Linux uses native SIGINT/SIGKILL signals; Windows uses Ctrl+C/Ctrl+Break followed by taskkill /T /F to terminate the process tree.
  • Continue (-c). (--resume --joblog in GNU parallel, sut it does not support multi-line commands, which are common in workflow)
  • awk -v like custom defined variables (-v). (Using Shell variable in GNU parallel)
  • Keeping output in order of input (-k). (Same -k/--keep-order in GNU parallel)
  • Exit on first error (-e): stop scheduling and clean up active child processes. (--halt 2 in GNU parallel)
  • Settable record delimiter (-D, default \n). (--recstart and --recend in GNU parallel)
  • Settable records sending to every command (-n, default 1). (-n/--max-args in GNU parallel)
  • Send record batches to commands via standard input (--pipe). (--pipe in GNU parallel)
  • Settable field delimiter (-d, default \s+). (Same -d/--delimiter in GNU parallel)
  • Practical replacement strings (like GNU parallel):
    • Input data
      • {}, full data. (Same in GNU parallel)
      • {n}, nth field in delimiter-delimited data. (Same in GNU parallel)
    • Job related
      • {#}, job ID. With --continue, its saved form is stable when input order changes. (Same replacement string as GNU parallel.)
      • {?}, threads per job, computed as max(1, CPUs / jobs). With --continue, its saved form stays stable when the job count changes. (Not directly supported in GNU parallel)
    • Directory and file
      • {/}, dirname. ({//} in GNU parallel)
      • {%}, basename. ({/} in GNU parallel)
      • {.}, remove the last file extension. (Same in GNU parallel)
      • {:}, remove all file extensions (Not directly supported in GNU parallel)
      • {^suffix}, remove suffix (Not directly supported in GNU parallel)
      • {@regexp}, capture submatch using regular expression (Not directly supported in GNU parallel). There's a limitation here: curly brackets can't be used in the regular expression.
    • Combinations
      • {%.}, {%:}, basename without extension
      • {2.}, {2/}, {2%.}, manipulate nth field
      • {file:}, {file:^_1}, remove all extensions of a preset variable (see below)
    • Special symbols
      • {{}}, {} itself
      • {{1,}}, data containing double quotes {1,}.
  • Preset variable (macro), e.g., rush -v p={^suffix} 'echo {p}_new_suffix', where {p} is replaced with {^suffix}. (Using Shell variable in GNU parallel)

Minor:

  • Dry run (--dry-run). (Same in GNU parallel)
  • Trim input data (--trim). (Same in GNU parallel)
  • Verbose output (--verbose). (Same in GNU parallel)

Note that the comparison between rush and GNU parallel might be outdated, as both tools might have improved. See Differences between rush and GNU parallel on GNU parallel site.

Performance

Performance of rush is similar to gargs, and they are both slightly faster than parallel (Perl) and both slower than Rust parallel (discussion).

Note that speed is not the #.1 target, especially for processes that last long.

Installation

rush is implemented in Go programming language, executable binary files for most popular operating systems are freely available in release page.

Method 0: Conda

Install conda, then run

conda install -c conda-forge rush

Or use mamba, which is faster.

mamba install -c conda-forge rush

Windows: Scoop

Add the rush Scoop bucket and install the published Windows build:

scoop bucket add rush https://github.com/shenwei356/rush
scoop install rush/rush

Scoop selects the 32-bit, 64-bit, or ARM64 binary for your system. The bucket manifest is updated automatically after a stable release is published with all three Windows archives. To get the new version, run scoop update followed by scoop update rush.

Method 1: Download binaries

rush v0.11.0 Github Releases (by Release)

Tip: run rush -V to check update !!!

Just download compressed executable file of your operating system, and decompress it with tar -zxvf *.tar.gz command or other tools. And then:

  1. For Linux-like systems

    1. If you have root privilege simply copy it to /usr/local/bin:

       sudo cp rush /usr/local/bin/
      
    2. Or copy to anywhere in the environment variable PATH:

       mkdir -p $HOME/bin/; cp rush $HOME/bin/
      
  2. For windows, just copy rush.exe to C:\WINDOWS\system32.

Method 2: For Go developer

go install github.com/shenwei356/rush@latest

Method 3: Compiling from source

# download Go from https://go.dev/dl
wget https://go.dev/dl/go1.25.14.linux-amd64.tar.gz

tar -zxf go1.25.14.linux-amd64.tar.gz -C $HOME/

# or 
#   echo "export PATH=$PATH:$HOME/go/bin" >> ~/.bashrc
#   source ~/.bashrc
export PATH=$PATH:$HOME/go/bin

git clone https://github.com/shenwei356/rush
cd rush

go build

# or statically-linked binary
CGO_ENABLED=0 go build -tags netgo -ldflags '-w -s'

# or cross compile for other operating systems and architectures
CGO_ENABLED=0 GOOS=openbsd GOARCH=amd64 go build -tags netgo -ldflags '-w -s'

Usage

rush -- a cross-platform command-line tool for executing jobs in parallel

Version: 0.11.0

Author: Wei Shen <shenwei356@gmail.com>

Homepage: https://github.com/shenwei356/rush

Input:
  - Input could be a list of strings or numbers, e.g., file paths.
  - Input can be given either from the STDIN or file(s) via the option -i/--infile.
  - Some options could be used to defined how the input records are parsed:
    -d, --field-delimiter   field delimiter in records (default "\s+")
    -D, --record-delimiter  record delimiter (default "\n")
    -n, --nrecords          number of records sent to a command (default 1)
    -J, --records-join-sep  record separator for joining multi-records (default "\n")
        --pipe              send each group of records to the command's standard input
    -T, --trim              trim white space (" \t\r\n") in input

Output:
  - Outputs of all commands are written to STDOUT by default,
    you can also use -o/--out-file to specify a output file.
  - Outputs of all commands are random, you can use the flag -k/--keep-order
    to keep output in order of input.
  - Outputs of all commands are buffered, you can use the flag -I/--immediate-output
    to print output immediately and interleaved.

Replacement strings in commands:
  {}          full data
  {n}         nth field in delimiter-delimited data
  {/}         dirname
  {%}         basename
  {.}         remove the last file extension
  {:}         remove all file extensions.
  {^suffix}   remove suffix
  {@regexp}   capture submatch using regular expression.
              Limitation: curly brackets can't be used in the regexp.
  {#}         job ID
  {?}         a value computed as $cpus / $jobs, which can be used as the number of
              threads for each command. This value is dynamically adjusted according
              to the number of jobs (-j/--jobs).

  With --continue, {#} and {?} are kept stable in the successful-command file,
  so changing input order or job count does not rerun otherwise unchanged jobs.

  Escaping curly brackets "{}":
    {{}}        {}
    {{1}}       {1}
    {{1,}}      {1,}
    {{a}}       {a}

  Combinations:
    {%.}, {%:}            basename without extension
    {2.}, {2/}, {2%.}     manipulate nth field
    {file:}, {file:^_1}   remove all extensions of a preset variable (see below)

Preset variable (macro):
  1. You can pass variables to the command like awk via the option -v. E.g.,
     $ seq 3 | rush -v p=prefix_ -v s=_suffix 'echo {p}{}{s}'
     prefix_3_suffix
     prefix_1_suffix
     prefix_2_suffix
  2. A variable name should start with a letter and be followed by letters, digits, or underscores.
     A regular expression is used to check them: ^[a-zA-Z][A-Za-z0-9_]*$
  3. The value could also contain replacement strings.
     # {p} will be replaced with {%:}, which computes the basename and remove all file extensions.
     $ echo a/b/c.txt.gz | rush -v 'p={%:}' 'echo {p} {p}.csv'
     c c.csv

Usage:
  rush [flags] [command] 

Examples:
  1. simple run, quoting is not necessary
      $ seq 1 10 | rush echo {}
  2. keep order
      $ seq 1 10 | rush 'echo {}' -k
  3. timeout
      $ seq 1 | rush 'sleep 2; echo {}' -t 1
  4. retry
      $ seq 1 | rush 'python script.py' -r 3
  5. dirname & basename & remove suffix
      $ echo dir/file_1.txt.gz | rush 'echo {/} {%} {^_1.txt.gz}'
      dir file.txt.gz dir/file
  6. basename without the last or any extension
      $ echo dir.d/file.txt.gz | rush 'echo {.} {:} {%.} {%:}'
      dir.d/file.txt dir.d/file file.txt file
  7. job ID, combine fields and other replacement strings
      $ echo 12 file.txt dir/s_1.fq.gz | rush 'echo job {#}: {2} {2.} {3%:^_1}'
      job 1: file.txt file s
  8. capture submatch using regular expression
      $ echo read_1.fq.gz | rush 'echo {@(.+)_\d}'
      read
  9. custom field delimiter
      $ echo a=b=c | rush 'echo {1} {2} {3}' -d =
      a b c
  10. custom record delimiter
      $ echo a=b=c | rush -D "=" -k 'echo {}'
      a
      b
      c
      $ echo abc | rush -D "" -k 'echo {}'
      a
      b
      c
  11. assign value to variable, like "awk -v"
      # seq 1 | rush 'echo Hello, {fname} {lname}!' -v fname=Wei,lname=Shen
      $ seq 1 | rush 'echo Hello, {fname} {lname}!' -v fname=Wei -v lname=Shen
      Hello, Wei Shen!

      # preset variables support extra operations as well.
      echo read_1.fq.gz | ./rush -v 'p={:^_1}' -v 'f=a.s-10.txt' 'echo {} {p} {f:} {f@s\-(\d+)}'
      read_1.fq.gz read a 10
  12. preset variable (Macro)
      # equal to: echo sample_1.fq.gz | rush 'echo {:^_1} {} {:^_1}_2.fq.gz'
      $ echo sample_1.fq.gz | rush -v p={:^_1} 'echo {p} {} {p}_2.fq.gz'
      sample sample_1.fq.gz sample_2.fq.gz
  13. save successful commands to continue in NEXT run
      $ seq 1 3 | rush 'sleep {}; echo {}' -c -t 2
      [ERRO] run cmd #1: sleep 2; echo 2: time out
      [ERRO] run cmd #2: sleep 3; echo 3: time out
  14. escape special symbols
      $ seq 1 | rush 'echo -e "a\tb" | awk "{print $1}"' -q
      a
  15. escape curly brackets "{}"
      $ echo aaa bbb ccc | sed -E "s/(\S){3,}/\1/g"
      a b c
      $ echo 1 | rush 'echo aaa bbb ccc | sed -E "s/(\S){{3,}}/\1/g"' --dry-run
      echo aaa bbb ccc | sed -E "s/(\S){3,}/\1/g"
  16. run a command with relative paths in Windows, please use backslash as the separator.
      # "brename -l -R" is used to search paths recursively
      $ brename -l -q -R -i -p "\.go$" | rush "bin\app.exe {}"
  17. send a fixed number of records to each command's standard input
      $ seq 10000 | rush --pipe -n 1000 -j 4 'wc -l'

  More examples: https://github.com/shenwei356/rush

Flags:
  -v, --assign strings            assign the value val to the variable var (format: var=val, val also
                                  supports replacement strings)
      --cleanup-time int          time to allow child processes to clean up between stop / kill signals
                                  (unit: seconds, 0 for no time) (default 3) (default 3)
  -c, --continue                  continue jobs. NOTES: 1) successful commands are saved in file (given
                                  by flag -C/--succ-cmd-file); 2) if the file does not exist, rush saves
                                  data so we can continue jobs next time; 3) if the file exists, rush
                                  ignores jobs in it and update the file; 4) skipped jobs are silent
                                  unless --verbose is used
      --delay float               minimum seconds between starting jobs (supports fractions)
      --dry-run                   print command but not run
  -q, --escape                    escape special symbols like $ which you can customize by flag
                                  -Q/--escape-symbols
  -Q, --escape-symbols string     symbols to escape (default "$#&`")
      --eta                       show ETA progress bar
  -d, --field-delimiter string    field delimiter in records, support regular expression (default "\\s+")
  -h, --help                      help for rush
  -I, --immediate-output          print output immediately and interleaved, to aid debugging
  -i, --infile strings            input data file, multi-values supported
  -j, --jobs int                  run n jobs in parallel (default value depends on your device) (default 16)
  -k, --keep-order                keep output in order of input
      --load string               start jobs only while system load is below this value (number or
                                  percent of CPUs)
      --memfree string            minimum available memory before starting jobs (bytes or K/M/G/T/P suffix)
      --no-kill-exes strings      exe names to exclude from kill signal, example: mspdbsrv.exe; or use
                                  all for all exes (default none)
      --no-stop-exes strings      exe names to exclude from stop signal, example: mspdbsrv.exe; or use
                                  all for all exes (default none)
  -n, --nrecords int              number of records sent to a command (default 1)
  -o, --out-file string           out file ("-" for stdout) (default "-")
      --pipe                      send each group of records to the command's standard input
      --print-retry-output        print output from retry commands (default true)
      --propagate-exit-status     propagate child exit status up to the exit status of rush (default true)
  -D, --record-delimiter string   record delimiter (default is "\n") (default "\n")
  -J, --records-join-sep string   record separator for joining multi-records (default is "\n") (default "\n")
  -r, --retries int               maximum retries (default 0)
      --retry-interval float      retry interval (unit: second, supports fractions like 0.5) (default 0)
  -e, --stop-on-error             stop scheduling and clean up active child processes on first error
  -C, --succ-cmd-file string      file for saving successful commands (default "successful_cmds.rush")
  -t, --timeout int               timeout of a command (unit: seconds, 0 for no timeout) (default 0)
  -T, --trim string               trim white space (" \t\r\n") in input (available values: "l" for left,
                                  "r" for right, "lr", "rl", "b" for both side)
      --verbose                   print verbose information
  -V, --version                   print version information and check for update

--delay sets the minimum interval between process starts in seconds, for example --delay 0.5. --load 100% allows a new job only when the system's one-minute load average is below the number of CPUs; a number such as --load 4 sets an absolute threshold. --memfree 1G waits until at least 1 GiB of physical memory is available. Uppercase size suffixes use powers of 1024, and lowercase suffixes use powers of 1000. These limits apply to starts and retries; -j still caps the number of concurrent jobs.

If available memory falls below half the --memfree threshold, rush stops the youngest running job and places it back in the queue. Its buffered standard output is discarded; standard error already written may remain visible. This restart does not use one of its -r/--retries attempts. On Windows, the load average is estimated from the processor queue and may initially read as zero. Resource checks use system-wide values, so memory limits imposed on a container may differ from the reported available memory.

seq 10 | rush -j 4 --delay 0.5 --load 100% --memfree 1G 'run-test {}'

Examples

  1. Simple run, quoting is not necessary

     # seq 1 3 | rush 'echo {}'
     $ seq 1 3 | rush echo {}
     3
     1
     2
    
  2. Read data from file (-i)

     $ rush echo {} -i data1.txt -i data2.txt
    
  3. Keep output order (-k)

     $ seq 1 3 | rush 'echo {}' -k
     1
     2
     3
    
  4. Timeout (-t)

     $ time seq 1 | rush 'sleep 2; echo {}' -t 1
     [ERRO] run command #1: sleep 2; echo 1: time out
    
     real    0m1.010s
     user    0m0.005s
     sys     0m0.007s
    
  5. Retry (-r)

     $ seq 1 | rush 'python unexisted_script.py' -r 1
     python: can't open file 'unexisted_script.py': [Errno 2] No such file or directory
     [WARN] wait command: python unexisted_script.py: exit status 2
     python: can't open file 'unexisted_script.py': [Errno 2] No such file or directory
     [ERRO] wait command: python unexisted_script.py: exit status 2
    
  6. Input containing {} (since v0.11.0)

     $ echo "a attr{href}"="h4 text{}" | rush -T b -k -D "=" 'echo "{}"'
     a attr{href}
     h4 text{}
    
     $ echo -ne "a{},b{{}},c{d}" | rush -D , -k "echo {}"
     a{}
     b{{}}
     c{d}
    
  7. Output {} itself (since v0.7.0)

     $ echo abc | rush 'echo "{} {{}}"'
     abc {}
    
  8. Dirname ({/}) and basename ({%}) and remove custom suffix ({^suffix})

     $ echo dir/file_1.txt.gz | rush 'echo {/} {%} {^_1.txt.gz}'
     dir file_1.txt.gz dir/file
    
  9. Get basename, and remove last ({.}) or any ({:}) extension

     $ echo dir.d/file.txt.gz | rush 'echo {.} {:} {%.} {%:}'
     dir.d/file.txt dir.d/file file.txt file
    
  10. Job ID, combine fields index and other replacement strings

     $ echo 12 file.txt dir/s_1.fq.gz | rush 'echo "job {#}: {2} {2.} {3%:^_1}"'
     job 1: file.txt file s
     
    
  11. Combine {#} with -c/--continue.

     $ seq 5 | rush 'timeout 3 sh -c "sleep {}; echo \"job {#}: input {}\""' -c
     job 1: input 1
     job 2: input 2
     15:37:10.744 [ERRO] wait cmd #4: timeout 3 sh -c "sleep 4; echo \"job 4: input 4\"": exit status 124
     15:37:10.744 [ERRO] wait cmd #3: timeout 3 sh -c "sleep 3; echo \"job 3: input 3\"": exit status 124
     15:37:10.744 [ERRO] wait cmd #5: timeout 3 sh -c "sleep 5; echo \"job 5: input 5\"": exit status 124
     
     $ cat successful_cmds.rush 
     timeout 3 sh -c "sleep 1; echo \"job {#}: input 1\""__CMD__
     timeout 3 sh -c "sleep 2; echo \"job {#}: input 2\""__CMD__
     
     $ seq 5 | rush 'timeout 3 sh -c "sleep {}; echo \"job {#}: input {}\""' -c
     15:37:19.186 [ERRO] wait cmd #2: timeout 3 sh -c "sleep 4; echo \"job 4: input 4\"": exit status 124
     15:37:19.186 [ERRO] wait cmd #1: timeout 3 sh -c "sleep 3; echo \"job 3: input 3\"": exit status 124
     15:37:19.186 [ERRO] wait cmd #3: timeout 3 sh -c "sleep 5; echo \"job 5: input 5\"": exit status 124
    
  12. Capture submatch using regular expression ({@regexp})

     $ echo read_1.fq.gz | rush 'echo {@(.+)_\d}'
    
  13. Custom field delimiter (-d)

     $ echo a=b=c | rush 'echo {1} {2} {3}' -d =
     a b c
    
  14. Send multi-lines to every command (-n)

     $ seq 5 | rush -n 2 -k 'echo "{}"; echo'
     1
     2
    
     3
     4
    
     5
    
     # Multiple records are joined with separator `"\n"` (`-J/--records-join-sep`)
     $ seq 5 | rush -n 2 -k 'echo "{}"; echo' -J ' '
     1 2
    
     3 4
    
     5
    
     $ seq 5 | rush -n 2 -k -j 3 'echo {1}'
     1
     3
     5
    
  15. Send record batches to the command's standard input (--pipe)

     $ seq 5 | rush --pipe -n 2 -k 'wc -l'
     2
     2
     1
    

    -n sets the maximum number of records in each batch. -D controls the input record delimiter. A delimiter terminating a non-empty record is preserved, while an unterminated final record remains unterminated. Empty records are ignored, as in normal mode. -J only affects record placeholders and does not change data sent to standard input.

    Retries receive the same batch again. With --continue, a batch is identified by both the expanded command and a digest of its standard input. Replacement strings remain available, but omit record placeholders such as {} when the goal is to avoid shell command-line size limits.

    rush currently reads all input before starting jobs. --pipe avoids command-line size limits, but does not yet provide streaming block processing.

  16. Custom record delimiter (-D), note that empty records are not used.

     $ echo a b c d | rush -D " " -k 'echo {}'
     a
     b
     c
     d
    
     $ echo abcd | rush -D "" -k 'echo {}'
     a
     b
     c
     d
    
     # FASTA format
     $ echo -ne ">seq1\nactg\n>seq2\nAAAA\n>seq3\nCCCC"
     >seq1
     actg
     >seq2
     AAAA
     >seq3
     CCCC
    
     $ echo -ne ">seq1\nactg\n>seq2\nAAAA\n>seq3\nCCCC" | rush -D ">" 'echo FASTA record {#}: name: {1} sequence: {2}' -k -d "\n"
     FASTA record 1: name: seq1 sequence: actg
     FASTA record 2: name: seq2 sequence: AAAA
     FASTA record 3: name: seq3 sequence: CCCC
    
  17. Assign value to variable, like awk -v (-v)

     $ seq 1  | rush 'echo Hello, {fname} {lname}!' -v fname=Wei -v lname=Shen
     Hello, Wei Shen!
    
     $ seq 1  | rush 'echo Hello, {fname} {lname}!' -v fname=Wei,lname=Shen
     Hello, Wei Shen!
    
     $ for var in a b; do \
     $   seq 1 3 | rush -k -v var=$var 'echo var: {var}, data: {}'; \
     $ done
     var: a, data: 1
     var: a, data: 2
     var: a, data: 3
     var: b, data: 1
     var: b, data: 2
     var: b, data: 3
     
    
  18. Preset variables support extra operations as well!!!

     $ echo read_1.fq.gz | ./rush -v 'p={:^_1}' -v 'f=a.s-10.txt' 'echo {} {p} {f:} {f@s\-(\d+)}'
     read_1.fq.gz read a 10
    
  19. Preset variable (-v), avoid repeatedly writing verbose replacement strings

     # naive way
     $ echo read_1.fq.gz | rush 'echo {:^_1} {:^_1}_2.fq.gz'
     read read_2.fq.gz
    
     # macro + removing suffix
     $ echo read_1.fq.gz | rush -v p='{:^_1}' 'echo {p} {p}_2.fq.gz'
    
     # macro + regular expression
     $ echo read_1.fq.gz | rush -v p='{@(.+?)_\d}' 'echo {p} {p}_2.fq.gz'
    
  20. Escape special symbols

     $ seq 1 | rush 'echo "I have $100"'
     I have 00
     $ seq 1 | rush 'echo "I have $100"' -q
     I have $100
     $ seq 1 | rush 'echo "I have $100"' -q --dry-run
     echo "I have \$100"
    
     $ seq 1 | rush 'echo -e "a\tb" | awk "{print $1}"'
     a       b
    
     $ seq 1 | rush 'echo -e "a\tb" | awk "{print $1}"' -q
     a
    
  21. Interrupt jobs by Ctrl-C, rush will stop unfinished commands and exit.

    Process cleanup differs by platform:

    • Linux: rush sends SIGINT to every marked child process, waits up to --cleanup-time, and then sends SIGKILL to any remaining processes using native system calls.
    • Windows: rush starts each command in a new process group and sends that group a directed Ctrl+Break, followed by forced tree cleanup. Windows cannot direct Ctrl+C to a child group; a user Ctrl+C still makes rush exit with status 130.
    • Unix Ctrl+C exits with status 130 and SIGTERM exits with status 143. A command timeout exits with status 124.
    • Cleanup covers ordinary descendants that remain in the Unix process group or Windows parent/child tree. Processes that deliberately detach, daemonize, create a new console/session, or use Windows breakaway are outside this guarantee.

    Press Ctrl-C again to skip the remaining cleanup delay and immediately kill unfinished processes. Commands that have not started are discarded after the interrupt and are not executed.

     $ seq 1 20 | rush -j 4 'sleep 1; echo {}'
     4
     1
     2
     3
     ^C23:16:30.725 [CRIT] received an interrupt, stopping unfinished commands...
     23:16:30.741 [ERRO] cancelled
     23:16:30.741 [ERRO] cancelled
     23:16:30.741 [ERRO] cancelled
     23:16:30.741 [ERRO] cancelled
    
  22. Continue/resume jobs (-c). When some jobs failed (by execution failure, timeout, or cancelling by user with Ctrl + C), please switch flag -c/--continue on and run again, so that rush can save successful commands and ignore them in NEXT run. Skipped commands are silent by default; use --verbose to print each one.

     $ seq 1 3 | rush 'sleep {}; echo {}' -t 3 -c
     1
     2
     [ERRO] run cmd #3: sleep 3; echo 3: time out
    
     # successful commands:
     $ cat successful_cmds.rush
     sleep 1; echo 1__CMD__
     sleep 2; echo 2__CMD__
    
     # run again
     $ seq 1 3 | rush 'sleep {}; echo {}' -t 3 -c
     [ERRO] run cmd #1: sleep 3; echo 3: time out
    

    Commands of multi-lines (Not supported in GNU parallel)

     $ seq 1 3 | rush 'sleep {}; echo {}; \
     echo finish {}' -t 3 -c -C finished.rush
     1
     finish 1
     2
     finish 2
     [ERRO] run cmd #3: sleep 3; echo 3; \
     echo finish 3: time out
    
     $ cat finished.rush
     sleep 1; echo 1; \
     echo finish 1__CMD__
     sleep 2; echo 2; \
     echo finish 2__CMD__
    
     # run again
     $ seq 1 3 | rush 'sleep {}; echo {}; \
     echo finish {}' -t 3 -c -C finished.rush
     [ERRO] run cmd #1: sleep 3; echo 3; \
     echo finish 3: time out
    

    Commands are saved to file (-C) right after it finished, so we can view the check finished jobs:

     grep -c __CMD__ successful_cmds.rush
    
  23. A comprehensive example: downloading 1K+ pages given by three URL list files using phantomjs save_page.js (some page contents are dynamicly generated by Javascript, so wget does not work). Here I set max jobs number (-j) as 20, each job has a max running time (-t) of 60 seconds and 3 retry changes (-r). Continue flag -c is also switched on, so we can continue unfinished jobs. Luckily, it's accomplished in one run :smile:

     $ for f in $(seq 2014 2016); do \
     $    /bin/rm -rf $f; mkdir -p $f; \
     $    cat $f.html.txt | rush -v d=$f -d = 'phantomjs save_page.js "{}" > {d}/{3}.html' -j 20 -t 60 -r 3 -c; \
     $ done
    
  24. A bioinformatics example: mapping with bwa, and processing result with samtools:

     $ tree raw.cluster.clean.mapping
     raw.cluster.clean.mapping
     ├── M1
     │   ├── M1_1.fq.gz -> ../../raw.cluster.clean/M1/M1_1.fq.gz
     │   ├── M1_2.fq.gz -> ../../raw.cluster.clean/M1/M1_2.fq.gz
     ...
    
     $ ref=ref/xxx.fa
     $ threads=25
     $ ls -d raw.cluster.clean.mapping/* \
         | rush -v ref=$ref -v j=$threads \
             'bwa mem -t {j} -M -a {ref} {}/{%}_1.fq.gz {}/{%}_2.fq.gz > {}/{%}.sam; \
             samtools view -bS {}/{%}.sam > {}/{%}.bam; \
             samtools sort -T {}/{%}.tmp -@ {j} {}/{%}.bam -o {}/{%}.sorted.bam; \
             samtools index {}/{%}.sorted.bam; \
             samtools flagstat {}/{%}.sorted.bam > {}/{%}.sorted.bam.flagstat; \
             /bin/rm {}/{%}.bam {}/{%}.sam;' \
             -j 2 --verbose -c -C mapping.rush
    

    Since {}/{%} appears many times, we can use preset variable (macro) to simplify it:

     $ ls -d raw.cluster.clean.mapping/* \
         | rush -v ref=$ref -v j=$threads -v p='{}/{%}' \
             'bwa mem -t {j} -M -a {ref} {p}_1.fq.gz {p}_2.fq.gz > {p}.sam; \
             samtools view -bS {p}.sam > {p}.bam; \
             samtools sort -T {p}.tmp -@ {j} {p}.bam -o {p}.sorted.bam; \
             samtools index {p}.sorted.bam; \
             samtools flagstat {p}.sorted.bam > {p}.sorted.bam.flagstat; \
             /bin/rm {p}.bam {p}.sam;' \
             -j 2 --verbose -c -C mapping.rush
    

Special Cases

  • Shell grep returns exit code 1 when no matches found. rush thinks it failed to run. Please use grep foo bar || true instead of grep foo bar.

      $ seq 1 | rush 'echo abc | grep 123'
      [ERRO] wait cmd #1: echo abc | grep 123: exit status 1
      $ seq 1 | rush 'echo abc | grep 123 || true'
    

Contributors

Main contributors:

Others contributors

Acknowledgements

Specially thank @brentp and his gargs, from which rush borrows some ideas.

Thank @bburgin for his contribution on improvement of child process management.

Contact

Create an issue to report bugs, propose new functions or ask for help.

License

MIT License

bioinformatics
command
cross-platform
execute
golang
parallel
pipeline
shell
windows

Contributors

shenwei356

204 commits

bburgin

3 commits

tekumara

2 commits

Languages

Go

95.8%

Shell

2.2%

Python

1.9%