Julia. Script dan argumen baris perintah parsing


Kami terus berurusan dengan bahasa pemrograman Julia. Karena hanya perlu memiliki mode operasi batch untuk bahasa yang difokuskan pada analisis dan pemrosesan data, pertimbangkan fitur-fitur dari implementasi skrip dalam bahasa Julia dan meneruskan argumen dari baris perintah kepada mereka. Topik ini mungkin tampak basi bagi seseorang, tetapi, mengingat kebaruan dari bahasa tersebut, saya berharap bahwa gambaran kecil tentang metode untuk mengurai argumen baris perintah dan perpustakaan untuk ini, disajikan dalam Julia, masih akan berguna.


Untuk mulai dengan, beberapa kata tentang bagaimana naskah dibuat. Setiap skrip dimulai dengan garis dalam format khusus yang menunjukkan juru bahasa. Garis dimulai dengan urutan yang dikenal sebagai Shebang. Untuk Julia, baris ini adalah:


#!/usr/bin/env julia 

Tentu saja, Anda tidak dapat melakukan ini, tetapi kemudian Anda harus menjalankan skrip dengan perintah:


 julia .jl 

Selain itu, skrip apa pun harus diakhiri dengan karakter baris baru. Ini adalah persyaratan standar POSIX, yang mengikuti definisi string sebagai urutan karakter yang diakhiri oleh karakter baris baru.


Agar skrip dijalankan secara langsung, skrip harus memiliki atribut yang executable . Anda dapat menambahkan atribut seperti itu di terminal dengan perintah:


 chmod +x .jl 

Aturan-aturan ini berlaku untuk semua sistem operasi modern, kecuali, mungkin, MS Windows.


Arg Array


Mari kita beralih ke opsi pertama untuk melewatkan parameter. Argumen baris perintah tersedia dalam skrip Julia melalui konstanta array Base.ARGS. Mari kita siapkan skrip yang paling sederhana:


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

Script ini hanya mencetak ke konsol jenis dan isi array ARGS.


Sangat sering, nama file dilewatkan sebagai argumen baris perintah. Dan di sini ada kekhasan pemrosesan templat file yang diteruskan sebagai argumen. Misalnya, jalankan skrip kami menggunakan perintah ./args.jl *.jl dan dapatkan:


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

Dan sekarang mari kita ubah parameter baris perintah sedikit, mengelilingi topeng dengan tanda kutip:
./args.jl "*.jl" . Sebagai hasilnya, kita mendapatkan:


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

Kami melihat perbedaan yang jelas. Dalam kasus pertama, kami mendapat array dengan nama-nama semua file yang ada di direktori yang sama. Dalam kasus kedua, ini hanya topeng yang sama yang dilewatkan sebagai argumen ke baris perintah. Alasan untuk perilaku skrip yang berbeda ini adalah karena bash interpreter (dan juga yang dekat dengannya), dari mana skrip dijalankan, mengenali templat nama file. Lebih banyak dapat ditemukan di mesin pencari untuk "Pencocokan Pola Bash" atau "Kartu Liar Bash". Dan secara keseluruhan disebut Glob.


Di antara polanya, dimungkinkan untuk menutupi beberapa karakter - *, menutupi satu karakter -? .. Cari berdasarkan rentang [...], dan, bahkan, kemampuan untuk menentukan kombinasi kompleks:


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

Lihat Ringkasan Alat Baris Perintah GNU / Linux untuk informasi lebih lanjut.


Jika, karena alasan tertentu, kami tidak ingin menggunakan mekanisme gumpalan yang disediakan oleh bash, maka Anda dapat menemukan file berdasarkan mask dari skrip menggunakan paket Globs.jl.
Kode berikut mengonversi semua yang ditemukan dalam string argumen menjadi satu array nama file. Yaitu, terlepas dari apakah pengguna menentukan topeng dalam tanda kutip, tanpa tanda kutip, atau hanya mendaftar nama file yang ada atau tidak ada, hanya nama file aktual atau direktori akan tetap dalam array daftar filelist dihasilkan.


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

Contoh-contoh sederhana ini, pada kenyataannya, adalah demonstrasi penggunaan array ARGS, di mana programmer mengimplementasikan semua logika untuk parsing argumen. Pendekatan ini sering digunakan ketika serangkaian argumen sangat sederhana. Misalnya, daftar nama file. Atau satu atau dua opsi yang dapat ditangani oleh operasi string sederhana. Akses ke elemen ARGS sama dengan elemen array lainnya. Hanya ingat bahwa indeks elemen pertama array di Julia adalah 1.


Paket ArgParse.jl


