Pokud jde o vývoj softwaru, většinu nástrojů, které máme k dispozici, tvoří aplikace příkazového řádku. Stojí také za povšimnutí, že mnohé z nástrojů používaných na příkazovém řádku jsou poměrně výkonné v tom, čeho mohou dosáhnout, od triviálních až po nudné. Dále můžete kombinovat aplikace příkazového řádku a řetězit tak posloupnost prací, abyste dosáhli požadovaného výstupu. Naučit se existující příkazy příkazového řádku vám umožní zvýšit produktivitu a lépe pochopit, jaké možnosti máte k dispozici a jaké úkoly možná budete muset realizovat sami.
Při návrhu nástroje příkazového řádku musíte skutečně věnovat pozornost tomu, kdo nebo co je vaší cílovou skupinou. Pokud například všichni lidé, kteří budou tento nástroj používat, používají stejný operační systém a prostředí, pak máte největší možnost využít celý ekosystém. Pokud však potřebujete implementovat aplikaci příkazového řádku tak, aby fungovala ve více operačních systémech, omezili jste nyní nástroje, které můžete používat, na ty, které jsou dostupné ve všech těchto systémech – nebo musíte do své aplikace implementovat výjimky pro jednotlivé operační systémy. (To může být velmi zdlouhavé při zkoumání a testování.)
Řešením, které se nejčastěji používá pro práci s různými prostředími operačních systémů, je pokud možno vyhnout se jakýmkoli externím nástrojům specifickým pro daný operační systém nebo delegovat tuto odpovědnost na knihovny, které již odvedly těžkou práci při implementaci funkcí pro práci s více architekturami. Čím více se vám podaří omezit implementaci na jedno jádro jazyka bez závislosti na externích závislostech, tím snazší bude údržba projektu.
Složky aplikace příkazového řádku v jazyce Ruby
Při vytváření aplikace příkazového řádku je třeba se zabývat třemi oblastmi: vstupem, výstupem a vším mezi nimi. Pokud vyvíjíte jednorázový nástroj příkazového řádku, který jednoduše přijme vstup nebo zdroj, zpracuje/formátuje jej a vyplivne informace, pak je vaše práce poměrně jednoduchá. Pokud však vyvíjíte uživatelské rozhraní s nabídkami, ve kterých se můžete pohybovat, začíná to být složitější.
Zpracování vstupu
Pro zahájení zpracování vstupu máme k dispozici parametry, které lze aplikaci zadat. Tyto argumenty jsou nejtypičtějším způsobem spuštění příkazu s podrobnostmi o tom, jak jej chcete provést, a vyhnete se tak nutnosti používat systém nabídek. V tomto příkladu:
ruby -e "puts RUBY_VERSION"
Ruby je spouštěný program příkazového řádku, -e
se nazývá příznak a "puts RUBY_VERSION"
je hodnota zadaná pro příznak. V tomto případě příznak -e
pro Ruby znamená, že se následující příkaz spustí jako kód Ruby. Když spustím výše uvedený příkaz na příkazovém řádku, dostanu zpět 2.4.0
vypsaný na standardní výstup (který se jednoduše zobrazí na dalším řádku).
Vstupní argument
Vstupní argument neboli parametry jsou všechny další kousky textu zadané za příkazem a oddělené mezerou. Téměř všechny příkazy umožňují zadat argument s příznakem nápovědy. Příznakový argument má před sebou pomlčku nebo dvě pomlčky.
Standardní pomocné příznaky jsou -h
, a --help
. V dobách MSDOS to byl (a možná stále je) /?
. Nevím, jestli se trend příznaků ve Windows udržel a používá se jako značka příznaku lomítko vpřed, ale v dnešních multiplatformních skriptech příkazového řádku se používají pomlčky.
V Ruby se vstup z příkazového řádku umisťuje do dvou různých proměnných: ARGV a ARGF. ARGV zpracovává vstupní parametry jako pole řetězců; ARGF slouží ke zpracování datových proudů. Pro parametry můžete použít přímo proměnnou ARGV, ale to může být více práce, než je nutné. Pro práci s argumenty příkazového řádku je v Ruby vytvořeno několik knihoven:
OptionParser
OptionParser má tu výhodu, že je součástí jazyka Ruby, takže není externí závislostí. OptionParser poskytuje snadný způsob, jak jednak zobrazit, jaké možnosti příkazového řádku jsou k dispozici, jednak zpracovat vstup do libovolného objektu jazyka Ruby. Zde je výňatek z jednoho z mých nástrojů příkazového řádku dfm:
require 'optionparser'options = {}printers = Array.newOptionParser.new do |opts| opts.banner = "Usage: dfm \nDefaults: dfm -xd ." + File::SEPARATOR opts.on("-f", "--filters FILTERS", Array, "File extension filters") do |filters| options = filters end opts.on("-x", "--duplicates-hex", "Prints duplicate files by MD5 hexdigest") do |dh| printers << "dh" end opts.on("-d", "--duplicates-name", "Prints duplicate files by file name") do |dh| printers << "dn" end opts.on("-s", "--singles-hex", "Prints non-duplicate files by MD5 hexdigest") do |dh| printers << "sh" end opts.on("-n", "--singles-name", "Prints non-duplicate files by file name") do |dh| printers << "sn" endend.parse!
V tomto příkladu má každý blok opts.on
kód, který se má provést, pokud byl příznak předán na příkazovém řádku. Spodní čtyři možnosti (v rámci svého bloku) jednoduše připojují informace o příznaku do pole, abych je mohl později použít.
První z nich má jako jeden z parametrů metody on
uveden Array
, takže vstup pro tento příznak bude převeden na pole Ruby a uložen do mého hashe s názvem options
pod klíčem filters
.
Zbytek parametrů uvedených v metodě on
jsou údaje o příznaku a popis. K provedení kódu uvedeného v následujícím bloku budou fungovat jak krátké, tak dlouhé příznaky.
OptionParser má ve výchozím nastavení zabudované také příznaky -h
a --help
, takže nemusíte znovu vynalézat kolo. Stačí zadat dfm -h
pro výše uvedený nástroj příkazového řádku a ten pěkně vypíše užitečný popis:
Usage: dfm Defaults: dfm -xd ./ -f, --filters FILTERS File extension filters -x, --duplicates-hex Prints duplicate files by MD5 hexdigest -d, --duplicates-name Prints duplicate files by file name -s, --singles-hex Prints non-duplicate files by MD5 hexdigest -n, --singles-name Prints non-duplicate files by file name
Slop
OptionParser je trochu upovídaný, pokud jde o vypisování definic příkazového řádku. Drahokam Slop je navržen tak, aby vám umožnil psát vlastní parsování příkazového řádku s mnohem menším úsilím. Místo toho, abyste museli zadávat bloky kódu v definici příznaku, Slop jednoduše vytvoří objekt, na který se můžete ve své aplikaci zeptat, zda byl příznak zadán a jaká hodnota (hodnoty) pro něj mohla být zadána.
# Excerpt from https://github.com/leejarvis/slopopts = Slop.parse do |o| o.string '-h', '--host', 'a hostname' o.integer '--port', 'custom port', default: 80 o.bool '-v', '--verbose', 'enable verbose mode' o.bool '-q', '--quiet', 'suppress output (quiet mode)' o.bool '-c', '--check-ssl-certificate', 'check SSL certificate for host' o.on '--version', 'print the version' do puts Slop::VERSION exit endendARGV #=> -v --host 192.168.0.1 --check-ssl-certificateopts #=> 192.168.0.1opts.verbose? #=> trueopts.quiet? #=> falseopts.check_ssl_certificate? #=> trueopts.to_hash #=> { host: "192.168.0.1", port: 80, verbose: true, quiet: false, check_ssl_certificate: true }
Tato jednoduchost může pomoci zjednodušit vaši kódovou základnu a sadu testů a také urychlit dobu vývoje.
Výstup (STDOUT)
Při psaní jednoduchého nástroje příkazového řádku často chcete, aby vypisoval výsledky provedené práce. Mějte na paměti, co je cílem tohoto nástroje, a to do značné míry určí, jak má výstup vypadat.
Výstupní data můžete formátovat jako jednoduchý řetězec, seznam, hash, vnořené pole, JSON, XML nebo jinou formu dat, která se budou konzumovat. Pokud budou vaše data streamována přes síťové připojení, pak budete chtít data zabalit do pevně zabaleného řetězce dat. Pokud je mají vidět oči uživatele, pak je budete chtít rozbalit reprezentativním způsobem.
Mnoho existujících nástrojů příkazového řádku pro Linux/Mac umí vypisovat údaje v párech nebo sadách hodnot. Informace mohou být odděleny dvojtečkou, mezerami, tabulátory a bloky odsazení. Pokud si nejste jisti, jak přesně mohou být použity, zvolte nejjednodušší a nejprezentativnější způsob prezentace dat.
Jedním z příkladů cíle, který může být třeba zvážit, je nástroj pro testování API. Mnoho rozhraní API poskytuje odpověď ve formátu JSON a lze k nim přistupovat pomocí nástroje příkazového řádku, například curl
. Pokud vám záleží na šířce pásma, použijte metodu JSON to_json
, ale pokud je určena pro práci na lokálním stroji, použijte pretty_generate
.
x = {"hello" => "world", this: {"apple" => 4, tastes: "delicious"}}require 'json'puts x.to_json# {"hello":"world","this":{"apple":4,"tastes":"delicious"}}puts JSON.pretty_generate( x )# {# "hello": "world",# "this": {# "apple": 4,# "tastes": "delicious"# }# }
Podobně můžete pro data použít YAML.
require 'yaml'puts x.to_yaml# ---# hello: world# :this:# apple: 4# :tastes: delicious
Pokud chcete mít složitější výstup pěkně formátovaný, pak vřele doporučuji použít drahokam awesome_print. Ten vám poskytne konkrétní kontrolu nad prezentací na výstupu.
Standardní chyba (STDERR)
Jedná se o další druh výstupu, který může nastat u aplikací příkazového řádku. Když je něco špatně nebo se něco pokazilo, je obvyklé, že nástroj příkazového řádku zapisuje na výstup známý jako STDERR. Zobrazí se jako běžný výstup, ale jiné nástroje mohou ověřit, že příkaz nebyl úspěšný.
STDERR.puts "Oops! You broke it!"
Běžnější praxí je použití loggeru pro zápis konkrétních podrobností o chybách. Ty můžete směrovat na výstup STDERR na příkazovém řádku nebo případně do souboru protokolu.
Uživatelské rozhraní (STDIN a STDOUT)
Napsání uživatelského rozhraní může být jednou z nejpřínosnějších věcí. Umožňuje vám uplatnit některé umělecké návrhy a poskytnout uživateli různé způsoby interakce.
Minimální uživatelské rozhraní je zobrazení nějakého textu s výzvou čekající na vstup. Může vypadat takto jednoduše:
Who is your favorite super hero /Batman/Wonder-Woman ?
Výše je uvedena otázka, možnosti a výchozí možnost pro případ, že se uživatel rozhodne stisknout klávesu enter, aniž by cokoli zadal. V čistém jazyce Ruby by to vypadalo takto:
favorite = "Superman"printf "Who is your favorite hero /Batman/Wonder Woman?"input = gets.chompfavorite = input unless input.empty?
To je velmi hrubý kód, který se používá pro vstup a výstup, a pokud se chystáte psát uživatelské rozhraní, vřele doporučuji vyzkoušet drahokam jako highline nebo tty.
Pomocí drahokamu highline byste výše uvedené mohli napsat takto:
require 'highline/import'favorite = ask("Who is your favorite hero Superman/Batman/Wonder Woman?") {|question| question.in = question.default = "Superman"}
Tady highline zobrazí otázku, zobrazí výchozí možnost a zachytí všechny nesprávně zadané možnosti a upozorní uživatele, že nevybral jednu z nabízených možností.
Díky highline i tty můžete pro nabídku a uživatelské prostředí udělat obrovské množství dalších věcí s příjemnými funkcemi, jako je například přidání barev na displej. Opět však musíte vzít v úvahu, kdo je vaší cílovou skupinou.
Čím více vizuálního zážitku poskytujete, tím více musíte věnovat pozornost multiplatformním možnostem těchto nástrojů. Ne všechny příkazové řádky zpracovávají stejná prezentační data stejným způsobem, což vytváří špatný uživatelský zážitek.
Kompatibilita napříč platformami
Skvělou zprávou je, že Ruby má mnoho řešení potřebných pro nástroje napříč různými operačními systémy. Při kompilaci jazyka Ruby pro libovolný konkrétní operační systém se vygeneruje zdrojový soubor pro RbConfig s absolutními hodnotami nativními pro systém, na kterém byl sestaven, uloženými ve formátu hash. Je to tedy klíč k detekci a využití vlastností operačního systému.
Chcete-li si tento soubor prohlédnout ve svém oblíbeném textovém editoru, můžete spustit následující kód Ruby:
editor = "sublime" # your preferred editor hereexec "#{editor} #{RbConfig.method(:ruby).source_location}"
Tím se vám vše zobrazí v reprezentativní podobě, což je podle mého názoru lepší než zobrazení hashe prostřednictvím RbConfig::CONFIG
. Tento hash obsahuje užitečné věci, jako jsou příkazy používané pro práci se souborovým systémem, verze Ruby, architektura systému, kde se nachází vše důležité pro Ruby a další podobné věci.
Pro kontrolu operačního systému můžete udělat:
case RbConfig::CONFIGwhen /mingw32|mswin/ # Probably a Windows operating systemelse # Most likely a Unix compatible system like BSD/Mac/Linuxend
Pro další hodnoty specifické pro operační systém se můžete podívat do zdrojového kódu Gem::Platform.
No, příkazy specifické pro souborový systém zde uložené nejsou určeny k použití z tohoto zdroje. Ruby má k tomu napsané třídy Dir, File a Pathname.
Pokud potřebujete zjistit, zda v systémovém PATH existuje spustitelný soubor, pak budete chtít použít MakeMakefile.find_executable. Ruby podporuje sestavování rozšíření C a jednou z pěkných funkcí, které kvůli tomu přidali, je tato možnost zjistit, zda existuje spustitelný soubor, který chcete zavolat.
Při každém spuštění to ale vygeneruje logovací soubor v aktuálním adresáři systému. Abyste se tedy vyhnuli vypisování tohoto souboru protokolu, budete muset udělat následující:
require 'mkmf'MakeMakefile::Logging.instance_variable_set(:@log, File.open(File::NULL, 'w'))executable = MakeMakefile.find_executable('clear') # or whatever executable you're looking for
Pokud kreslíte vizuální okenní nabídku na příkazovém řádku, je žádoucí, aby nabídka zůstala při aktualizaci zobrazení na pevném místě. Nejjednodušší způsob, jak toho dosáhnout, je vymazat příkazový řádek mezi každým zápisem celého zobrazení.
V uvedeném příkladu jsem hledal příkaz clear
. V systému Windows je příkaz shellu pro vymazání příkazového řádku cls
, ale nenajdete pro něj spustitelný příkaz, protože je součástí interního kódu command.com
.
Dobrá zpráva je, že zachycení řetězcového výstupu z příkazu clear
v systému Linux vytváří tuto sekvenci escape kódu: \e[3J\e[H\e[2J
. Já i ostatní jsme to testovali ve Windows, Macu i Linuxu a dělá to přesně to, co chceme: vymazání obrazovky pro překreslení na ní.
Tento escape řetězec má tři různé akce, které provádí:
CLEAR = (ERASE_SCOLLBACK = "\e[3J") + (CURSOR_HOME = "\e[H") + (ERASE_DISPLAY = "\e[2J")
Je však vhodnější použít knihovnu příkazového řádku než psát escape kódy sami. Tento však stojí za zmínku.
Testování STDIN/STDOUT/STDERR
U všeho, co zapisuje na něco jako STDOUT nebo STDERR, by bylo nejrozumnější vytvořit vnitřní proměnnou v objektu UI Ruby, kterou lze měnit pomocí volitelného parametru new
. Takže když váš program normálně běží, hodnota bude STDOUT
. Při psaní testů však budete předávat StringIO.new
, proti kterému můžete snadno testovat.
Pokud se snažíte číst IO z příkazu spuštěného mimo Ruby, je testování trochu složitější. Pravděpodobně se budete muset podívat na Open3.popen3, který zpracovává jednotlivé IO proudy STDIN, STDOUT a STDERR.
Používání příkazů Bash
Bash přišel do Windows (WSL)! To znamená, že máte přístup k příkazům bash v naprosté většině operačních systémů používaných po celém světě. Tím se vám otevírají další možnosti pro vlastní nástroj příkazového řádku.
Typické je vzájemné propojování příkazů „rourou“, kdy se výstup jednoho příkazu posílá jako proud do dalšího příkazu. S tímto vědomím můžete zvážit přidání podpory proudového vstupu nebo přemýšlet o tom, jak lépe formátovat výstup pro konzumaci jinými nástroji příkazového řádku.
Uveďme si příklad regexové substituce s příkazem Bash sed
, který dostává svůj vstup potrubím z příkazu echo:
echo hello | sed "s/ll/ck n/g"
Tímto výstupem je heck no
. Čím více se naučíte o jazyku Bash, tím lépe vám bude umožněno provádět činnosti na příkazovém řádku a tím lépe budete vybaveni pro psaní vlastních lepších nástrojů příkazového řádku.
Shrnutí
Při psaní vlastních nástrojů příkazového řádku se toho můžete naučit hodně. Čím více situací musíte zohlednit, tím složitější věci se stávají.
Věnujte čas učení se nástrojům, které máte k dispozici, a sklidíte mnoho odměn, o kterých jste nevěděli, že vám chybí. Přeji vám vše nejlepší!