rush run

The run command evaluates an R expression and prints (or saves) the result. It is the core command of rush.

Usage

rush run [options] [<expression>] [--] [<file>...]

Simple expressions

At its core, rush run evaluates any R expression and prints the result:

rush run '6 * 7'
#> 42

Multiple semicolon-separated expressions work; the value of the last one is printed:

rush run 'x <- 1:10; mean(x)'
#> 5.5

Strings, vectors, and lists all print naturally:

rush run 'paste("Hello", "from", "R", sep = ", ")'
#> Hello, from, R
rush run 'rev(LETTERS[1:8])'
#> H
#> G
#> F
#> E
#> D
#> C
#> B
#> A

Reading input files

Pass a file path after the expression, and it is read into a data frame called df:

rush run 'nrow(df)' penguins.csv
#> 333

With just a file and no expression, the data is printed. A quick way to peek:

rush run 'head(df, 5)' penguins.csv
#> species,island,bill_length_mm,bill_depth_mm,flipper_length_mm,body_mass_g,sex,year
#> Adelie,Torgersen,39.1,18.7,181,3750,male,2007
#> Adelie,Torgersen,39.5,17.4,186,3800,female,2007
#> Adelie,Torgersen,40.3,18,195,3250,female,2007
#> Adelie,Torgersen,36.7,19.3,193,3450,female,2007
#> Adelie,Torgersen,39.3,20.6,190,3650,male,2007

The df and dfs variables

Two variables are always available in your expression:

  • df: the first input (a data frame for flat files, or a named list of tables for database files).
  • dfs: a named list of all inputs, keyed by file stem. Database entries are nested one level deeper: dfs$dbname$tablename.
rush run 'names(dfs)' penguins.csv cities.tsv
#> penguins
#> cities
rush run 'nrow(dfs[["penguins"]])' penguins.csv cities.tsv
#> 333

Reading from standard input

Use - to read from stdin. This makes rush composable in shell pipelines:

echo "name,score
Alice,92
Bob,85
Carol,97" | rush run 'df |> dplyr::arrange(dplyr::desc(score))' -
#> name,score
#> Carol,97
#> Alice,92
#> Bob,85

You can pipe the output of one rush into another:

rush run 'head(df, 5)' penguins.csv | rush run 'dplyr::select(df, species, bill_length_mm, body_mass_g)' -
#> species,bill_length_mm,body_mass_g
#> Adelie,39.1,3750
#> Adelie,39.5,3800
#> Adelie,40.3,3250
#> Adelie,36.7,3450
#> Adelie,39.3,3650

Use -F to read non-CSV formats from stdin, for example JSON:

rush run -O json 'head(df, 3)' penguins.csv | rush run -F json 'dplyr::select(df, species, body_mass_g)' -
#> species,body_mass_g
#> Adelie,3750
#> Adelie,3800
#> Adelie,3250

Reading DuckDB databases

Point rush at a .duckdb file and all tables are loaded into dfs:

rush run 'names(dfs$shop)' shop.duckdb
#> customers
#> orders

Options

--delimiter (-d)

Override the delimiter for both reading and writing:

rush run -d "\t" 'head(df, 3)' cities.tsv
#> city\pop\province
#> Amsterdam\921402\Noord-Holland
#> Rotterdam\655468\Zuid-Holland
#> Utrecht\361924\Utrecht

Write tab-separated output:

rush run -d "\t" 'head(df, 3)' penguins.csv | cat
#> species_island_bill_length_mm_bill_depth_mm_flipper_length_mm_body_mass_g_sex_year
#> Adelie,Torgersen,39.1,18.7,181,3750,male,2007
#> Adelie,Torgersen,39.5,17.4,186,3800,female,2007
#> Adelie,Torgersen,40.3,18,195,3250,female,2007

--no-header (-H)

Suppress headers on both input and output. Input columns are auto-named x1, x2, …:

rush run -H 'sum(df$x1)' numbers.txt
#> 150
seq 5 | rush run -H 'cumsum(df$x1)' -
#> 1
#> 3
#> 6
#> 10
#> 15

--no-input-header

Read without a header row, but still include a header in the output:

seq 5 | rush run --no-input-header 'cumsum(df$x1)' -
#> 1
#> 3
#> 6
#> 10
#> 15

--no-output-header

Read normally but omit the header row from the output:

rush run --no-output-header 'head(df, 3)' penguins.csv | cat
#> Adelie,Torgersen,39.1,18.7,181,3750,male,2007
#> Adelie,Torgersen,39.5,17.4,186,3800,female,2007
#> Adelie,Torgersen,40.3,18,195,3250,female,2007

