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.

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). Commented Apr 27, 2018 at 23:39
  • ...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. Commented Apr 27, 2018 at 23:40

3 Answers 3

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.

Sign up to request clarification or add additional context in comments.

5 Comments

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)))))) `
@orangeorangepeel 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.
That's exactly what I ended up doing after getting it work correctly- thanks again! :)
(if-not (empty? args)...) could be shortened to (if (seq args)...)
@Sonicsmooth 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.
7

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

(doseq [arg *command-line-args*]
  (println (str "Read an argument: " arg)))

Comments

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.