How to use command line arguments in Clojure?
Asked Answered
P

3

8

I'm working my way through one of my first Clojure programs. The one thing I have left to do is have my program accept command line arguments. The number of args can vary (but needs to be at least one), and then each command line arg needs to be provided as an argument to a function in my main, one at a time. I've been reading online and it seems that clojure/tools.cli is the way to do it, (using parse-opts maybe?). But I can't figure it out for the life of me. There's no validation, really that needs to happen -- whatever the user would provide would be valid. (The only thing that needs to be checked is that there is at least one argument provided). Any suggestions on how to go about this?

All the examples I run into seem very complicated and go over my head very easily.

An easy example of what I'm trying to do is after a user provides any number of command line arguments, then have clojure print each string in a new line of the terminal.

I use leiningen to run my programs.

Phlogopite answered 27/4, 2018 at 23:31 Comment(2)
Can you show your attempts to use clojure.tools.cli/parse-opts -- ie. what code you have, what behavior you want, what behavior you're actually getting instead? (See the minimal reproducible example page in the Help Center for guidance on putting together the shortest possible code sample that lets someone else see your problem for themselves).Astigmatic
...actually, if everything is valid, you arguably don't need parse-opts, and can just implement a main function that operates on its argument list.Astigmatic
M
8

Since your entire question seems to boil down to:

An easy example of what I'm trying to do is after a user provides any number of command line arguments, then have clojure print each string in a new line of the terminal.

I'll answer that. That can be done fairly succinctly:

(defn -main [& args] ; & creates a list of var-args
  (if (seq args)
    ; Foreach arg, print the arg...
    (doseq [arg args]
      (println arg))

    ; Handle failure however here
    (throw (Exception. "Must have at least one argument!"))))

Note, unless you absolutely want to fail outright when 0 arguments are passed, you can skip the seq check. doseq won't run if args is empty since there isn't anything to iterate over.

You can also replace the whole doseq with:

(mapv println args)

But that's arguably an abuse of mapv.

Mahoney answered 27/4, 2018 at 23:48 Comment(5)
awesome, thanks so much! from your example, i've been able to write this up: ` (doseq [arg args] (if-not (empty? (find-files arg paths)) (println (apply str (find-files arg paths)))))) `Phlogopite
@Phlogopite You probably don't want to call (find-files arg paths) twice. That sounds expensive. You should call it once, save the result using a let binding, and use that instead.Mahoney
That's exactly what I ended up doing after getting it work correctly- thanks again! :)Phlogopite
(if-not (empty? args)...) could be shortened to (if (seq args)...)Niagara
@Niagara Yes, I only recently adopted the seq idiom. I always felt awkward for me until I started using nil properly. I'll try to remember to update this when I get home.Mahoney
A
7

The easy way is to use clojure.core/*command-line-args*:

(doseq [arg *command-line-args*]
  (println (str "Read an argument: " arg)))
Astigmatic answered 27/4, 2018 at 23:44 Comment(0)
R
1

Clojure Spec can do many things, and parsing and validating command line arguments is one of those things. Here is an example:

(ns cmdargs.core
  (:require [clojure.spec.alpha :as spec]))

;; Specification of a single argument.
;; For example, replace `any?` by `number?`
;; if your program only accepts numeric args.
(spec/def ::arg any?)

(spec/def ::args (spec/+ ::arg))

(defn my-fun [& args]
  (doseq [arg args]
    (println "Got arg: " arg)))

(defn -main [& args]
  (let [parsed-args (spec/conform ::args args)]
    (if (= ::spec/invalid parsed-args)
      (do (println "Bad commandline arguments")
          (spec/explain ::args args))
      (apply my-fun parsed-args))))

Clojure Spec ships from version 1.9 of Clojure.

Rein answered 28/4, 2018 at 6:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.