Julia. Scripts e argumentos de linha de comando de análise


Continuamos a lidar com a linguagem de programação Julia. Como é simplesmente necessário ter um modo de operação em lote para uma linguagem focada na análise e processamento de dados, considere os recursos da implementação de scripts na linguagem Julia e passando argumentos da linha de comando para eles. Este tópico pode parecer banal para alguém, mas, dada a novidade da linguagem, espero que uma pequena visão geral dos métodos para analisar argumentos e bibliotecas de linha de comando para isso, apresentada em Julia, ainda seja útil.


Para começar, algumas palavras sobre como o script é elaborado. Qualquer script começa com uma linha em um formato especial que indica o intérprete. A linha começa com uma sequência conhecida como Shebang. Para Julia, esta linha é:


#!/usr/bin/env julia 

Obviamente, você não pode fazer isso, mas é necessário executar o script com o comando:


 julia .jl 

Além disso, qualquer script deve terminar com um caractere de nova linha. Este é um requisito do padrão POSIX, que segue da definição de uma sequência como uma sequência de caracteres terminados por um caractere de nova linha.


Para que o script seja executado diretamente, ele deve ter o atributo executable . Você pode adicionar esse atributo no terminal com o comando:


 chmod +x .jl 

Essas regras são válidas para todos os sistemas operacionais modernos, exceto, talvez, o MS Windows.


Matriz ARGS


Vamos passar para a primeira opção para passar parâmetros. Os argumentos da linha de comando estão disponíveis no script Julia por meio da constante Base.ARGS da matriz. Vamos preparar o script mais simples:


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

Esse script simplesmente imprime no console o tipo e o conteúdo da matriz ARGS.


Muitas vezes, o nome do arquivo é passado como argumentos de linha de comando. E aqui há uma peculiaridade de processar um modelo de arquivo passado como argumento. Por exemplo, execute nosso script usando o comando ./args.jl *.jl e obtenha:


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

E agora vamos mudar um pouco o parâmetro da linha de comando, envolvendo a máscara entre aspas:
./args.jl "*.jl" . Como resultado, obtemos:


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

Vemos a diferença óbvia. No primeiro caso, obtivemos uma matriz com os nomes de todos os arquivos que estão no mesmo diretório. No segundo caso, essa é apenas a mesma máscara que foi passada como argumento para a linha de comando. O motivo desse comportamento diferente do script é que o interpretador do bash (bem como os que estão próximos), a partir do qual o script foi executado, reconhece os modelos de nome de arquivo. Pode encontrar mais informações no mecanismo de pesquisa para "Correspondência de padrões de basquete" ou "Curingas de basquete". E todos juntos se chama Globs.


Entre os modelos, é possível mascarar vários caracteres - *, mascarar um caractere -? .. Pesquise por [...] intervalo e, até, a capacidade de especificar combinações complexas:


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

Veja o Resumo das Ferramentas de Linha de Comando GNU / Linux para mais informações.


Se, por algum motivo, não quisermos usar o mecanismo globs fornecido pelo bash, você poderá encontrar arquivos por máscara já do script usando o pacote Globs.jl.
O código a seguir converte tudo encontrado na cadeia de argumentos em uma única matriz de nomes de arquivos. Ou seja, independentemente de o usuário ter especificado a máscara entre aspas, sem aspas ou simplesmente listado os nomes de arquivos existentes ou inexistentes, apenas os nomes dos arquivos ou diretórios reais permanecerão na matriz de filelist resultante.


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

Esses exemplos simples, de fato, são uma demonstração do uso da matriz ARGS, em que o programador implementa toda a lógica para analisar argumentos. Essa abordagem é frequentemente usada quando o conjunto de argumentos é extremamente simples. Por exemplo, uma lista de nomes de arquivos. Ou uma ou duas opções que podem ser tratadas por operações simples de cadeia. O acesso aos elementos ARGS é o mesmo que aos elementos de qualquer outra matriz. Lembre-se apenas de que o índice do primeiro elemento da matriz em Julia é 1.


