朱莉娅 脚本和解析命令行参数


我们将继续使用编程语言Julia。 由于只需要针对一种专注于数据分析和处理的语言使用批处理操作模式,因此请考虑使用Julia语言实现脚本并将参数从命令行传递给它们的功能。 对于某人来说,这个主题似乎有些陈词滥调,但是,鉴于该语言的新颖性,我希望以Julia呈现的有关解析命令行参数和库的方法的简要概述仍然有用。


首先,介绍一下脚本的制作方式。 任何脚本都以特殊格式的行开头,该行指示解释器。 该行以称为Shebang的序列开头。 对于Julia,此行是:


#!/usr/bin/env julia 

当然,您不能执行此操作,但随后必须使用以下命令运行脚本:


 julia .jl 

另外,任何脚本都必须以换行符结尾。 这是POSIX标准的要求,它来自于将字符串定义为以换行符结尾的字符序列的字符串。


为了直接运行脚本,它必须具有executable属性。 您可以使用以下命令在终端中添加这样的属性:


 chmod +x .jl 

这些规则对所有现代操作系统(MS Windows除外)均有效。


ARGS阵列


让我们继续第一个传递参数的选项。 在Julia脚本中,可以通过数组常量Base.ARGS使用命令行参数。 让我们准备最简单的脚本:


 #!/usr/bin/env julia @show typeof(ARGS) @show ARGS 

该脚本仅将ARGS数组的类型和内容打印到控制台。


通常,文件名作为命令行参数传递。 这是处理作为参数传递的文件模板的特性。 例如,使用命令./args.jl *.jl运行我们的脚本并获取:


 >./args.jl *.jl typeof(ARGS) = Array{String,1} ARGS = ["argparse.jl", "args.jl", "docopt.jl"] 

现在让我们稍微更改一下命令行参数,用引号将掩码括起来:
./args.jl "*.jl" 。 结果,我们得到:


 >./args.jl "*.jl" typeof(ARGS) = Array{String,1} ARGS = ["*.jl"] 

我们看到明显的区别。 在第一种情况下,我们得到一个数组,其中包含同一目录中所有文件的名称。 在第二种情况下,这只是作为参数传递给命令行的同一掩码。 脚本具有这种不同行为的原因是,运行脚本的bash解释器(及其附近的解释器)识别文件名模板。 可以在搜索引擎中找到更多有关“ Bash模式匹配”或“ Bash通配符”的信息。 所有这些统称为Globs。


在模板中,可以屏蔽多个字符-*,屏蔽一个字符-?..按范围搜索,甚至可以指定复杂的组合:


 >./args.jl {args,doc}* typeof(ARGS) = Array{String,1} ARGS = ["args.jl", "docopt.jl"] 

有关更多信息,请参见GNU / Linux命令行工具摘要。


如果由于某种原因,我们不想使用bash提供的globs机制,则可以使用Globs.jl包从脚本中通过遮罩查找文件。
以下代码将在参数字符串中找到的所有内容转换为单个文件名数组。 也就是说,无论用户是在引号中指定掩码还是在没有引号的情况下,还是仅列出现有文件或不存在文件的名称,都仅将实际文件或目录的名称保留在结果filelist数组中。


 using Glob filelist = unique(collect(Iterators.flatten(map(arg -> glob(arg), ARGS)))) 

实际上,这些简单的示例演示了ARGS数组的用法,程序员在其中实现了用于解析参数的所有逻辑。 当参数集非常简单时,通常使用此方法。 例如,文件名列表。 或可以通过简单的字符串操作处理的一个或两个选项。 对ARGS元素的访问与对任何其他数组的元素的访问相同。 仅记住Julia中数组的第一个元素的索引为1。


软件包ArgParse.jl


它是用于描述命令行的属性和选项的灵活工具,无需实现解析逻辑。
我们将使用软件包文档中经过稍微修改的示例-http : //carlobaldassi.imtqy.com/ArgParse.jl/stable/


 #!/usr/bin/env julia using ArgParse function parse_commandline() s = ArgParseSettings() @add_arg_table s begin "--opt1" help = "an option with an argument" "--opt2", "-o" help = "another option with an argument" arg_type = Int default = 0 "--flag1" help = "an option without argument, ie a flag" action = :store_true "arg1" help = "a positional argument" required = true end return parse_args(s) end function main() @show parsed_args = parse_commandline() println("Parsed args:") for (arg,val) in parsed_args print(" $arg => ") show(val) println() end end main() 