Ini adalah alat yang fleksibel untuk menggambarkan atribut dan opsi dari baris perintah tanpa perlu menerapkan parsing logic.
Mari kita gunakan contoh yang sedikit dimodifikasi dari dokumentasi paket - 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() 

Jika kami menjalankan skrip ini tanpa argumen, kami mendapatkan output informasi referensi pada komposisi mereka:


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

Selain itu, dalam tanda kurung siku kita melihat argumen opsional. Sementara argumen yang ditandai sebagai arg1 (yaitu, apa yang kita gantikan) adalah wajib.


Jalankan lagi, tetapi tentukan atribut yang diperlukan 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 

Kita dapat melihat bahwa parsed_args adalah array asosiatif, di mana kuncinya adalah nama atribut menurut deklarasi yang dibuat dalam fungsi parse_commandline , dan nilainya adalah apa yang ditetapkan secara default atau diteruskan sebagai nilai argumen baris perintah. Selain itu, nilai-nilai dari tipe yang secara eksplisit ditentukan selama deklarasi.


Argumen @add_arg_table menggunakan makro @add_arg_table . Dimungkinkan untuk mendeklarasikan opsi:


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

Atau argumen


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

Selain itu, opsi dapat ditentukan menunjukkan bentuk penuh dan pendek (pada saat yang sama --opt2 dan -o ). Atau, hanya dalam satu bentuk. arg_type ditentukan di bidang arg_type . Nilai default dapat diatur menggunakan default = ... Alternatif untuk nilai default adalah dengan memerlukan argumen - required = true .
Dimungkinkan untuk mendeklarasikan tindakan otomatis, misalnya, menetapkan true atau false tergantung pada ada atau tidak adanya argumen. Ini dilakukan dengan menggunakan action = :store_true


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

Bidang help berisi teks yang akan ditampilkan pada prompt di baris perintah.
Jika saat startup kita menentukan semua atribut, maka kita mendapatkan:


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

Untuk debugging dari Atom / Juno IDE, di baris pertama skrip Anda dapat menambahkan kode berikut, agak kotor, tetapi berfungsi untuk menginisialisasi array ARGS.


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

Makro @__FILE__ adalah nama file tempat makro digunakan. Dan nama ini untuk REPL berbeda dari nama file program saat ini yang diperoleh melalui Base.source_path() . Tidak mungkin menginisialisasi Base.ARGS array Base.ARGS nilai yang berbeda, tetapi pada saat yang sama, Anda dapat menambahkan baris baru, karena array itu sendiri bukan konstanta. Array adalah kolom untuk Julia, jadi kami menggunakan vcat (vertical concatenate).


Namun, dalam pengaturan editor Juno Anda dapat mengatur argumen untuk menjalankan skrip. Tetapi mereka harus diubah setiap waktu untuk setiap skrip yang didebug secara terpisah.


Paket DocOpt.jl


Opsi ini merupakan implementasi dari pendekatan bahasa docopt markup - http://docopt.org/ . Gagasan utama dari bahasa ini adalah deskripsi deklaratif dari opsi dan argumen dalam bentuk, yang juga bisa menjadi deskripsi internal skrip. Bahasa template khusus digunakan.


Kami akan menggunakan contoh dari dokumentasi untuk paket ini 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 

Notasi doc = ... adalah penciptaan string doc Julia, yang berisi seluruh deklarasi untuk docopt. Hasil menjalankan pada baris perintah tanpa argumen adalah:


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

Jika kita menggunakan petunjuk dan mencoba untuk "membuat kapal baru", kita mendapatkan cetakan dari array args asosiatif, yang dihasilkan oleh hasil penguraian baris perintah


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

Fungsi docopt dinyatakan sebagai:


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

Argumen help , version , oprtions_first , exit_on_error menentukan perilaku parser perintah argumen secara default. Misalnya, jika terjadi kesalahan - untuk menyelesaikan eksekusi, pada permintaan versi, kembalikan nilai version=… diganti di sini, atas permintaan -h - bantuan masalah. options_first digunakan untuk menunjukkan bahwa opsi harus sebelum argumen posisi.


Sekarang, mari kita lihat lebih dekat bahasa deklaratif ini dan reaksi parser argumen terhadap nilai yang dimasukkan.


Deklarasi dimulai dengan teks arbitrer, yang, selain teks untuk baris perintah, dapat menjadi bagian dari dokumentasi skrip itu sendiri. Kata layanan "Penggunaan:" menyatakan pola penggunaan untuk skrip ini.


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