Pacote ArgParse.jl


É uma ferramenta flexível para descrever atributos e opções da linha de comando sem a necessidade de implementar a lógica de análise.
Usaremos um exemplo ligeiramente modificado da documentação do pacote - 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() 

Se executarmos esse script sem argumentos, obteremos a saída de informações de referência em sua composição:


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

Além disso, entre colchetes, vemos argumentos opcionais. Embora o argumento marcado como arg1 (ou seja, o que substituímos por ele) seja obrigatório.


Execute-o novamente, mas especifique o atributo requerido 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 

Podemos ver que parsed_args é uma matriz associativa, onde as chaves são os nomes dos atributos de acordo com a declaração feita na função parse_commandline , e seus valores são os que foram configurados por padrão ou transmitidos como os valores dos argumentos da linha de comandos. Além disso, os valores são do tipo especificado explicitamente durante a declaração.


Os argumentos são @add_arg_table usando a macro @add_arg_table . É possível declarar opções:


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

Ou argumentos


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

Além disso, as opções podem ser especificadas indicando a forma completa e curta (ao mesmo tempo --opt2 e -o ). Ou, apenas em uma única forma. O tipo é especificado no campo arg_type . O valor padrão pode ser definido usando default = ... Uma alternativa ao valor padrão é exigir um argumento - required = true .
É possível declarar uma ação automática, por exemplo, atribuir true ou false dependendo da presença ou ausência de um argumento. Isso é feito usando a action = :store_true


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

O campo de help contém o texto que será exibido no prompt na linha de comando.
Se na inicialização especificarmos todos os atributos, obteremos:


 >./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 

Para depuração do IDE Atom / Juno, nas primeiras linhas do script, você pode adicionar o seguinte código, um pouco sujo, mas funcional, para inicializar a matriz ARGS.


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

A macro @__FILE__ é o nome do arquivo no qual a macro está implantada. E esse nome para REPL é diferente do nome do arquivo de programa atual obtido por meio de Base.source_path() . É impossível inicializar a Base.ARGS matriz Base.ARGS valor diferente, mas ao mesmo tempo, você pode adicionar novas linhas, pois a matriz em si não é uma constante. Uma matriz é uma coluna para Julia, então usamos vcat (concatenar vertical).


No entanto, nas configurações do editor Juno, você pode definir os argumentos para executar o script. Mas eles terão que ser alterados todas as vezes para cada script depurado individualmente.


Pacote DocOpt.jl


Esta opção é uma implementação da abordagem da linguagem de marcação docopt - http://docopt.org/ . A idéia principal dessa linguagem é uma descrição declarativa de opções e argumentos em um formulário, que também pode ser uma descrição interna de um script. Uma linguagem de modelo especial é usada.


Usaremos um exemplo da documentação deste pacote 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 

A notação doc = ... é a criação da sequência de doc Julia, que contém toda a declaração para docopt. O resultado da execução na linha de comando sem argumentos será:


 >./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 

Se usarmos a dica e tentarmos "criar uma nova nave", obteremos uma impressão da matriz args associativa, gerada pelo resultado da análise da linha de comando


 >./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") 

A função docopt declarada como:


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

Os argumentos nomeados help , version , oprtions_first , exit_on_error especificam o comportamento do analisador de comandos do argumento por padrão. Por exemplo, no caso de erros - para concluir a execução, na solicitação de versão, retorne o valor version=… substituído aqui, na solicitação -h - emita a ajuda. options_first usado para indicar que as opções devem estar antes dos argumentos posicionais.


Agora, vamos analisar mais detalhadamente essa linguagem declarativa e a reação do analisador de argumentos aos valores inseridos.


A declaração começa com um texto arbitrário que, além do texto da linha de comando, pode fazer parte da documentação do próprio script. A palavra de serviço "Uso:" declara padrões de uso para este script.


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

