Julia. Scripts et analyse des arguments de ligne de commande


Nous continuons à traiter avec le langage de programmation Julia. Puisqu'il est simplement nécessaire d'avoir un mode de fonctionnement par lots pour un langage axé sur l'analyse et le traitement des données, considérez les fonctionnalités d'implémentation de scripts dans le langage Julia et de leur passer des arguments de la ligne de commande. Ce sujet peut sembler banal à quelqu'un, mais, étant donné la nouveauté du langage, j'espère qu'un petit aperçu des méthodes d'analyse des arguments et des bibliothèques de ligne de commande pour cela, présenté dans Julia, sera toujours utile.


Pour commencer, quelques mots sur la façon dont le script est élaboré. Tout script commence par une ligne dans un format spécial qui indique l'interpréteur. La ligne commence par une séquence connue sous le nom de Shebang. Pour Julia, cette ligne est:


#!/usr/bin/env julia 

Bien sûr, vous ne pouvez pas le faire, mais vous devez ensuite exécuter le script avec la commande:


 julia .jl 

De plus, tout script doit se terminer par un caractère de nouvelle ligne. Il s'agit d'une exigence de la norme POSIX, qui découle de la définition d'une chaîne comme une séquence de caractères terminée par un caractère de nouvelle ligne.


Pour que le script soit exécuté directement, il doit avoir l'attribut executable . Vous pouvez ajouter un tel attribut dans le terminal avec la commande:


 chmod +x .jl 

Ces règles sont valables pour tous les systèmes d'exploitation modernes, à l'exception peut-être de MS Windows.


ARGS Array


Passons à la première option pour passer des paramètres. Les arguments de ligne de commande sont disponibles dans le script Julia via la constante de tableau Base.ARGS. Préparons le script le plus simple:


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

Ce script imprime simplement à la console le type et le contenu du tableau ARGS.


Très souvent, le nom de fichier est transmis comme arguments de ligne de commande. Et ici, il y a une particularité de traiter un modèle de fichier passé en argument. Par exemple, exécutez notre script en utilisant la commande ./args.jl *.jl et obtenez:


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

Et maintenant, changeons un peu le paramètre de ligne de commande, entourant le masque de guillemets:
./args.jl "*.jl" . En conséquence, nous obtenons:


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

Nous voyons la différence évidente. Dans le premier cas, nous avons obtenu un tableau avec les noms de tous les fichiers qui se trouvent dans le même répertoire. Dans le second cas, il s'agit uniquement du même masque qui a été passé en argument à la ligne de commande. La raison de ce comportement différent du script est que l'interpréteur bash (ainsi que ceux qui lui sont proches), à partir duquel le script a été exécuté, reconnaît les modèles de nom de fichier. Plus d'informations peuvent être trouvées dans le moteur de recherche pour "Bash Pattern Matching" ou "Bash Wildcards". Et tous ensemble, ça s'appelle Globs.


Parmi les modèles, il est possible de masquer plusieurs caractères - *, masquer un caractère -? .. Recherche par plage [...], et même la possibilité de spécifier des combinaisons complexes:


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

Voir le résumé des outils de ligne de commande GNU / Linux pour plus d'informations.


Si, pour une raison quelconque, nous ne voulons pas utiliser le mécanisme globs fourni par bash, vous pouvez déjà trouver des fichiers par masque à partir du script à l'aide du package Globs.jl.
Le code suivant convertit tout ce qui se trouve dans la chaîne d'arguments en un seul tableau de noms de fichiers. Autrement dit, que l'utilisateur ait spécifié le masque entre guillemets, sans les guillemets, ou simplement répertorié les noms des fichiers existants ou inexistants, seuls les noms des fichiers ou répertoires réels resteront dans le tableau de filelist résultant.


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

Ces exemples simples sont en fait une démonstration de l'utilisation du tableau ARGS, où le programmeur implémente toute la logique d'analyse des arguments. Cette approche est souvent utilisée lorsque l'ensemble d'arguments est extrêmement simple. Par exemple, une liste de noms de fichiers. Ou une ou deux options qui peuvent être gérées par de simples opérations de chaîne. L'accès aux éléments ARGS est le même qu'aux éléments de tout autre tableau. N'oubliez pas que l'index du premier élément du tableau dans Julia est 1.