--names

Provide explicit column names (comma-separated). Implies --no-input-header:

echo -e "1,Alice\n2,Bob" | rush run --names "id,name" 'df'

--no-clean-names (-C)

By default, rush normalizes column names (lowercase, underscores). Disable with -C to preserve the originals:

echo "First Name,Last Name,Score (%)
Alice,Smith,92" | rush run 'names(df)' -
#> first_name
#> last_name
#> score_percent
echo "First Name,Last Name,Score (%)
Alice,Smith,92" | rush run -C 'names(df)' -
#> First Name
#> Last Name
#> Score (%)

--tidyverse (-t)

Load the entire Tidyverse (plus glue) so you can use dplyr, tidyr, stringr, etc. without qualifying names:

rush run -t 'df |> group_by(species) |> summarise(mean_mass = mean(body_mass_g)) |> arrange(desc(mean_mass))' penguins.csv
#> species,mean_mass
#> Gentoo,5092.436974789916
#> Chinstrap,3733.0882352941176
#> Adelie,3706.1643835616437

--library (-l)

Load specific packages. Comma-separate multiple packages:

rush run -l stringr 'stringr::str_to_title("hello world")'
#> Hello World
rush run -l dplyr,tidyr 'df |>
  tidyr::pivot_longer(cols = c(bill_length_mm, bill_depth_mm), names_to = "metric") |>
  dplyr::slice_head(n = 4)' penguins.csv
#> species,island,flipper_length_mm,body_mass_g,sex,year,metric,value
#> Adelie,Torgersen,181,3750,male,2007,bill_length_mm,39.1
#> Adelie,Torgersen,181,3750,male,2007,bill_depth_mm,18.7
#> Adelie,Torgersen,186,3800,female,2007,bill_length_mm,39.5
#> Adelie,Torgersen,186,3800,female,2007,bill_depth_mm,17.4

--seed

Set the random seed for reproducible results:

rush run --seed 42 'sample(1:100, 5)'
#> 49
#> 65
#> 25
#> 74
#> 18

Run it again; same seed, same result:

rush run --seed 42 'sample(1:100, 5)'
#> 49
#> 65
#> 25
#> 74
#> 18

--output (-o)

Write the result to a file instead of stdout. The output format is inferred from the extension:

rush run -o summary.csv 'data.frame(stat = c("min", "mean", "max"),
                                     mass = c(min(df$body_mass_g), mean(df$body_mass_g), max(df$body_mass_g)))' penguins.csv
cat summary.csv
#> stat,mass
#> min,2700
#> mean,4207.057057057057
#> max,6300

Write to Parquet:

rush run -o subset.parquet 'dplyr::filter(df, body_mass_g > 5000)' penguins.csv
rush run 'df' subset.parquet
#> species,island,bill_length_mm,bill_depth_mm,flipper_length_mm,body_mass_g,sex,year
#> Gentoo,Biscoe,50,16.3,230,5700,male,2007
#> Gentoo,Biscoe,50,15.2,218,5700,male,2007
#> Gentoo,Biscoe,47.6,14.5,215,5400,male,2007
#> Gentoo,Biscoe,46.7,15.3,219,5200,male,2007
#> Gentoo,Biscoe,46.8,15.4,215,5150,male,2007
#> Gentoo,Biscoe,49,16.1,216,5550,male,2007
#> Gentoo,Biscoe,48.4,14.6,213,5850,male,2007
#> Gentoo,Biscoe,49.3,15.7,217,5850,male,2007
#> Gentoo,Biscoe,49.2,15.2,221,6300,male,2007
#> Gentoo,Biscoe,48.7,15.1,222,5350,male,2007
#> Gentoo,Biscoe,50.2,14.3,218,5700,male,2007
#> Gentoo,Biscoe,46.3,15.8,215,5050,male,2007
#> Gentoo,Biscoe,46.1,15.1,215,5100,male,2007
#> Gentoo,Biscoe,47.8,15,215,5650,male,2007
#> Gentoo,Biscoe,50,15.3,220,5550,male,2007
#> Gentoo,Biscoe,47.3,15.3,222,5250,male,2007
#> Gentoo,Biscoe,45.1,14.5,207,5050,female,2007
#> Gentoo,Biscoe,59.6,17,230,6050,male,2007
#> Gentoo,Biscoe,49.1,14.8,220,5150,female,2008
#> Gentoo,Biscoe,48.4,16.3,220,5400,male,2008
#> Gentoo,Biscoe,44.4,17.3,219,5250,male,2008
#> Gentoo,Biscoe,48.7,15.7,208,5350,male,2008
#> Gentoo,Biscoe,49.6,16,225,5700,male,2008
#> Gentoo,Biscoe,50.5,15.9,222,5550,male,2008
#> Gentoo,Biscoe,50.5,15.9,225,5400,male,2008
#> Gentoo,Biscoe,44.9,13.3,213,5100,female,2008
#> Gentoo,Biscoe,45.2,15.8,215,5300,male,2008
#> Gentoo,Biscoe,48.5,14.1,220,5300,male,2008
#> Gentoo,Biscoe,45,15.4,220,5050,male,2008
#> Gentoo,Biscoe,50.4,15.3,224,5550,male,2008
#> Gentoo,Biscoe,46.2,14.9,221,5300,male,2008
#> Gentoo,Biscoe,54.3,15.7,231,5650,male,2008
#> Gentoo,Biscoe,49.8,16.8,230,5700,male,2008
#> Gentoo,Biscoe,49.5,16.2,229,5800,male,2008
#> Gentoo,Biscoe,50.7,15,223,5550,male,2008
#> Gentoo,Biscoe,48.2,15.6,221,5100,male,2008
#> Gentoo,Biscoe,46.5,14.8,217,5200,female,2008
#> Gentoo,Biscoe,48.6,16,230,5800,male,2008
#> Gentoo,Biscoe,51.1,16.3,220,6000,male,2008
#> Gentoo,Biscoe,45.2,16.4,223,5950,male,2008
#> Gentoo,Biscoe,52.5,15.6,221,5450,male,2009
#> Gentoo,Biscoe,50,15.9,224,5350,male,2009
#> Gentoo,Biscoe,50.8,17.3,228,5600,male,2009
#> Gentoo,Biscoe,51.3,14.2,218,5300,male,2009
#> Gentoo,Biscoe,52.1,17,230,5550,male,2009
#> Gentoo,Biscoe,52.2,17.1,228,5400,male,2009
#> Gentoo,Biscoe,49.5,16.1,224,5650,male,2009
#> Gentoo,Biscoe,50.8,15.7,226,5200,male,2009
#> Gentoo,Biscoe,51.1,16.5,225,5250,male,2009
#> Gentoo,Biscoe,55.9,17,228,5600,male,2009
#> Gentoo,Biscoe,49.1,15,228,5500,male,2009
#> Gentoo,Biscoe,46.8,16.1,215,5500,male,2009
#> Gentoo,Biscoe,53.4,15.8,219,5500,male,2009
#> Gentoo,Biscoe,48.1,15.1,209,5500,male,2009
#> Gentoo,Biscoe,49.8,15.9,229,5950,male,2009
#> Gentoo,Biscoe,51.5,16.3,230,5500,male,2009
#> Gentoo,Biscoe,55.1,16,230,5850,male,2009
#> Gentoo,Biscoe,48.8,16.2,222,6000,male,2009
#> Gentoo,Biscoe,50.4,15.7,222,5750,male,2009
#> Gentoo,Biscoe,45.2,14.8,212,5200,female,2009
#> Gentoo,Biscoe,49.9,16.1,213,5400,male,2009

--output-root, --output-record

Customize the XML element names when writing XML. Defaults are root (root) and record (each record). Also used by TOML for the record name:

rush run -n -O xml --output-root plants --output-record observation 'head(df)' data.csv
#> #!/usr/bin/env -S ir run
#> #| packages:
#> #|   - rush
#> 
#> rush::init(
#>   output_format = "xml",
#>   dpi = 300L,
#>   output_root = "plants",
#>   output_record = "observation"
#> )
#> 
#> dfs <- list()
#> dfs[["data"]] <- rush::read("data.csv")
#> df <- dfs[[1]]
#> result <- head(df)
#> 
#> rush::write(result)

--dry-run (-n)

Print the generated script instead of running it. Great for learning what rush does under the hood, or for saving scripts:

rush run -n 'df |> dplyr::filter(body_mass_g > 5000)' penguins.csv
#> #!/usr/bin/env -S ir run
#> #| packages:
#> #|   - rush
#> 
#> rush::init(
#>   dpi = 300L
#> )
#> 
#> dfs <- list()
#> dfs[["penguins"]] <- rush::read("penguins.csv")
#> df <- dfs[[1]]
#> result <- dplyr::filter(df, body_mass_g > 5000)
#> 
#> rush::write(result)

--no-ir (-I)

Run with plain Rscript instead of ir. No package resolution happens – everything must already be installed:

rush run -I '2 + 2'
#> 4

--verbose (-v)

Show parsed arguments before running. Useful for debugging complex invocations:

rush run -v --seed 7 'sum(1:10)' 2>&1
#> ── Arguments ───────────────────────────────────────────────────────────────────
#> • command         <chr> "run"
#> • delimiter       <chr> ","
#> • dpi             <int> 300L
#> • dry_run         <lgl> FALSE
#> • expression      <language> list(sum(1:10))
#> • file            <list> list()
#> • head            <NULL> 
#> • height          <NULL> 
#> • help            <lgl> FALSE
#> • input_delimiter <NULL> 
#> • input_format    <chr> "auto"
#> • input_sheet     <NULL> 
#> • library         <NULL> 
#> • names           <NULL> 
#> • no_clean_names  <lgl> FALSE
#> • no_header       <lgl> FALSE
#> • no_input_header <lgl> FALSE
#> • no_ir           <lgl> FALSE
#> • no_output_header <lgl> FALSE
#> • no_rush         <lgl> FALSE
#> • output          <NULL> 
#> • output_delimiter <NULL> 
#> • output_format   <chr> "auto"
#> • output_indent   <int> 2L
#> • output_record   <chr> "record"
#> • output_root     <chr> "root"
#> • output_sheet    <NULL> 
#> • run             <lgl> TRUE
#> • seed            <int> 7L
#> • tidyverse       <lgl> FALSE
#> • units           <chr> "in"
#> • verbose         <lgl> TRUE
#> • version         <lgl> FALSE
#> • width           <NULL> 
#> • resolved_no_input_header <lgl> FALSE
#> • resolved_no_output_header <lgl> FALSE
#> • resolved_col_names <lgl> TRUE
#> • resolved_input_delimiter <chr> ","
#> • resolved_output_delimiter <chr> ","
#> • resolved_output_format <chr> "delim"
#> ────────────────────────────────────────────────────────────────────────────────
#> 55

Help

rush run -h
#> rush: Run an R expression
#> 
#> Usage:
#>   rush run [options] [<expression>] [--] [<file>...]
#> 
#> Arguments:
#>   <expression>             R expression to evaluate. The value of the last
#>                            expression is printed or written out.
#>   <file>                   Data file(s) to read. Two variables are always set:
#>                            'df' (the first input) and 'dfs' (a named list of
#>                            all inputs, keyed by file stem). Database files
#>                            (.duckdb, .sqlite) are nested one level deeper:
#>                            dfs$dbname$tablename. The reader is chosen by
#>                            extension. Use '-' to read from standard input
#>                            (becomes dfs$stdin).
#> 
#> Note: -d/--delimiter sets both input and output delimiter. Use -F, -D, or -O
#> to override format and delimiter for each side independently.
#> 
#> Reading options:
#>   -d, --delimiter <str>         Delimiter (input and output) [default: ,].
#>       --input-delimiter <str>   Input delimiter (overrides -d).
#>   -F, --input-format <format>   Input format [default: auto].
#>       --input-sheet <name|int>  Excel sheet to read.
#>       --names <a,b,c>           Column names (implies no input header).
#>   -C, --no-clean-names          No clean names.
#>   -H, --no-header               No header (input and output).
#>       --no-input-header         No input header.
#>       --no-output-header        No output header.
#> 
#> Setup options:
#>   -l, --library <name>          Libraries to load.
#>   -t, --tidyverse               Enter the Tidyverse.
#> 
#> Saving options:
#>       --dpi <int>               Plot resolution [default: 300].
#>       --head <int>              Limit output rows.
#>       --height <num>            Plot height.
#>   -o, --output <str>            Output file.
#>   -D, --output-delimiter <str>  Output delimiter (overrides -d).
#>   -O, --output-format <format>  Output format [default: auto].
#>       --output-indent <int>     Indentation level (JSON, YAML) [default: 2].
#>       --output-record <str>     Record element name (XML, TOML) [default: record].
#>       --output-root <str>       Root element name (XML) [default: root].
#>       --output-sheet <str>      Excel sheet name to write.
#>       --units <str>             Plot size units [default: in].
#>   -w, --width <num>             Plot width.
#> 
#> General options:
#>   -n, --dry-run                 Only print generated script.
#>   -h, --help                    Show this help.
#>   -I, --no-ir                   Run with Rscript, not ir.
#>   -R, --no-rush                 Inline all code (no rush dep).
#>       --seed <int>              Seed random number generator.
#>   -v, --verbose                 Be verbose.
#>       --version                 Show version.