乐趣区

关于commonlisp:如何在CommonLisp中解析命令行参数

clingon

clingon 是一个 Common Lisp 的命令行选项的解析器,它能够轻松地解析具备简单格局的命令行选项。例如,上面的代码能够打印给定次数的打招呼信息

#!/bin/sh
#|-*- mode:lisp -*-|#
#|
exec ros -Q -- $0 "$@"
|#
(progn ;;init forms
  (ros:ensure-asdf)
  #+quicklisp(ql:quickload '(clingon) :silent t)
  )

(defpackage :ros.script.hello.3868869124
  (:use :cl
        :clingon))
(in-package :ros.script.hello.3868869124)

(defun top-level/handler (cmd)
  (check-type cmd clingon:command)
  (let ((count (clingon:getopt cmd :count))
        (name (first (clingon:command-arguments cmd))))
    (dotimes (_ count)
      (declare (ignorable _))
      (format t "Hello ~A!~%" name))))

(defun main (&rest argv)
  (let ((app (clingon:make-command
              :handler #'top-level/handler
              :name "hello"
              :options (list
                        (clingon:make-option
                         :integer
                         :description "number of greetings"
                         :initial-value 1
                         :key :count
                         :long-name "count")))))
    (clingon:run app argv)))
;;; vim: set ft=lisp lisp:

略微做一些解释。首先执行命令 ros init hello 生成下面的代码的雏形——加载依赖、包定义,以及空的函数 main。为了加载 clingon,将其作为函数ql:quickload 的参数。而后别离定义一个commandhandler,以及option

在 clingon 中,类 clingon:command 的实例对象示意一个能够在 shell 中被触发的命令,它们由函数 clingon:make-command 创立。每一个命令起码要有三个因素:

  1. :handler,负责应用命令行选项、实现业务逻辑的函数;
  2. :name,命令的名字,个别会被展现在命令的用法说明中;
  3. :options,该命令所承受的选项。

此处的 :handler 就是函数 top-level/handler,它会被函数clingon:run 调用(依赖注入的滋味),并将一个适合的 clingon:command 对象传入。:options目前只承载了一个选项的定义,即

                        (clingon:make-option
                         :integer
                         :description "number of greetings"
                         :initial-value 1
                         :key :count
                         :long-name "count")

它定义了一个值为整数的选项,在命令行中通过 --count 指定。如果没有传入该选项,那么在应用函数 clingon:getopt 取值时,会取得默认值 1。如果要从一个命令对象中取出这个选项的值,须要以它的 :key 参数的值作为参数来调用函数 clingon:getopt,正如下面的函数top-level/handler 所示。

子命令

clingon 也能够实现诸如 git addgit branch 这样的子命令个性。像 addbranch 这样的子命令,对于 clingon 而言依然是类 clingon:command 的实例对象,只不过它们不会传递给函数 clingon:run 调度,而是传递给函数 clingon:make-command 的参数:sub-command,如下列代码所示

(defun top-level/handler (cmd)
  (declare (ignorable cmd)))

(defun main (&rest argv)
  (let ((app (clingon:make-command
              :handler #'top-level/handler
              :name "cli"
              :sub-commands (list
                             (clingon:make-command
                              :handler #'(lambda (cmd)
                                           (declare (ignorable cmd))
                                           (format t "Dropped the database~%"))
                              :name "dropdb")
                             (clingon:make-command
                              :handler #'(lambda (cmd)
                                           (declare (ignorable cmd))
                                           (format t "Initialized the database~%"))
                              :name "initdb")))))
    (clingon:run app argv)))

选项与参数

在 clingon 中通过命令行传递给过程的信息分为选项和参数两种状态,选项是通过名字来援用,而参数则通过它们的下标来援用。例如在第一个例子中,就定义了一个名为 --count 的选项,它在解析后果中被赋予了 :count 这个关键字,能够通过函数 clingon:getopt 来援用它的值;与之相同,变量 name 是从命令行中解析了选项后、残余的参数中的 第一个 ,它是以地位来标识的。clingon 通过函数clingon:make-option 来定义选项,它提供了丰盛的控制能力。

选项名称

选项有好几种名字,一种叫做 :key,是在程序外部应用的名字,用作函数clingon:getopt 的参数之一;一种叫做:long-name,个别为多于一个字符的字符串,如"count",在命令行该名称须要带上两个连字符的前缀来应用,如--count 3;最初一种叫做:short-name,为一个独自的字符,如#\v,在命令行中带上一个连字符前缀来应用,如-v

必要性与默认值