Package ArgParse.jl


Il s'agit d'un outil flexible pour décrire les attributs et les options de la ligne de commande sans avoir à implémenter de logique d'analyse.
Nous utiliserons un exemple légèrement modifié de la documentation du paquet - 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 nous exécutons ce script sans arguments, nous obtenons la sortie d'informations de référence sur leur composition:


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

De plus, entre crochets, nous voyons des arguments facultatifs. Alors que l'argument marqué comme arg1 (c'est-à-dire ce que nous lui substituons) est obligatoire.


Exécutez-le à nouveau, mais spécifiez l'attribut requis 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 

Nous pouvons voir que parsed_args est un tableau associatif, où les clés sont les noms des attributs selon la déclaration faite dans la fonction parse_commandline , et leurs valeurs sont celles qui ont été définies par défaut ou transmises comme valeurs des arguments de la ligne de commande. De plus, les valeurs sont du type qui est explicitement spécifié lors de la déclaration.


Les arguments sont @add_arg_table à l'aide de la macro @add_arg_table . Il est possible de déclarer des options:


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

Ou des arguments


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

De plus, des options peuvent être spécifiées en indiquant la forme complète et courte (en même temps --opt2 et -o ). Ou, uniquement sous une seule forme. Le type est spécifié dans le champ arg_type . La valeur par défaut peut être définie à l'aide de default = ... Une alternative à la valeur par défaut est d'exiger un argument - required = true .
Il est possible de déclarer une action automatique, par exemple, affecter true ou false selon la présence ou l'absence d'un argument. Cela se fait en utilisant l' action = :store_true


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

Le champ d' help contient le texte qui sera affiché dans l'invite sur la ligne de commande.
Si au démarrage nous spécifions tous les attributs, alors nous obtenons:


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

Pour le débogage à partir de l'IDE Atom / Juno, dans les premières lignes du script, vous pouvez ajouter le code suivant, un peu sale, mais fonctionnel pour initialiser le tableau ARGS.


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

La macro @__FILE__ est le nom du fichier dans lequel la macro est déployée. Et ce nom pour REPL est différent du nom du fichier programme actuel obtenu via Base.source_path() . Il est impossible d'initialiser la Base.ARGS tableau Base.ARGS valeur différente, mais en même temps, vous pouvez ajouter de nouvelles lignes, car le tableau lui-même n'est pas une constante. Un tableau est une colonne pour Julia, nous utilisons donc vcat (concaténation verticale).


Cependant, dans les paramètres de l'éditeur Juno, vous pouvez définir les arguments pour exécuter le script. Mais ils devront être modifiés à chaque fois pour chaque script débogué individuellement.


Package DocOpt.jl


Cette option est une implémentation de l'approche du langage de balisage docopt - http://docopt.org/ . L'idée principale de ce langage est une description déclarative des options et des arguments dans un formulaire, qui peut également être une description interne d'un script. Un langage de modèle spécial est utilisé.


Nous utiliserons un exemple de la documentation de ce package 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 notation doc = ... est la création de la chaîne de doc Julia, qui contient la déclaration entière pour docopt. Le résultat de l'exécution sur la ligne de commande sans arguments sera:


 >./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 nous utilisons l'indice et essayons de «créer un nouveau vaisseau», nous obtenons une impression du tableau args associatif, qui a été généré par le résultat de l'analyse de la ligne de commande


 >./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 fonction docopt déclarée comme:


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

Les arguments nommés help , version , oprtions_first , exit_on_error spécifient le comportement de l'analyseur de commande d'argument par défaut. Par exemple, en cas d'erreurs - pour terminer l'exécution, sur la demande de version, renvoyez la valeur version=… substituée ici, sur la demande -h - lancez l'aide. options_first utilisé pour indiquer que les options doivent être avant les arguments positionnels.


Examinons maintenant de plus près ce langage déclaratif et la réaction de l'analyseur d'arguments aux valeurs entrées.


La déclaration commence par du texte arbitraire qui, en plus du texte de la ligne de commande, peut faire partie de la documentation du script lui-même. Le mot de service «Usage:» déclare les modèles d'utilisation de ce script.


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