如果我们在不带参数的情况下运行此脚本,则会获得有关其组成的参考信息的输出:


 >./argparse.jl required argument arg1 was not provided usage: argparse.jl [--opt1 OPT1] [-o OPT2] [--flag1] arg1 

此外,在方括号中,我们看到了可选参数。 标为arg1的参数(也就是我们替代它的参数)是强制性的。


再次运行它,但是指定必需的属性arg1


 >./argparse.jl test parsed_args = parse_commandline() = Dict{String,Any}("flag1"=>false,"arg1"=>"test","opt1"=>nothing,"opt2"=>0) Parsed args: flag1 => false arg1 => "test" opt1 => nothing opt2 => 0 

我们可以看到parsed_args是一个关联数组,其中键是根据parse_commandline函数中声明的属性名称,其值是默认设置或作为命令行参数值传递的值。 而且,这些值具有在声明期间明确指定的类型。


使用@add_arg_table@add_arg_table参数。 可以声明选项:


  "--opt2", "-o" help = "another option with an argument" arg_type = Int default = 0 

或论点


  "arg1" help = "a positional argument" required = true 

此外,可以指定指示完整和简短格式的选项(同时--opt2-o )。 或者,仅以一种形式。 该类型在arg_type字段中指定。 可以使用default = ...设置默认值。 替代默认值的方法是需要一个参数required = true
可以声明自动操作,例如,根据参数的存在或不存在来分配truefalse 。 这是通过action = :store_true


  "--flag1" help = "an option without argument, ie a flag" action = :store_true 

help字段包含将在命令行提示中显示的文本。
如果在启动时指定所有属性,则得到:


 >./argparse.jl --opt1 "2+2" --opt2 "4" somearg --flag parsed_args = parse_commandline() = Dict{String,Any}("flag1"=>true,"arg1"=>"somearg","opt1"=>"2+2","opt2"=>4) Parsed args: flag1 => true arg1 => "somearg" opt1 => "2+2" opt2 => 4 

为了从Atom / Juno IDE进行调试,您可以在脚本的第一行中添加以下内容(虽然有些脏但可以使用)来初始化ARGS数组。


 if (Base.source_path() != Base.basename(@__FILE__)) vcat(Base.ARGS, ["--opt1", "2+2", "--opt2", "4", "somearg", "--flag"] ) end 

@__FILE__是在其中部署宏的文件的名称。 REPL的名称与通过Base.source_path()获得的当前程序文件的名称不同。 无法Base.ARGS其他值初始化Base.ARGS数组Base.ARGS ,但是同时,由于数组本身不是常量,因此可以添加新行。 数组是Julia的列,因此我们使用vcat (垂直串联)。


但是,在Juno编辑器的设置中,可以设置用于运行脚本的参数。 但是对于每个调试脚本,每次都必须更改它们。


软件包DocOpt.jl


此选项是docopt标记语言方法的实现-http://docopt.org/。 该语言的主要思想是以形式对选项和参数进行声明式描述,也可以是脚本的内部描述。 使用一种特殊的模板语言。


我们将使用该包文档中的示例https://github.com/docopt/DocOpt.jl


 #!/usr/bin/env julia doc = """Naval Fate. Usage: naval_fate.jl ship new <name>... naval_fate.jl ship <name> move <x> <y> [--speed=<kn>] naval_fate.jl ship shoot <x> <y> naval_fate.jl mine (set|remove) <x> <y> [--moored|--drifting] naval_fate.jl -h | --help naval_fate.jl --version Options: -h --help Show this screen. --version Show version. --speed=<kn> Speed in knots [default: 10]. --moored Moored (anchored) mine. --drifting Drifting mine. """ using DocOpt # import docopt function args = docopt(doc, version=v"2.0.0") @show args 

doc = ...是Julia doc字符串的创建,其中包含docopt的整个声明。 在不带参数的情况下在命令行上运行的结果将是:


 >./docopt.jl Usage: naval_fate.jl ship new <name>... naval_fate.jl ship <name> move <x> <y> [--speed=<kn>] naval_fate.jl ship shoot <x> <y> naval_fate.jl mine (set|remove) <x> <y> [--moored|--drifting] naval_fate.jl -h | --help naval_fate.jl --version 

如果使用提示并尝试“创建新船”,则将获得关联args数组的打印输出,该数组是由解析命令行的结果生成的


 >./docopt.jl ship new Bystriy args = Dict{String,Any}( "remove"=>false, "--help"=>false, "<name>"=>["Bystriy"], "--drifting"=>false, "mine"=>false, "move"=>false, "--version"=>false, "--moored"=>false, "<x>"=>nothing, "ship"=>true, "new"=>true, "shoot"=>false, "set"=>false, "<y>"=>nothing, "--speed"=>"10") 