Os argumentos são declarados no formato <name> , <x> , <y> . Observe que na matriz associativa args obtida anteriormente, esses argumentos atuam como chaves. Usamos o formulário de inicialização ./docopt.jl ship new Bystriy , para ./docopt.jl ship new Bystriy os seguintes valores explicitamente inicializados:


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

De acordo com a linguagem docopt, os elementos opcionais são especificados entre colchetes. Por exemplo [--speed=<kn>] . Os elementos obrigatórios são especificados entre parênteses, mas com uma determinada condição. Por exemplo (set|remove) define o requisito para um deles. Se o elemento for especificado sem colchetes, por exemplo naval_fate.jl --version , ele diz que nesta opção de execução específica, --version é uma opção necessária.


A próxima seção é a seção de descrição da opção. Começa com a palavra "Opções:"
As opções são declaradas cada uma em uma linha separada. Os recuos à esquerda do início de uma linha são importantes. Para cada opção, você pode especificar o formulário completo e abreviado. Bem como a descrição da opção exibida na dica de ferramenta. Nesse caso, as opções -h | --help, --version -h | --help, --version reconhecidos automaticamente. A resposta a eles é dada pelos argumentos da função docopt . Interessante a considerar é a declaração:


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

Aqui, o formulário ...=<kn> define a presença de algum valor e [default: 10] define o valor padrão. Voltamos novamente aos valores obtidos em args :


 "--speed"=>"10" 

A diferença fundamental, por exemplo, do pacote ArgParse, é que os valores não são digitados. Ou seja, o valor default: 10 é definido como a sequência "10".
Quanto aos outros argumentos, que são apresentados em args como resultado da análise dos argumentos, você deve prestar atenção aos seus valores:


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

Ou seja, absolutamente todos os elementos do modelo especificado na declaração docopt para todos os casos de uso são apresentados como resultado da análise com os nomes originais. Todos os argumentos opcionais que não estavam presentes na linha de comando são falsos aqui. Os argumentos <x> , <y> também <y> ausentes na linha de lançamento e não têm o valor nada. Outros argumentos para os quais o padrão de análise correspondia receberam valores verdadeiros:


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

E já recebemos valores específicos para os seguintes elementos do modelo:


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

O primeiro valor foi definido explicitamente na linha de comandos como uma substituição de argumento e o segundo foi uma opção com um valor padrão.

Observe também que o nome do script atual pode ser calculado automaticamente.
Por exemplo, podemos inserir:


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

Uma recomendação adicional para colocar o analisador de argumentos da linha de comandos é colocá-lo no início do arquivo. Uma característica desagradável de Julia no momento é uma conexão bastante longa de módulos. Por exemplo, using Plots; using DataFrames using Plots; using DataFrames pode enviar um script para aguardar alguns segundos. Isso não é um problema para scripts de carregamento único do lado do servidor, mas incomodará os usuários que desejam apenas ver uma dica para argumentos de linha de comando. É por isso que, primeiro, você precisa emitir ajuda e verificar os argumentos da linha de comando e, somente então, continuar o download das bibliotecas necessárias para o trabalho.


Conclusão


O artigo não pretende considerar todos os métodos de análise de argumentos em Julia. No entanto, as opções consideradas, de fato, abrangem três opções possíveis. Análise totalmente manual da matriz ARGS . Argumentos estritamente declarados mas analisados ​​automaticamente no ArgParse. E uma forma de documento totalmente declarativa, embora não estrita. A escolha do caso de uso depende inteiramente da complexidade dos argumentos analisados. A opção usando docopt parece ser a mais fácil de usar, embora exija uma conversão explícita de tipo para os valores dos argumentos recebidos. No entanto, se o script não aceitar nada além do nome do arquivo, será possível tirar vantagem da emissão de ajuda usando a função habitual println("Run me with file name") e analise os nomes dos arquivos diretamente do ARGS como mostrado na primeira seção.


Referências


Source: https://habr.com/ru/post/pt430942/


All Articles