通过传入参数 :required t 给函数 clingon:make-option,能够要求一个选项为必传的。例如上面的命令的选项--n 就是必传的

(defun top-level/handler (cmd)
  (dotimes (i (clingon:getopt cmd :n))
    (declare (ignorable i))
    (format t ".")))

(defun main (&rest argv)
  (let ((app (clingon:make-command
              :handler #'top-level/handler
              :name "dots"
              :options (list
                        (clingon:make-option
                         :integer
                         :description "打印的英文句号的数量"
                         :key :n
                         :long-name "n"
                         :required t)))))
    (clingon:run app argv)))

如果不心愿在一些最简略的状况下也要繁琐地编写 --n 1 这样的命令行参数,能够用 :initial-value 1 来指定。除此之外,也能够让选项默认读取指定的环境变量中的值,应用 :env-vars 指定环境变量名即可

(defun top-level/handler (cmd)
  (format t "Hello ~A~%" (clingon:getopt cmd :username)))

(defun main (&rest argv)
  (let ((app (clingon:make-command
              :handler #'top-level/handler
              :name "greet"
              :options (list
                        (clingon:make-option
                         :string
                         :description "用户名"
                         :env-vars '("GREETER_USERNAME")
                         :key :username
                         :long-name "username")))))
    (clingon:run app argv)))

可屡次应用的选项

curl 中的选项 -H 就是能够屡次应用的,每指定一次就能够在申请中增加一个 HTTP 头部,如下图所示

在 clingon 中能够通过往函数 clingon:make-option 传入 :list 来实现。当用 clingon:getopt 取出类型为 :list 的选项的值时,失去的是一个列表,其中顺次寄存着输出的值的字符串。

(defun top-level/handler (cmd)
  (let ((messages (clingon:getopt cmd :message)))
    (format t "~{~A~^~%~}" messages)))

(defun main (&rest argv)
  (let ((app (clingon:make-command
              :handler #'top-level/handler
              :name "commit"
              :options (list
                        (clingon:make-option
                         :list
                         :description "提交的音讯"
                         :key :message
                         :long-name "message"
                         :short-name #\m)))))
    (clingon:run app argv)))

另一种状况是只管没有值,但依然屡次应用同一个选项。例如命令 ssh 的选项 -v,应用的次数越多(最多为 3 次),则ssh 打印的调试信息也就越具体。这种类型的选项在 clingon 中称为:counter

(defun top-level/handler (cmd)
  (format t "Verbosity: ~D~%" (clingon:getopt cmd :verbose)))

(defun main (&rest argv)
  (let ((app (clingon:make-command
              :handler #'top-level/handler
              :name "log"
              :options (list
                        (clingon:make-option
                         :counter
                         :description "啰嗦水平"
                         :key :verbose
                         :long-name "verbose"
                         :short-name #\v)))))
    (clingon:run app argv)))

信号选项

有一些选项只须要辨别【有】和【没有】两种状况就能够了,而不须要在意这个选项的值——或者这类选项自身就不容许有值,例如 docker run 命令的选项 -d--detach。这种选项的类型为 :boolean/true,如果指定了这个选项,那么取出来的值始终为t。与之相同,类型:boolean/false 取出来的值始终为nil

(defun top-level/handler (cmd)
  (let ((rv (software-type)))
    (when (clingon:getopt cmd :shout)
      (setf rv (concatenate 'string (string-upcase rv)"!!!!111")))

    (format t "~A~%" rv)))

(defun main (&rest argv)
  (let ((app (clingon:make-command
              :handler #'top-level/handler
              :name "info"
              :options (list
                        (clingon:make-option
                         :boolean/true
                         :description "大喊"
                         :key :shout
                         :long-name "shout")))))
    (clingon:run app argv)))

抉择型选项

如果一个选项只管承受的是字符串,但并非所有输出都是有意义的,例如命令 dot 的选项 -T。从dot 的 man 文档能够看到,它所反对的图片类型是无限的,如 pspdfpng 等。比起申明一个 :string 类型的选项,让 clingon 代劳输出值的有效性查看来得更轻松,这里能够应用 :choice 类型

(defun top-level/handler (cmd)
  (format t "~A~%" (clingon:getopt cmd :hash-type)))

(defun main (&rest argv)
  (let ((app (clingon:make-command
              :handler #'top-level/handler
              :name "digest"
              :options (list
                        (clingon:make-option
                         :choice
                         :description "哈希类型"
                         :items '("MD5""SHA1")
                         :key :hash-type
                         :long-name "hash-type")))))
    (clingon:run app argv)))

浏览原文

退出移动版