{"id":13568708,"url":"https://github.com/babashka/process","last_synced_at":"2025-05-14T23:05:11.458Z","repository":{"id":39663523,"uuid":"285854127","full_name":"babashka/process","owner":"babashka","description":"Clojure library for shelling out / spawning sub-processes","archived":false,"fork":false,"pushed_at":"2025-03-31T13:44:54.000Z","size":784,"stargazers_count":227,"open_issues_count":7,"forks_count":32,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-05-12T21:05:46.227Z","etag":null,"topics":["babashka","clojure"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/babashka.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-08-07T14:52:54.000Z","updated_at":"2025-04-07T20:33:17.000Z","dependencies_parsed_at":"2025-03-17T20:30:24.199Z","dependency_job_id":"a558e868-9f79-4e58-a349-117b32a7802b","html_url":"https://github.com/babashka/process","commit_stats":{"total_commits":251,"total_committers":18,"mean_commits":"13.944444444444445","dds":"0.11155378486055778","last_synced_commit":"9c0fb6cd0c407c597e58919d68976be308cd1357"},"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babashka%2Fprocess","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babashka%2Fprocess/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babashka%2Fprocess/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babashka%2Fprocess/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/babashka","download_url":"https://codeload.github.com/babashka/process/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254243358,"owners_count":22038046,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["babashka","clojure"],"created_at":"2024-08-01T14:00:30.679Z","updated_at":"2025-05-14T23:05:06.394Z","avatar_url":"https://github.com/babashka.png","language":"Clojure","funding_links":[],"categories":["Clojure"],"sub_categories":[],"readme":"# process\n\n[![Clojars Project](https://img.shields.io/clojars/v/babashka/process.svg)](https://clojars.org/babashka/process)\n[![bb built-in](https://raw.githubusercontent.com/babashka/babashka/master/logo/built-in-badge.svg)](https://book.babashka.org#badges)\n\nClojure library for shelling out / spawning sub-processes.\n\n\u003e **_NOTE:_**  When using process from babashka, this README assumes v1.0.168 or later.\n\n## API\n\nIn 90% of the use cases you will probably need `shell` and for the remaining use\ncases you will probably need a combination of `process` and `check`. Start\nreading the docs for those, skim over the rest and revisit the remaining functions when you need them.\n\nSee [API docs](API.md) as generated by quickdoc.\n\n## Installation\n\nThis library is included in [babashka](https://github.com/babashka/babashka)\nsince\n[0.2.3](https://github.com/babashka/babashka/blob/master/CHANGELOG.md#v023-2020-10-21)\nbut is also intended as a JVM library.\n\n[![Clojars Project](https://img.shields.io/clojars/v/babashka/process.svg)](https://clojars.org/babashka/process)\n\n## Usage\n\n### Syntax\n\nThe functions `shell`, `process` and `exec` take an optional map followed by one or more strings:\n\n``` clojure\n(require '[babashka.process :refer [shell process exec]])\n\n(shell \"ls\" \"-la\") ;; no options\n(shell \"ls -la\" \"dir\") ;; first string is tokenized automatically, more strings may be provided\n(shell {:dir \"target\"} \"ls\" \"-la\")\n(process {:in \"hello\"} \"cat\")\n(exec {:extra-env {\"FOO\" \"BAR\"}} \"bash\")\n```\n\nPrevious versions of babashka process supported the `(process [\"prog\" \"arg\"]\n{})` syntax. This syntax is no longer recommended, but is still supported to not\nbreak existing programs.\n\n### shell\n\nMost commonly you will use `shell`. It executes a command and streams the output\nto stdout and stderr while the process is running. The name `shell` comes from\n\"shelling out\", but note that it does _not_ invoke a bash/zsh/cmd.exe shell: it\njust starts an external program.\n\n``` clojure\nuser=\u003e (shell \"ls\" \"-la\")\ntotal 144\ndrwxr-xr-x@ 22 borkdude  staff    704 Dec  4 13:39 .\ndrwxr-xr-x@ 75 borkdude  staff   2400 Dec  3 14:18 ..\ndrwxr-xr-x@  4 borkdude  staff    128 Mar 10  2022 .circleci\ndrwxr-xr-x@  5 borkdude  staff    160 Mar 10  2022 .clj-kondo\ndrwxr-xr-x@ 50 borkdude  staff   1600 Dec  3 20:55 .cpcache\n```\n\nThe first string argument to `shell` is tokenized automatically: `\"ls -la\"` is\nbroken up into `\"ls\"` and `\"-la\"`, so `(shell \"ls -la\")` also works. This eases\nthe migration from existing bash scripts.\n\nYou can provide more arguments if you need to:\n\n``` clojure\nuser=\u003e (shell \"ls -la\" \"src\" \"test\")\nsrc:\ntotal 0\ndrwxr-xr-x@  3 borkdude  staff   96 Mar 10  2022 .\ndrwxr-xr-x@ 22 borkdude  staff  704 Dec  4 14:13 ..\ndrwxr-xr-x@  4 borkdude  staff  128 Dec  4 14:01 babashka\n\ntest:\ntotal 0\ndrwxr-xr-x@  3 borkdude  staff   96 Mar 10  2022 .\ndrwxr-xr-x@ 22 borkdude  staff  704 Dec  4 14:13 ..\ndrwxr-xr-x@  3 borkdude  staff   96 Dec  4 14:01 babashka\n```\n\nThis is particularly handy when you want to supply commands coming from the command line:\n\n``` clojure\n(apply shell \"ls -la\" *command-line-args*)\n```\n\nThe `shell` function checks the command's exit code and throws if it is non-zero:\n\n``` clojure\nuser=\u003e (shell \"ls nothing\")\nls: nothing: No such file or directory\nExecution error (ExceptionInfo) at babashka.process/check (process.cljc:113).\n```\n\nTo avoid throwing when the command's exit code is non-zero, use `:continue true`.\nYou will still see the error printed to stderr, but no exception will be thrown. This is convenient\nwhen you want to handle the `:exit` code yourself:\n\n``` clojure\nuser=\u003e (-\u003e (shell {:continue true} \"ls nothing\") :exit)\nls: nothing: No such file or directory\n1\n```\n\n\u003e Note that `:continue true` only suppresses throwing an exception when the executed command's exit code is non-zero.\n\u003e Other exceptions can throw, for example, when the executable is not found.\n\nTo collect output as a `:string`, use the `:out :string` option as the first argument:\n\n``` clojure\nuser=\u003e (-\u003e (shell {:out :string} \"ls -la\") :out str/split-lines first)\n\"total 144\"\n```\n\nTo also capture stderr to a `:string`, add in the `:err :string` option:\n\n``` clojure\nuser=\u003e (-\u003e (shell {:out :string :err :string} \"git conpig user.name\")\n           (select-keys [:out :err]))\n{:out \"borkdude\\n\", :err \"WARNING: You called a Git command named 'conpig', which does not exist.\\nContinuing in -1.1 seconds, assuming that you meant 'config'.\\n\"}\n```\n\nTo redirect stderr to stdout specify the `:err :out` option:\n\n``` clojure\nuser=\u003e (-\u003e (shell {:out :string :err :out} \"git conpig user.name\") :out)\n\"WARNING: You called a Git command named 'conpig', which does not exist.\\nContinuing in -1.1 seconds, assuming that you meant 'config'.\\nborkdude\\n\"\n```\n\nTo change the working directory, use the `:dir` option:\n\n``` clojure\nuser=\u003e (-\u003e (shell {:out :string :dir \"src/babashka\"} \"ls -la\") :out str/split-lines first)\n\"total 48\"\n```\n\nTo add environment variables, use `:extra-env`:\n\n``` clojure\nuser=\u003e (-\u003e (shell {:out :string :extra-env {\"FOO\" \"BAR\"}} \"bb -e '(System/getenv \\\"FOO\\\")'\") :out print)\n\"BAR\"\n```\n\n### process\n\nThe `shell` function is a combination of `process` and `deref` and `check`. The\n`process` function is the lower level function of this library that doesn't make\nany opinionated choices:\n\n- It does not provide a default for `:out`, `:in` and `:err`: in `shell` these\n  default to `:inherit` which means: read and write from and to the console. In\n  `process` they default to the default of `java.lang.ProcessBuilder`.\n- It does not wait until the process completes.\n- It does not check the exit code and throw an exception.\n\nUse `process` when you need to change one of the above and `shell`'s options do\nnot support it. In practice this means: whenever you need async processing,\ne.g. reading output from a process while it is running.\n\nThe return value of `process` implements `clojure.lang.IDeref`. When\ndereferenced, it will wait for the process to finish and will add the `:exit` value.\n\n``` clojure\nuser=\u003e (-\u003e (process \"ls foo\") deref :exit)\n1\n```\n\nThe function `check` takes a process, waits for it to finish (so you can omit\n`deref`) and returns it. When the exit code is non-zero, it will throw.\n\n``` clojure\nuser=\u003e (-\u003e (process {:out :string} \"ls\") check :out str/split-lines first)\n\"API.md\"\nuser=\u003e (-\u003e (process {:out :string} \"ls foo\") check :out str/split-lines first)\nExecution error (ExceptionInfo) at babashka.process/check (process.clj:74).\nls: foo: No such file or directory\n```\n\nBoth `:in`, `:out` may contain objects that are compatible with `clojure.java.io/copy`:\n\n``` clojure\nuser=\u003e (with-out-str (check (process {:in \"foo\" :out *out*} \"cat\")))\n\"foo\"\n\nuser=\u003e (-\u003e\u003e (with-out-str (check (process {:out *out*} \"ls\"))) str/split-lines (take 2))\n(\"API.md\" \"CHANGELOG.md\")\n```\n\nThe `:out` option also supports `:string` for collecting stdout into a string\nand `:bytes` for getting the raw output as a byte array. You will need to\n`deref` the process in order for the string or byte array to be there, since the\noutput can't be finalized if the process hasn't finished running:\n\n``` clojure\nuser=\u003e (-\u003e @(process {:out :string} \"ls\") :out str/split-lines first)\n\"API.md\"\nuser=\u003e (-\u003e @(process {:out :bytes} \"head -c 10 /dev/urandom\") :out seq)\n(119 -43 -68 -64 -16 -56 32 45 86 56)\n```\n\n## Piping output\n\nBoth `shell` and `process` support piping output from one process to the next\nusing but note that `shell` writes the output to the system's stdout by\ndefault, so you have to provide it with `{:out :string}` for the next process to\ncapture the input, while `process` uses the default `java.lang.ProcessBuilder`\nsetting which defaults to writing to a stream:\n\n``` clojure\nuser=\u003e (let [stream (-\u003e (process \"ls\") :out)]\n         @(process {:in stream\n                    :out :inherit} \"cat\")\n         nil)\nAPI.md\nCHANGELOG.md\nLICENSE\nREADME.md\n...\n```\n\nForwarding the output of a process as the input of another process can also be done with thread-first (`-\u003e`):\n\n``` clojure\nuser=\u003e (-\u003e (process \"ls\")\n           (process {:out :string} \"grep README\") deref :out)\n\"README.md\\n\"\n```\n\n## Redirecting output to a file\n\nTo write to a file use `:out :write` and set `:out-file` to a file:\n\n``` clojure\nuser=\u003e (require '[clojure.java.io :as io])\nnil\nuser=\u003e (do @(process {:out :write :out-file (io/file \"/tmp/out.txt\")} \"ls\") nil)\nnil\nuser=\u003e (slurp \"/tmp/out.txt\")\n\"CHANGELOG.md\\nLICENSE\\nREADME.md...\"\n```\n\nor simply:\n\n``` clojure\n(do (shell {:out \"/tmp/out.txt\"} \"ls\") nil)\n```\n\nTo append to a file, use `:out :append`:\n\n``` clojure\nuser=\u003e (do @(process {:out :append :out-file (io/file \"/tmp/out.txt\")} \"ls\") nil)\nnil\nuser=\u003e (slurp \"/tmp/out.txt\")\n\"CHANGELOG.md\\nLICENSE\\nREADME.md...\"\n```\n\n## Feeding input\n\nHere is an example of a `cat` process to which we send input while the process\nis running, then close stdin and read the output of cat afterwards:\n\n``` clojure\n(ns cat-demo\n  (:require [babashka.process :refer [process alive?]]\n            [clojure.java.io :as io]))\n\n(def catp (process \"cat\"))\n\n(alive? catp) ;; true\n\n(def stdin (io/writer (:in catp)))\n\n(binding [*out* stdin]\n  (println \"hello\"))\n\n(.close stdin)\n\n(slurp (:out catp)) ;; \"hello\\n\"\n\n(:exit @catp) ;; 0\n\n(alive? catp) ;; false\n```\n\n## Processing streaming output\n\nHere is an example where we read the output of `bb -o -e '(range)'`, an infinite\nstream of numbers, line by line and print it ourselves:\n\n``` clojure\n(require '[babashka.process :as p :refer [process destroy-tree]]\n         '[clojure.java.io :as io])\n\n(def number-stream\n  (process\n   {:err :inherit\n    :shutdown destroy-tree}\n   \"bb -o -e '(range)'\"))\n\n(with-open [rdr (io/reader (:out number-stream))]\n  (binding [*in* rdr]\n    (loop [max 10]\n      (when-let [line (read-line)]\n        (println :line line)\n        (when (pos? max)\n          (recur (dec max)))))))\n\n;; kill the streaming bb process:\n(p/destroy-tree number-stream)\n```\n\n## Printing command\n\nThe `:pre-start-fn` option can be used to report commands being run:\n\n``` clojure\n(require '[babashka.process :refer [process]])\n\n(doseq [file [\"LICENSE\" \"CHANGELOG.md\"]]\n  (-\u003e (process\n        {:out :string\n         :pre-start-fn #(apply println \"Running\" (:cmd %))}\n        \"head\" \"-1\" file)\n      deref :out println))\n\nRunning head -1 LICENSE\nEclipse Public License - v 1.0\n\nRunning head -1 CHANGELOG.md\n# Changelog\n```\n\n## sh\n\n`sh` is a convenience function around `process` which sets `:out` and `:err` to\n`:string` and blocks automatically, similar to `clojure.java.shell/sh`:\n\n``` clojure\nuser=\u003e (def config {:output {:format :edn}})\n#'user/config\nuser=\u003e (-\u003e (sh [\"clj-kondo\" \"--lint\" \"src\"]) :out slurp edn/read-string)\n{:findings [], :summary {:error 0, :warning 0, :info 0, :type :summary, :duration 34}}\n```\n\n## Tokenization\n\nAll of `shell`, `process` and `sh` support tokenization on the first string argument using `tokenize`:\n\n``` clojure\nuser=\u003e (require '[babashka.process :refer [sh tokenize]])\nnil\nuser=\u003e (tokenize \"hello there\")\n[\"hello\" \"there\"]\nuser=\u003e (-\u003e (sh \"echo hello there\") :out)\n\"hello there\\n\"\n```\n\n``` clojure\nuser=\u003e (-\u003e (sh {:in \"(inc)\"} \"clj-kondo --lint -\") :out)\n\"\u003cstdin\u003e:1:1: error: clojure.core/inc is called with 0 args but expects 1\\nlinting took 10ms, errors: 1, warnings: 0\\n\"\n```\n\n## Output buffering\n\nNote that `check` will wait for the process to end in order to check the exit\ncode. When the process has lots of data to write to stdout, it is recommended to\nadd an explicit `:out` option to prevent deadlock due to buffering. This example\nwill deadlock because the process is buffering the output stream but it's not\nbeing consumed, so the process won't be able to finish:\n\n``` clojure\nuser=\u003e (-\u003e (process {:in (apply str (repeat 1000000 \"hello\\n\"))} \"cat\") check :out count)\n```\n\nThe way to deal with this is providing an explicit `:out` option so the process\ncan finish writing its output:\n\n``` clojure\nuser=\u003e (-\u003e (process {:out :string :in (apply str (repeat 1000000 \"hello\\n\"))} \"cat\") check :out count)\n6000000\n```\n\n## Add Environment\n\nThe `:env` option replaces your entire environment with the provided map. To add environment variables you can use `:extra-env` instead:\n\n```clojure\n:extra-env {\"FOO\" \"BAR\"}\n```\n\n\u003e **Windows TIP**: Unlike in the CMD and Powershell shells, environment variable names are case sensitive for `:extra-env`.\nFor example, `\"PATH\"` will not update the value of `\"Path\"` on Windows.\nHere's an [example of a babashka task](https://github.com/babashka/fs/blob/3b8010d1a0db166771ac7f47573ea09ed45abe33/bb.edn#L10-L11) that understands this nuance.\n\n\u003e **:env TIP**: An OS might have default environment variables it always includes.\nFor example, as of this writing, Windows always includes `SystemRoot` and macOS always includes `__CF_USER_TEXT_ENCODING`.\n\n## Pipelines\n\nThe `pipeline` function returns a\n[`sequential`](https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/sequential?)\nof processes from a process that was created with `-\u003e` or by passing multiple\nobjects created with `pb`:\n\n``` clojure\nuser=\u003e (require '[babashka.process :refer [pipeline pb process check]])\nnil\nuser=\u003e (mapv :cmd (pipeline (-\u003e (process \"ls\") (process \"cat\"))))\n[[\"ls\"] [\"cat\"]]\nuser=\u003e (mapv :cmd (pipeline (pb \"ls\") (pb \"cat\")))\n[[\"ls\"] [\"cat\"]]\n```\n\nTo obtain the right-most process from the pipeline, use `last` (or `peek`):\n\n``` clojure\nuser=\u003e (-\u003e (pipeline (pb \"ls\") (pb \"cat\")) last :out slurp)\n\"LICENSE\\nREADME.md\\ndeps.edn\\nsrc\\ntest\\n...\"\n```\n\nCalling `pipeline` on the right-most process returns the pipeline:\n\n``` clojure\nuser=\u003e (def p (pipeline (pb \"ls\") (pb \"cat\")))\n#'user/p\nuser=\u003e (= p (pipeline (last p)))\ntrue\n```\n\nTo check an entire pipeline for non-zero exit codes, you can use:\n\n``` clojure\nuser=\u003e (run! check (pipeline (pb \"ls foo\") (pb \"cat\")))\nExecution error (ExceptionInfo) at babashka.process/check (process.clj:37).\nls: foo: No such file or directory\n```\n\nAlthough you can create pipelines with `-\u003e`, for some applications it may be\npreferable to create a pipeline with `pipeline` which defers to\n`ProcessBuilder/startPipeline`. In the following case it takes a long time\nbefore you would see any output due to buffering.\n\n``` clojure\n(future\n  (loop []\n    (spit \"log.txt\" (str (rand-int 10) \"\\n\") :append true)\n    (Thread/sleep 10)\n    (recur)))\n\n(-\u003e (process \"tail\" \"-f\" \"log.txt\")\n    (process \"cat\")\n    (process {:out :inherit} \"grep \"5\"))\n```\n\nThe solution then it to use `pipeline` + `pb`:\n\n``` clojure\n(pipeline (pb \"tail\" \"-f\" \"log.txt\")\n          (pb \"cat\")\n          (pb {:out :inherit} \"grep\" \"5\"))\n```\n\nThe varargs arity of `pipeline` is only available in JDK9 or higher due to the\navailability of `ProcessBuilder/startPipeline`. If you are on JDK8 or lower, the\nfollowing solution that reads the output of `tail` line by line may work for\nyou:\n\n``` clojure\nuser=\u003e (require '[clojure.java.io :as io])\nnil\n\n(def tail (process {:err :inherit} \"tail\" \"-f\" \"log.txt\"))\n\n(def cat-and-grep\n  (-\u003e (process {:err :inherit} \"cat\")\n      (process {:out :inherit\n                :err :inherit} \"grep 5\")))\n\n(binding [*in*  (io/reader (:out tail))\n          *out* (io/writer (:in cat-and-grep))]\n  (loop []\n    (when-let [x (read-line)]\n      (println x)\n      (recur))))\n```\n\nAnother solution is to let bash handle the pipes by shelling out with `bash -c`.\n\n## Program Resolution\n\n### macOS \u0026 Linux\nOn macOS \u0026 Linux, programs are resolved the way you expect:\n\n- `a` resolves against the system `PATH`\n- `./a` resolves against `:dir` if specified, otherwise the current working directory\n- `/some/absolute/a` resolves absolutely\n\nIn all cases, the working directory for `a` is `:dir`, if specified, otherwise your current working directory.\n\n### Windows\n\nWindows executable files have extensions, which, if not specified, are resolved in order: `.com`,`.exe`,`.bat`,`.cmd`.\nPrograms are resolved in directories using the same rules as macOS, Linux, and Windows PowerShell.\n\n\u003e **Windows .ps1 TIP**: Babashka process will never resolve to, and cannot launch, `.ps1` scripts directly.\nTo launch a `.ps1` script, you must do so through PowerShell.\nExample:\n\u003e ```Clojure\n\u003e (p/shell \"powershell.exe -File .\\\\a.ps1\")\n\u003e ```\n\n\u003e **Windows TIP**: If you prefer a more CMD Shell-like experience where programs are resolved first in the current working directory, then on the `PATH`, and are OK with the security implications of doing so, you can override the default `:program-resolver` with your own.\n\n## Differences with `clojure.java.shell/sh`\n\nIf `clojure.java.shell` works for your purposes, keep using it. But there are\ncontexts in which you need more flexibility. The major differences compared with\nthis library:\n\n- `sh` is blocking, `process` makes blocking explicit via `deref`\n- `sh` focuses on convenience but limits what you can do with the underlying\n  process, `process` exposes as much as possible while still offering an ergonomic\n  API\n- `process` supports piping processes via `-\u003e` or `pipeline`\n- `sh` offers integration with `clojure.java.io/copy` for `:in`, `process` extends\n  this to `:out` and `:err`\n\n## Differences with `clojure.java.process`\n\nClojure 1.12.0 features a new `clojure.java.process`\nnamespace. `babashka.process` predates it, but the API is very similar, although defaults will differ.\n\nNote that the `exec` function in `babashka.process` does something very\ndifferent than the same-named function in `clojure.java.process`: in\n`babashka.process` it replaces the parent process via a Unix `exec` call, while\nin `clojure.java.process/exec` the process is launched as a child process.\n\nOther notable differences:\n\n- `clojure.java.process` does not do any tokenization, so it doesn't support passing a string like `\"ls -la\"`\n- `clojure.java.process` does not have any Windows-specific support.\n\n### Script termination\n\nBecause `process` spawns threads for non-blocking I/O, you might have to run\n`(shutdown-agents)` at the end of your Clojure JVM scripts to force\ntermination. Babashka does this automatically.\n\n## Clojure.pprint\n\nWhen pretty-printing a process, by default you will get an exception:\n\n``` clojure\nuser=\u003e (require '[babashka.process :refer [process]])\nnil\nuser=\u003e (require '[clojure.pprint :as pprint])\nnil\nuser=\u003e (pprint/pprint (process \"ls\"))\nExecution error (IllegalArgumentException) at user/eval257 (REPL:1).\nMultiple methods in multimethod 'simple-dispatch' match dispatch value: class babashka.process.Process -\u003e interface clojure.lang.IDeref and interface clojure.lang.IPersistentMap, and neither is preferred\n```\n\nThe reason is that a process is both a record and a `clojure.lang.IDeref` and\npprint does not have a preference for how to print this. The recommended solution is to require the `babashka.process.pprint` namespace, which will define a `pprint` implementation for a `Process` record:\n```clojure\nuser=\u003e (require '[babashka.process.pprint])\nnil\n\nuser=\u003e (pprint/pprint (process \"ls\"))\n{:proc\n #object[java.lang.ProcessImpl 0x1d61a348 \"Process[pid=43771, exitValue=\\\"not exited\\\"]\"],\n :exit nil,\n...\n```\n\n### Promesa\n\nOn the JVM (not in bb), you can combine this library with [promesa](https://github.com/funcool/promesa)\nin the following way. This requires `:exit-fn` which was released in version\n`0.2.10`.\n\n``` clojure\n(require '[babashka.process :as proc]\n         '[promesa.core :as prom])\n\n(defn process\n  \"Returns promise that will be resolved upon process termination. The promise is rejected when the exit code is non-zero.\"\n  [opts \u0026 cmd]\n  (prom/create\n   (fn [resolve reject]\n     (let [exit-fn (fn [response]\n                     (let [{:keys [exit] :as r} response]\n                       (if (zero? exit)\n                         (resolve r)\n                         (reject r))))]\n       (apply proc/process (assoc opts :exit-fn exit-fn) cmd)))))\n\n(prom/let [ls (process\n               {:out :string\n                :err :inherit}\n               \"ls\")\n           ls-out (:out ls)]\n  (prn ls-out))\n```\n\n## License\n\nCopyright © 2020-2025 Michiel Borkent\n\nDistributed under the EPL License. See LICENSE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbabashka%2Fprocess","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbabashka%2Fprocess","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbabashka%2Fprocess/lists"}