Julia Scripts y análisis de argumentos de línea de comando


Seguimos lidiando con el lenguaje de programación Julia. Dado que es simplemente necesario tener un modo de operación por lotes para un lenguaje enfocado en el análisis y procesamiento de datos, considere las características de implementar scripts en el lenguaje Julia y pasarles argumentos desde la línea de comandos. Este tema puede parecer trivial para alguien, pero, dada la novedad del lenguaje, espero que una pequeña descripción de los métodos para analizar los argumentos de la línea de comandos y las bibliotecas para esto, presentada en Julia, siga siendo útil.


Para empezar, algunas palabras sobre cómo se hace el guión. Cualquier secuencia de comandos comienza con una línea en un formato especial que indica el intérprete. La línea comienza con una secuencia conocida como Shebang. Para Julia, esta línea es:


#!/usr/bin/env julia 

Por supuesto, no puede hacer esto, pero luego debe ejecutar el script con el comando:


 julia .jl 

Además, cualquier script debe terminar con un carácter de nueva línea. Este es un requisito del estándar POSIX, que se deriva de la definición de una cadena como una secuencia de caracteres terminados por un carácter de nueva línea.


Para que el script se ejecute directamente, debe tener el atributo executable . Puede agregar dicho atributo en la terminal con el comando:


 chmod +x .jl 

Estas reglas son válidas para todos los sistemas operativos modernos, excepto, tal vez, MS Windows.


Matriz ARGS


Pasemos a la primera opción para pasar parámetros. Los argumentos de la línea de comando están disponibles en el script de Julia a través de la constante de matriz Base.ARGS. Preparemos el guión más simple:


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

Este script simplemente imprime en la consola el tipo y el contenido de la matriz ARGS.


Muy a menudo, el nombre del archivo se pasa como argumentos de línea de comando. Y aquí hay una peculiaridad de procesar una plantilla de archivo que se pasa como argumento. Por ejemplo, ejecute nuestro script usando el comando ./args.jl *.jl y obtenga:


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

Y ahora cambiemos un poco el parámetro de la línea de comando, rodeando la máscara con comillas:
./args.jl "*.jl" . Como resultado, obtenemos:


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

Vemos la diferencia obvia. En el primer caso, obtuvimos una matriz con los nombres de todos los archivos que están en el mismo directorio. En el segundo caso, esta es solo la misma máscara que se pasó como argumento a la línea de comando. La razón de este comportamiento diferente del script es porque el intérprete bash (así como los que están cerca de él), desde el cual se ejecutó el script, reconoce las plantillas de nombre de archivo. Se puede encontrar más en el motor de búsqueda de "Bash Pattern Matching" o "Bash Wildcards". Y todos juntos se llama Globs.


Entre las plantillas, es posible enmascarar varios caracteres - *, enmascarar un carácter -? .. Buscar por rango [...] e, incluso, la capacidad de especificar combinaciones complejas:


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

Consulte el Resumen de herramientas de línea de comandos de GNU / Linux para obtener más información.


Si, por alguna razón, no queremos utilizar el mecanismo global proporcionado por bash, puede encontrar archivos por máscara desde el script utilizando el paquete Globs.jl.
El siguiente código convierte todo lo que se encuentra en la cadena de argumento en una sola matriz de nombres de archivo. Es decir, independientemente de si el usuario especificó la máscara entre comillas, sin comillas, o simplemente enumeró los nombres de archivos existentes o inexistentes, solo los nombres de los archivos o directorios reales permanecerán en la matriz resultante de la lista de filelist .


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

Estos ejemplos simples, de hecho, son una demostración del uso de la matriz ARGS, donde el programador implementa toda la lógica para analizar argumentos. Este enfoque se usa a menudo cuando el conjunto de argumentos es extremadamente simple. Por ejemplo, una lista de nombres de archivo. O una o dos opciones que pueden manejarse mediante simples operaciones de cadena. El acceso a los elementos ARGS es el mismo que para los elementos de cualquier otra matriz. Recuerde solo que el índice del primer elemento de la matriz en Julia es 1.


