Last active
February 22, 2025 10:41
-
-
Save ericnormand/6bb4562c4bc578ef223182e3bb1e72c5 to your computer and use it in GitHub Desktop.
Boilerplate for running Clojure as a shebang script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
#_( | |
#_DEPS is same format as deps.edn. Multiline is okay. | |
DEPS=' | |
{:deps {clj-time {:mvn/version "0.14.2"}}} | |
' | |
#_You can put other options here | |
OPTS=' | |
-J-Xms256m -J-Xmx256m -J-client | |
' | |
exec clojure $OPTS -Sdeps "$DEPS" "$0" "$@" | |
) | |
(println "Hello!") | |
(require '[clj-time.core :as t]) | |
(prn (str (t/now))) | |
(prn *command-line-args*) | |
(println (.. (Runtime/getRuntime) | |
totalMemory)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ cp script.clj ~/bin/cljtest2 | |
# ~/bin is on my $PATH | |
$ chmod +x ~/bin/cljtest2 | |
$ time cljtest2 "Yo" "Hey" 1 3 4 - -ff | |
Hello! | |
"2019-03-01T17:22:43.564Z" | |
("Yo" "Hey" "1" "3" "4" "-" "-ff") | |
268435456 | |
real 0m2.073s | |
user 0m6.073s | |
sys 0m0.297s | |
$ |
I just figured out a way to make it even more portable by adding a step that install a specific version of Clojure local to the script.
#!/bin/sh
#_(
#_DEPS is same format as deps.edn. Multiline is okay.
DEPS='
{:deps {
clj-http/clj-http {:mvn/version "3.12.3"}
cheshire/cheshire {:mvn/version "5.11.0"}
}}
'
#_You can put other options here
OPTS='
-J-Xms4m -J-Xmx256m
'
if [[ ! -x .local/bin/clojure ]]; then
[[ ! -d .local ]] && mkdir .local
pushd .local
curl -O https://download.clojure.org/install/posix-install-1.11.1.1273.sh
chmod +x posix-install-1.11.1.1273.sh
./posix-install-1.11.1.1273.sh -p $PWD
popd
fi
exec .local/bin/clojure $OPTS -Sdeps "$DEPS" "$0" "$@"
)
(require
'[clj-http.client :as client]
'[clojure.pprint :as pp])
(defn -main [& args]
(pp/pprint (:body (client/get "https://www.example.com" {}))))
(apply -main *command-line-args*)
So with this small block added, your only dependency is java.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Just in case somebody is looking at this and is okay with having separate
deps.edn
(I do, mainly forcider-jack-in-clj
), then this could be simplified to a single string:This should be a first line of a file. From Clojure's point of view, it's a string
":"
and then a comment (since it starts with;
), so a no-op practically.From shell's point of view, if there is no shebang and no pound sign as a first character of a file, then this file is going to be run with
/bin/sh
.:
is an empty command (try it out in your shell), then;
is a command separator and then next command isexec
effectively handing control over toclojure
.All this stuff here is to pass
-m namespace
toclojure
, so that it will call function-main
in that script. This means you can safely eval this in REPL how many times you want without re-executing initialization code. This is done by$(basename $0 .clj)
, of course.Babashka sets "babashka.file" system property to a file being run, which simplifies all that machinery.
clojure
does nothing similar, so this is the way I came up with. Would love to find out if there is a way like Python'sif __name__ == "__main__"
.