smithery.ai

babashka.process

Clojure library for spawning sub-processes and shell operations

Installation

$ npx skills add https://smithery.ai

Also in this package

Other skills from smithery.ai · top by installs.

npx skills add https://smithery.ai

Browse all from smithery.ai

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 9,316 B
  • docs SUMMARY.md 86 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

babashka.process

A Clojure library for shelling out and spawning sub-processes. Wraps java.lang.ProcessBuilder with an ergonomic API supporting pipelines, streaming I/O, and process control.

Overview

babashka.process provides two main entry points:

  • shell - High-level convenience function with sensible defaults
  • process - Low-level function for fine-grained control

Included in Babashka since v0.2.3. Also usable as a JVM library.

Repository: https://github.com/babashka/process

Installation

;; deps.edn
{:deps {babashka/process {:mvn/version "0.6.25"}}}

Built into Babashka - no installation needed for bb scripts.

Core Concepts

shell vs process

Aspect shell process
Blocking Yes No (returns immediately)
Exit check Throws on non-zero No checking
I/O default :inherit (console) Streams
Tokenization Auto-tokenizes first arg Manual

Use shell for simple commands. Use process for pipelines, streaming, or async operations.

Process Records

Both functions return a record containing:

  • :proc - java.lang.Process instance
  • :in - Input stream (stdin)
  • :out - Output stream (stdout)
  • :err - Error stream (stderr)
  • :cmd - Command vector
  • :prev - Previous process (pipelines)

Dereferencing (@ or deref) waits for completion and adds :exit.

API Reference

shell

High-level function for running external programs.