Paquete ArgParse.jl


Es una herramienta flexible para describir atributos y opciones de la línea de comando sin la necesidad de implementar la lógica de análisis.
Utilizaremos un ejemplo ligeramente modificado de la documentación del paquete: 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() 

Si ejecutamos este script sin argumentos, obtenemos la salida de información de referencia sobre su composición:


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

Además, entre corchetes vemos argumentos opcionales. Si bien el argumento marcado como arg1 (es decir, lo que lo sustituimos) es obligatorio.


Ejecútelo nuevamente, pero especifique el 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 es una matriz asociativa, donde las claves son los nombres de los atributos de acuerdo con la declaración realizada en la función parse_commandline , y sus valores son los que se configuraron por defecto o se pasaron como los valores de los argumentos de la línea de comandos. Además, los valores son del tipo que se especifica explícitamente durante la declaración.


Los argumentos se @add_arg_table utilizando la macro @add_arg_table . Es posible declarar opciones:


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

O argumentos


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

Además, se pueden especificar opciones que indiquen la forma completa y corta (al mismo tiempo --opt2 y -o ). O, solo en una sola forma. El tipo se especifica en el campo arg_type . El valor predeterminado se puede establecer usando default = ... Una alternativa al valor predeterminado es requerir un argumento: required = true .
Es posible declarar una acción automática, por ejemplo, asignar true o false dependiendo de la presencia o ausencia de un argumento. Esto se hace usando action = :store_true


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

El campo de help contiene el texto que se mostrará en la solicitud en la línea de comando.
Si al inicio especificamos todos los atributos, obtenemos:


 >./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 la depuración del IDE de Atom / Juno, en las primeras líneas del script puede agregar el siguiente código, algo sucio pero funcional, para inicializar la matriz ARGS.


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

La macro @__FILE__ es el nombre del archivo en el que se implementa la macro. Y este nombre para REPL es diferente del nombre del archivo de programa actual obtenido a través de Base.source_path() . Es imposible inicializar la Base.ARGS matriz Base.ARGS valor diferente, pero al mismo tiempo, puede agregar nuevas líneas, ya que la matriz en sí no es una constante. Una matriz es una columna para Julia, por lo que utilizamos vcat (concatenación vertical).


Sin embargo, en la configuración del editor Juno, puede establecer los argumentos para ejecutar el script. Pero deberán cambiarse cada vez para cada script depurado individualmente.


Paquete DocOpt.jl


Esta opción es una implementación del enfoque del lenguaje de marcado docopt: http://docopt.org/ . La idea principal de este lenguaje es una descripción declarativa de opciones y argumentos en un formulario, que también puede ser una descripción interna de un script. Se utiliza un lenguaje de plantilla especial.


Utilizaremos un ejemplo de la documentación de este paquete 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 

La notación doc = ... es la creación de la cadena de doc Julia, que contiene la declaración completa de docopt. El resultado de ejecutarse en la línea de comando sin 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 

Si usamos la sugerencia e intentamos "crear una nueva nave", obtenemos una copia impresa de la matriz de args asociativos, que se generó como resultado del análisis de la línea 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") 

La función docopt declara como:


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

Los argumentos nombrados help , version , oprtions_first , exit_on_error especifican el comportamiento del analizador de argumentos por la línea de comando por defecto. Por ejemplo, en caso de errores, para completar la ejecución, en la solicitud de versión, devuelva el valor version=… sustituido aquí, en la solicitud -h , emita ayuda. options_first usa para indicar que las opciones deben estar antes de los argumentos posicionales.


Ahora, echemos un vistazo más de cerca a este lenguaje declarativo y la reacción del analizador de argumentos a los valores ingresados.


La declaración comienza con texto arbitrario, que, además del texto de la línea de comando, puede formar parte de la documentación del script en sí. La palabra de servicio "Uso:" declara patrones de uso para este script.


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

