Run command line tools in a reproducible and isolated way, right from R.
system() calls that work on your machine often break on others. condathis creates self-contained environments for your command line tools, so your R code runs reliably for you, your colleagues, and your future self.
A Quick Example
Two commands install cowpy in its own isolated environment and run it:
# 1. Install 'cowpy' into an environment named 'cowpy-env'
condathis::create_env("cowpy", env_name = "cowpy-env")
#> v Environment cowpy-env successfully created.
# 2. Run it!
# Example showing redirection from a file to the Standard Input (STDIN)
temp_file <- tempfile()
writeLines("{condathis} is awesome!", temp_file)
condathis::run("cowpy", stdin = temp_file, env_name = "cowpy-env")
#> _________________________
#> < {condathis} is awesome! >
#> -------------------------
#> \ ^__^
#> \ (oo)\_______
#> (__)\ )\/\
#> ||----w |
#> || ||For something fancier, use rich-cli to format messages:
condathis::create_env("rich-cli", env_name = "rich-cli-env")
#> v Environment rich-cli-env successfully created.
condathis::run(
"rich", "[b]Condathis[/b] is awesome!", "-p", "-a", "ascii",
env_name = "rich-cli-env"
)
#> +-----------------------+
#> | Condathis is awesome! |
#> +-----------------------+You can package any command line tool with your R script, and it runs on any machine where its packages are available.
Installation
Install the release version of the package from CRAN:
install.packages("condathis")Or get the development version from R-Universe:
install.packages("condathis", repos = c("https://luciorq.r-universe.dev", getOption("repos")))Why condathis?
R’s system() and system2() depend on tools being installed on the host system. This creates a few problems:
- Reproducibility: Will your script from last year still work? Will your collaborator be able to run your code if they have a different version of a tool?
- Conflicts: What if two different tools need two different versions of the same dependency?
condathis solves these issues by creating isolated environments for each tool.
Reproducibility: An Example
Let’s say you’re using fastqc for quality control in a bioinformatics pipeline. Different versions of fastqc can produce slightly different results.
With condathis, you can lock in a specific version:
fastq_file <- system.file("extdata", "sample1_L001_R1_001.fastq.gz", package = "condathis")
temp_out_dir <- file.path(tempdir(), "output")
fs::dir_create(temp_out_dir)
# Always use fastqc version 0.12.1
condathis::create_env(
"fastqc==0.12.1",
env_name = "fastqc-0.12.1",
channels = c("conda-forge", "bioconda")
)
#> v Environment fastqc-0.12.1 successfully created.
condathis::run("fastqc", fastq_file, "-o", temp_out_dir, env_name = "fastqc-0.12.1")
#> application/gzip
#> Started analysis of sample1_L001_R1_001.fastq.gz
#> Approx 90% complete for sample1_L001_R1_001.fastq.gz
#> Analysis complete for sample1_L001_R1_001.fastq.gzNow your analysis uses the same fastqc version, regardless of where or when it runs.
Isolation: An Example
Need a specific version of a tool like curl that is different from the one your system or R provides?
The libcurl that R uses:
as.character(libcurlVersion())
#> [1] "8.23.0"A specific curl version, isolated with condathis:
condathis::create_env(
"curl==8.10.1",
env_name = "curl-env"
)
#> v Environment curl-env successfully created.
# Capture the output as an R object, then read its first line
out <- condathis::run(
"curl", "--version",
env_name = "curl-env",
verbose = "silent"
)
condathis::parse_output(out)[1]
#> [1] "curl 8.10.1 (x86_64-conda-linux-gnu) libcurl/8.10.1 OpenSSL/3.6.5 zlib/1.3.2 zstd/1.5.7 libssh2/1.11.1 nghttp2/1.70.0"This lets you run tools with conflicting dependencies side by side.
How It Works
condathis relies on micromamba to bring reproducibility and isolation. micromamba is a package manager that “does not need a base environment and does not come with a default version of Python”. condathis downloads micromamba when it first needs it and finds no usable copy. It keeps micromamba and the environments in its own data directory (see condathis::get_install_dir()). Environments stay there until you remove them. condathis::list_envs() lists them, condathis::get_env_dir("cowpy-env") returns the directory of one, condathis::remove_env("cowpy-env") removes it, and condathis::clean_cache() deletes the downloaded package files.
micromamba is the built-in backend of condathis. Other packages can add backends with register_backend(). New environments use micromamba unless you pass method (for example method = "rattler", which needs the rattlerthis package, not yet on CRAN) or set options(condathis.backend_priority = ...). The backends article shows how to install rattlerthis and use both backends together.
Already have an environment created outside condathis, for example by conda, mamba, or micromamba itself? Pass its directory as env_name = condathis::env_path("/path/to/env") to run(), run_bin(), run_pipeline() or run_step() to run commands from it. condathis never modifies such an environment. It runs commands in it with micromamba by default.
Known Caveats
No shell is involved, so shell syntax in an argument is passed literally.
- Shell builtins, such as
echoordiron Windows, are not available as commands. To use shell features, run a shell explicitly, for examplecondathis::run("bash", "-c", "..."). - Shell operators such as
|and>are not interpreted.- Instead of
>, use thestdout/stderrarguments (a file path redirects to that file). - Instead of
cmd1 | cmd2, usecondathis::run_pipeline()with onecondathis::run_step()per command, as inrun_pipeline(run_step("cmd1"), run_step("cmd2", env_name = "other-env")). It connects the commands (optionally each in its own Conda environment) via kernel-level pipes. Alternatively, callcondathis::run()several times and use thestdout/stdinfile arguments to pass data between the calls. -
stdinaccepts a file path. Set it to"|"together with theinputargument to write in-memory data (character or raw vector) directly to a command’s standard input. This works withrun(),run_bin(), andrun_pipeline().
- Instead of
- A
~in a command argument is not expanded.- Expand such paths in R, for example with
path.expand()orfs::path_expand(). - Relative paths, such as
"data/sample.fastq"or"../sample.fastq", work: they are relative to the working directory of R.
- Expand such paths in R, for example with
Learn More
-
vignette("condathis")walks through a complete example. - The articles on the package website show how to use Python with
reticulate, how to stream binary data through pipelines, and how to choose a backend.