docopt函数声明为:


 docopt(doc::AbstractString, argv=ARGS; help=true, version=nothing, options_first=false, exit_on_error=true) 

默认情况下,命名参数helpversionexit_on_error指定参数命令解析器的行为。 例如,如果发生错误-若要完成执行,请在版本请求中,在-h请求中返回此处替换的值version=…发出帮助。 options_first用于指示选项应在位置参数之前。


现在,让我们仔细看一下这种声明性语言以及参数解析器对输入值的反应。


声明以任意文本开头,除了命令行文本外,该文本可以是脚本本身文档的一部分。 服务词“用法:”声明此脚本的用法模式。


 Usage: naval_fate.jl ship new <name>... naval_fate.jl ship <name> move <x> <y> [--speed=<kn>] 

参数以<name><x><y>的形式声明。 请注意,在之前获得的args关联数组中,这些参数充当键。 我们使用启动表单./docopt.jl ship new Bystriy ,因此获得了以下显式初始化的值:


  "<name>"=>["Bystriy"], "ship"=>true, "new"=>true, 

根据docopt语言,可选元素在方括号中指定。 例如[--speed=<kn>] 。 强制性元素在括号中指定,但有一定条件。 例如(set|remove)设置其中之一的要求。 如果指定的元素没有括号,例如naval_fate.jl --version ,则表示在此特定的运行选项中, --version naval_fate.jl --version是必需的选项。


下一部分是选项描述部分。 它以“选项:”一词开头
选项在单独的行中声明。 行开头左侧的填充很重要。 对于每个选项,您都可以指定完整和简短形式。 以及工具提示中显示的选项说明。 在这种情况下,选项-h | --help, --version -h | --help, --version自动识别。 对它们的响应由docopt函数的参数给出。 值得考虑的是声明:


  --speed=<kn> Speed in knots [default: 10]. 

在这里,形式...=<kn>定义某个值的存在, [default: 10]定义默认值。 我们再次转向在args获得的值:


 "--speed"=>"10" 

例如,与ArgParse包的基本区别是未键入值。 即, default: 10值设置为字符串“ 10”。
至于其他参数,由于解析参数而以args形式出现,因此您应注意它们的值:


  "remove"=>false, "--help"=>false, "--drifting"=>false, "mine"=>false, "move"=>false, "--version"=>false, "--moored"=>false, "<x>"=>nothing, "shoot"=>false, "set"=>false, "<y>"=>nothing, 

也就是说,在docopt声明中为所有用例绝对指定的模板的所有元素都是使用原始名称进行分析的结果。 命令行中不存在的所有可选参数在此处均为false。 启动行<y>也缺少参数<x><y>并且其值无。 解析模式与之匹配的其他参数接收到真实值:


  "ship"=>true, "new"=>true, 

而且我们已经收到了模板以下元素的特定值:


  "<name>"=>["Bystriy"], "--speed"=>"10" 

第一个值在命令行上显式设置为参数替换,第二个是具有默认值的选项。

另请注意,可以自动计算当前脚本的名称。
例如,我们可以输入:


 doc = """Naval Fate. Usage: $(Base.basename(@__FILE__)) ship new <name>… """ 

放置命令行参数解析器的另一项建议是将其放置在文件的最开始。 目前,Julia的一个令人不快的功能是模块之间的连接时间较长。 例如using Plots; using DataFrames using Plots; using DataFrames可以发送脚本以等待几秒钟。 对于服务器端的单加载脚本而言,这不是问题,但是它将使只想查看命令行参数提示的用户感到烦恼。 因此,首先需要发出帮助并检查命令行参数,然后才开始下载工作所需的库。


结论


本文并不假装考虑Julia中解析参数的所有方法。 但是,考虑的选项实际上涵盖了3种可能的选项。 完全手动分析ARGS阵列。 在ArgParse中严格声明但自动解析的参数。 还有一种完全声明性的(尽管不是严格的)docopt形式。 用例的选择完全取决于解析参数的复杂性。 使用docopt的选项似乎最易于使用,尽管它需要对接收到的参数的值进行显式类型转换。 但是,如果脚本不接受文件名以外的任何其他内容,则很有可能利用通常的println("Run me with file name")功能为其提供帮助,并直接从ARGS解析文件名,如图所示在第一部分。


参考文献


Source: https://habr.com/ru/post/zh-CN430942/


All Articles