Los argumentos se declaran en la forma <name> , <x> , <y> . Tenga en cuenta que en la matriz asociativa args que se obtuvo anteriormente, estos argumentos actúan como claves. Utilizamos el formulario de lanzamiento ./docopt.jl ship new Bystriy , por lo que obtuvimos los siguientes valores inicializados explícitamente:


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

Según el lenguaje docopt, los elementos opcionales se especifican entre corchetes. Por ejemplo [--speed=<kn>] . Los elementos obligatorios se especifican entre paréntesis, pero con una determinada condición. Por ejemplo (set|remove) establece el requisito para uno de ellos. Si el elemento se especifica sin corchetes, por ejemplo, naval_fate.jl --version , dice que en esta opción de ejecución particular, --version es una opción obligatoria.


La siguiente sección es la sección de descripción de la opción. Comienza con la palabra "Opciones:"
Las opciones se declaran cada una en una línea separada. El relleno a la izquierda del comienzo de la línea es importante. Para cada opción, puede especificar la forma completa y corta. Además de la descripción de la opción que se muestra en la información sobre herramientas. En este caso, las opciones -h | --help, --version -h | --help, --version reconocen automáticamente. La respuesta a ellos viene dada por los argumentos de la función docopt . Es interesante considerar la declaración:


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

Aquí la forma ...=<kn> define la presencia de algún valor, y [default: 10] define el valor predeterminado. Volvemos nuevamente a los valores obtenidos en args :


 "--speed"=>"10" 

La diferencia fundamental, por ejemplo, del paquete ArgParse, es que los valores no están escritos. Es decir, el default: 10 se establece como la cadena "10".
En cuanto a los otros argumentos, que se presentan en argumentos como resultado del análisis de los argumentos, debe prestar atención a sus valores:


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

Es decir, absolutamente todos los elementos de la plantilla especificada en la declaración docopt para todos los casos de uso se presentan como resultado del análisis con los nombres originales. Todos los argumentos opcionales que no estaban presentes en la línea de comando son falsos aquí. Los argumentos <x> , <y> también faltan en la línea de lanzamiento y no tienen el valor nada. Otros argumentos para los cuales el patrón de análisis coincidió recibieron valores verdaderos:


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

Y ya hemos recibido valores específicos para los siguientes elementos de la plantilla:


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

El primer valor se estableció explícitamente en la línea de comando como una sustitución de argumento, y el segundo era una opción con un valor predeterminado.

También tenga en cuenta que el nombre del script actual se puede calcular automáticamente.
Por ejemplo, podemos ingresar:


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

Una recomendación adicional para colocar el analizador de argumentos de la línea de comando es colocarlo al comienzo del archivo. Una característica desagradable de Julia en este momento es una conexión de módulos bastante larga. Por ejemplo, using Plots; using DataFrames using Plots; using DataFrames puede enviar un script para esperar unos segundos. Esto no es un problema para los scripts de carga única del lado del servidor, pero molestará a los usuarios que solo quieren ver una pista para los argumentos de la línea de comandos. Por eso, primero debe emitir ayuda y verificar los argumentos de la línea de comandos, y solo entonces, proceder a descargar las bibliotecas necesarias para el trabajo.


Conclusión


El artículo no pretende considerar todos los métodos de análisis de argumentos en Julia. Sin embargo, las opciones consideradas, de hecho, cubren 3 opciones posibles. Análisis completamente manual de la matriz ARGS . Declaraciones estrictamente pero analizadas automáticamente en ArgParse. Y una forma completamente declarativa, aunque no estricta, de docopt. La elección del caso de uso depende completamente de la complejidad de los argumentos analizados. La opción que usa docopt parece ser la más fácil de usar, aunque requiere una conversión de tipo explícita para los valores de los argumentos recibidos. Sin embargo, si la secuencia de comandos no acepta nada más que el nombre del archivo, es muy posible aprovechar la ayuda que le println("Run me with file name") función usual println("Run me with file name") y analizar los nombres de archivo directamente desde ARGS como se muestra en la primera sección


Referencias


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


All Articles