Argumen dideklarasikan dalam bentuk <name> , <x> , <y> . Perhatikan bahwa dalam array asosiatif args yang diperoleh sebelumnya, argumen ini bertindak sebagai kunci. Kami menggunakan formulir peluncuran ./docopt.jl ship new Bystriy , jadi kami mendapatkan nilai yang diinisialisasi secara eksplisit berikut:


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

Menurut bahasa docopt, elemen opsional ditentukan dalam tanda kurung. Misalnya [--speed=<kn>] . Elemen wajib ditentukan dalam tanda kurung, tetapi dengan kondisi tertentu. Sebagai contoh (set|remove) menetapkan persyaratan untuk salah satunya. Jika elemen ditentukan tanpa tanda kurung, misalnya naval_fate.jl --version , ia mengatakan bahwa dalam opsi run khusus ini, --version adalah opsi yang diperlukan.


Bagian selanjutnya adalah bagian deskripsi opsi. Dimulai dengan kata "Opsi:"
Opsi dideklarasikan masing-masing pada baris terpisah. Padding di sebelah kiri awal garis penting. Untuk setiap opsi, Anda dapat menentukan formulir lengkap dan pendek. Serta deskripsi opsi yang ditampilkan di tooltip. Dalam hal ini, opsi -h | --help, --version -h | --help, --version otomatis dikenali. Tanggapan mereka diberikan oleh argumen untuk fungsi docopt . Yang menarik untuk dipertimbangkan adalah deklarasi:


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

Di sini form ...=<kn> mendefinisikan keberadaan beberapa nilai, dan [default: 10] mendefinisikan nilai default. Kami kembali ke nilai yang diperoleh dalam args :


 "--speed"=>"10" 

Perbedaan mendasar, misalnya, dari paket ArgParse, adalah bahwa nilainya tidak diketik. Artinya, nilai default: 10 ditetapkan sebagai string "10".
Adapun argumen lain, yang disajikan dalam args sebagai hasil dari parsing argumen, Anda harus memperhatikan nilainya:


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

Artinya, benar-benar semua elemen template yang ditentukan dalam deklarasi docopt untuk semua kasus penggunaan disajikan sebagai hasil analisis dengan nama asli. Semua argumen opsional yang tidak ada pada baris perintah salah di sini. Argumen <x> , <y> juga hilang dari baris peluncuran dan tidak memiliki nilai apa pun. Argumen lain yang cocok dengan pola parsing menerima nilai sebenarnya:


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

Dan kami telah menerima nilai spesifik untuk elemen templat berikut:


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

Nilai pertama ditetapkan secara eksplisit pada baris perintah sebagai substitusi argumen, dan yang kedua adalah opsi dengan nilai default.

Perhatikan juga bahwa nama skrip saat ini dapat dihitung secara otomatis.
Misalnya, kita bisa masuk:


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

Rekomendasi tambahan untuk menempatkan parser argumen baris perintah adalah menempatkannya di awal file. Fitur yang tidak menyenangkan dari Julia saat ini adalah koneksi modul yang agak panjang. Misalnya using Plots; using DataFrames using Plots; using DataFrames dapat mengirim skrip untuk menunggu beberapa detik. Ini bukan masalah untuk skrip sisi-server, beban-tunggal, tetapi akan mengganggu pengguna yang hanya ingin melihat petunjuk untuk argumen baris perintah. Itulah sebabnya, pertama Anda perlu mengeluarkan bantuan dan memeriksa argumen baris perintah, dan baru kemudian, lanjutkan untuk mengunduh perpustakaan yang diperlukan untuk pekerjaan tersebut.


Kesimpulan


Artikel itu tidak berpura-pura mempertimbangkan semua metode penguraian argumen dalam Julia. Namun, opsi yang dipertimbangkan, pada kenyataannya, mencakup 3 opsi yang mungkin. Analisis manual array ARGS . Argumen yang dideklarasikan secara ketat tetapi secara otomatis di ArgParse. Dan bentuk docopt sepenuhnya deklaratif, meskipun tidak ketat. Pilihan use case tergantung sepenuhnya pada kompleksitas argumen yang diuraikan. Opsi menggunakan docopt tampaknya yang paling mudah digunakan, meskipun membutuhkan konversi jenis eksplisit untuk nilai-nilai argumen yang diterima. Namun, jika skrip tidak menerima apa pun selain nama file, maka sangat mungkin untuk mengambil keuntungan dari mengeluarkan bantuan di atasnya menggunakan fungsi println("Run me with file name") biasa println("Run me with file name") , dan parsing nama file langsung dari ARGS seperti yang ditunjukkan di bagian pertama.


Referensi


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


All Articles