Les arguments sont déclarés sous la forme <name> , <x> , <y> . Notez que dans le tableau associatif args obtenu précédemment, ces arguments agissent comme des clés. Nous avons utilisé le formulaire de lancement ./docopt.jl ship new Bystriy , nous avons donc obtenu les valeurs explicitement initialisées suivantes:


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

Selon le langage docopt, les éléments facultatifs sont spécifiés entre crochets. Par exemple [--speed=<kn>] . Les éléments obligatoires sont spécifiés entre parenthèses, mais avec une certaine condition. Par exemple (set|remove) définit l'exigence pour l'un d'eux. Si l'élément est spécifié sans crochets, par exemple naval_fate.jl --version , il indique que dans cette option d'exécution particulière, --version est une option requise.


La section suivante est la section de description des options. Cela commence par le mot «Options:»
Les options sont déclarées chacune sur une ligne distincte. Le remplissage à gauche du début de la ligne est important. Pour chaque option, vous pouvez spécifier le formulaire complet et court. Ainsi que la description de l'option affichée dans l'infobulle. Dans ce cas, les options -h | --help, --version -h | --help, --version automatiquement reconnus. docopt réponse est donnée par les arguments de la fonction docopt . Il est intéressant de considérer la déclaration:


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

Ici, le formulaire ...=<kn> définit la présence d'une valeur, et [default: 10] définit la valeur par défaut. Nous revenons aux valeurs obtenues en args :


 "--speed"=>"10" 

La différence fondamentale, par exemple, avec le package ArgParse, est que les valeurs ne sont pas saisies. C'est-à-dire que la default: 10 est définie comme la chaîne "10".
En ce qui concerne les autres arguments, qui sont présentés dans les arguments à la suite de l'analyse des arguments, vous devez faire attention à leurs valeurs:


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

Autrement dit, absolument tous les éléments de modèle spécifiés dans la déclaration docopt pour tous les cas d'utilisation sont présentés à la suite d'une analyse avec les noms d'origine. Tous les arguments facultatifs qui n'étaient pas présents sur la ligne de commande sont faux ici. Les arguments <x> , <y> également absents de la ligne de lancement et n'ont la valeur rien. D'autres arguments pour lesquels le modèle d'analyse correspondait ont reçu des valeurs vraies:


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

Et nous avons déjà reçu des valeurs spécifiques pour les éléments suivants du modèle:


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

La première valeur a été définie explicitement sur la ligne de commande comme substitution d'argument et la seconde était une option avec une valeur par défaut.

Notez également que le nom du script actuel peut être calculé automatiquement.
Par exemple, nous pouvons saisir:


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

Une recommandation supplémentaire pour placer l'analyseur d'argument de ligne de commande est de le placer au tout début du fichier. Une caractéristique désagréable de Julia en ce moment est une connexion assez longue des modules. Par exemple, en using Plots; using DataFrames using Plots; using DataFrames peut envoyer un script pour attendre quelques secondes. Ce n'est pas un problème pour les scripts à chargement unique côté serveur, mais cela ennuiera les utilisateurs qui veulent juste voir un indice pour les arguments de ligne de commande. C'est pourquoi, vous devez d'abord lancer l'aide et vérifier les arguments de la ligne de commande, et ensuite seulement, procéder au téléchargement des bibliothèques nécessaires au travail.


Conclusion


L'article ne prétend pas considérer toutes les méthodes d'analyse des arguments dans Julia. Cependant, les options envisagées couvrent en fait 3 options possibles. Analyse entièrement manuelle du réseau ARGS . Arguments strictement déclarés mais analysés automatiquement dans ArgParse. Et une forme pleinement déclarative, mais pas stricte, de docopt. Le choix du cas d'utilisation dépend entièrement de la complexité des arguments analysés. L'option utilisant docopt semble être la plus simple à utiliser, bien qu'elle nécessite une conversion de type explicite pour les valeurs des arguments reçus. Cependant, si le script n'accepte rien d'autre que le nom de fichier, il est tout à fait possible de tirer parti de l'aide sur celui-ci à l'aide de la fonction println("Run me with file name") habituelle, et d'analyser les noms de fichiers directement à partir d' ARGS comme indiqué. dans la première section.


Les références


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


All Articles