(require '[babashka.process :refer [shell]])

;; Basic usage - tokenizes automatically
(shell "ls -la")

;; Multiple arguments
(shell "git" "commit" "-m" "message")

;; With options
(shell {:dir "src"} "ls")

;; Capture output
(-> (shell {:out :string} "echo hello") :out)
;; => "hello\n"

;; Continue on error (don't throw)
(shell {:continue true} "ls nonexistent")

Options:

  • :continue - Don't throw on non-zero exit
  • All process options supported

process

Low-level function with no opinionated defaults.

(require '[babashka.process :refer [process]])

;; Returns immediately
(def p (process "sleep" "5"))

;; Deref to wait and get exit code
(:exit @p)

;; Capture output
(->> (process {:out :string} "ls") deref :out)

check

Wait for process and throw on non-zero exit.

(require '[babashka.process :refer [process check]])

;; Throws if ls fails
(->> (process {:out :string} "ls") check :out)

;; Chain with process
(-> (process "make") check)

sh

Convenience wrapper defaulting :out and :err to :string.

(require '[babashka.process :refer [sh]])

(sh "ls" "-la")
;; => {:exit 0 :out "..." :err ""}

$

Macro for shell-like syntax with interpolation.

(require '[babashka.process :refer [$]])

(def file "README.md")
($ ls -la ~file)

;; With options via metadata
(^{:out :string} $ echo hello)

tokenize

Split string into argument vector.

(require '[babashka.process :refer [tokenize]])

(tokenize "ls -la")
;; => ["ls" "-la"]

(tokenize "echo 'hello world'")
;; => ["echo" "hello world"]

alive?

Check if process is running.

(require '[babashka.process :refer [process alive?]])

(def p (process "sleep" "10"))
(alive? p) ;; => true

destroy / destroy-tree

Terminate process. destroy-tree also kills descendants (JDK9+).

(require '[babashka.process :refer [process destroy destroy-tree]])

(def p (process "sleep" "100"))
(destroy p)

;; Kill process and all children
(destroy-tree p)

exec

Replace current process image (GraalVM/Babashka only).

(require '[babashka.process :refer [exec]])

;; Replaces bb process with ls
(exec "ls" "-la")

pb / pipeline

Create process builders for pipelines.

(require '[babashka.process :refer [pb pipeline]])

;; JDK9+ pipeline
(-> (pipeline (pb "cat" "file.txt")
              (pb "grep" "pattern")
              (pb "wc" "-l"))
    last
    deref
    :out
    slurp)

Options Reference

I/O Options

Option Values Description
:in stream, string, :inherit Stdin source
:out :string, :bytes, :inherit, :write, :append, file Stdout destination
:err Same as :out, plus :out to merge Stderr destination
:in-enc charset Input encoding
:out-enc charset Output encoding
:err-enc charset Error encoding

Process Options

Option Description
:dir Working directory
:env Replace environment (map)
:extra-env Add to environment (map)
:inherit If true, inherit all streams
:cmd Command vector (overrides args)
:prev Previous process for piping

Hooks

Option Description
:pre-start-fn Called before start with process info
:shutdown Called when child process ends
:exit-fn Called on exit (JDK11+)

Common Patterns

Capture Output

;; As string
(-> (shell {:out :string} "date") :out str/trim)

;; As bytes
(-> (shell {:out :bytes} "cat" "image.png") :out)

;; Merge stderr into stdout
(shell {:err :out :out :string} "cmd")

Working Directory

(shell {:dir "/tmp"} "ls")

Environment Variables

;; Add to environment
(shell {:extra-env {"DEBUG" "1"}} "./script.sh")

;; Replace environment
(shell {:env {"PATH" "/usr/bin"}} "ls")

Piping Processes

;; Using threading
(->> (process "cat" "file.txt")
     (process {:out :string} "grep" "pattern")
     deref
     :out)

;; Using pipeline (JDK9+)
(-> (pipeline (pb "ls") (pb "grep" "clj"))
    last deref :out slurp)

Input to Process

;; String input
(-> (process {:in "hello\nworld" :out :string} "cat")
    deref :out)

;; File input
(-> (process {:in (io/file "data.txt") :out :string} "wc")
    deref :out)

Close Stdin

For processes that read stdin until EOF, close it immediately:

;; Empty string - simplest approach
(process {:in ""} "cmd")

;; Null device
(process {:in null-file} "cmd")

;; Explicit close after start
(let [p (process "cmd")]
  (.close (:in p))
  p)

Streaming Output

(require '[clojure.java.io :as io])

(def p (process {:err :inherit} "bb" "-e" "(doseq [i (range)] (println i) (Thread/sleep 100))"))

(with-open [rdr (io/reader (:out p))]
  (doseq [line (line-seq rdr)]
    (println "Got:" line)))

Interactive Process

(def p (process "cat"))
(def w (io/writer (:in p)))

(binding [*out* w]
  (println "hello")
  (println "world"))
(.close w)

(slurp (:out p))
;; => "hello\nworld\n"

Write to File

;; Overwrite
(shell {:out :write :out-file "log.txt"} "ls")

;; Append
(shell {:out :append :out-file "log.txt"} "date")

Discard Output

(require '[babashka.process :refer [shell null-file]])

(shell {:out null-file :err null-file} "noisy-command")

Timeout

(let [p (process "sleep" "100")]
  (when-not (deref p 1000 nil)
    (destroy-tree p)
    (println "Timed out")))

Pre-start Hook

(shell {:pre-start-fn (fn [{:keys [cmd]}]
                        (println "Running:" cmd))}
       "ls")

Error Handling

Check Exit Code

;; shell throws by default
(try
  (shell "ls" "nonexistent")
  (catch Exception e
    (println "Failed:" (ex-message e))))

;; Suppress with :continue
(let [{:keys [exit]} (shell {:continue true} "ls" "nonexistent")]
  (when-not (zero? exit)
    (println "Command failed")))

Process Errors

;; Manual checking with process
(let [{:keys [exit out err]} @(process {:out :string :err :string} "cmd")]
  (if (zero? exit)
    (println "Success:" out)
    (println "Error:" err)))

Capture Stderr

(let [{:keys [err]} (shell {:err :string :continue true} "ls" "nonexistent")]
  (println "Error output:" err))

Performance Tips

  1. Avoid shell for simple operations - Use Clojure/Java directly when possible
  2. Stream large outputs - Don't use :string for large data; stream instead
  3. Reuse process builders - For repeated commands, create pb once
  4. Use destroy-tree - Prevent zombie processes when killing

Platform Notes

Windows

  • Environment variable names are case-sensitive for :extra-env
  • Cannot launch .ps1 scripts directly; invoke through PowerShell:

``clojure (shell "powershell" "-File" "script.ps1") ``

  • Globbing doesn't work; expand patterns in Clojure

macOS/Linux

  • First argument not tokenized if it contains spaces without quotes
  • Use tokenize explicitly when needed

Comparison with clojure.java.shell

Feature clojure.java.shell/sh babashka.process
Blocking Always Explicit via deref
Piping No Yes
Streaming No Yes
Process control Limited Full access
Exit checking Manual check / shell

See Also

  • [babashka.fs](../babashka.fs/) - File system operations
  • [babashka-cli](../babashka-cli/) - CLI argument parsing