ソフトウェア開発に関して言えば、私たちが利用できるツールの大部分はコマンドライン アプリケーションです。 また、コマンドラインで使用されるツールの多くは、些細なことから退屈なことまで、達成できることが非常に強力であることも十分に注目に値します。 さらに言えば、コマンドラインアプリケーションを組み合わせて、一連の作業を連鎖的に行い、目的の出力を得ることも可能です。 既存のコマンドライン・コマンドを学習することにより、生産性を向上させ、どのような機能が手元にあり、どのようなタスクを自分で実装する必要があるのかをより理解することができます。 たとえば、このツールを使用するすべての人が同じオペレーティング システムと環境を使用する場合、完全なエコシステムを利用する上で最も柔軟性があります。 しかし、コマンドラインアプリを複数のオペレーティングシステムで動作するように実装する必要がある場合、使用できるツールをすべてのシステムで利用可能なものに制限することになります — あるいは、アプリにオペレーティングシステムごとの例外処理を実装しなければなりません。 (これは、調査やテストが非常に面倒です。)
多様な OS 環境を扱うために最もよく使われる解決策は、可能な限り OS 固有の外部ツールを避けるか、複数のアーキテクチャで動作する機能を実装するという難しい作業をすでに行っているライブラリにその責任を委ねるというものです。
Rubyにおけるコマンドラインアプリケーションの構成要素
コマンドラインアプリケーションを構築する際、入力、出力、およびその間のすべてという3つの領域について対処する必要があります。 単に入力やソースを受け取り、それを処理/フォーマットして情報を吐き出す単発のコマンドライン・ツールを開発するのであれば、作業はむしろ単純です。
入力の処理
入力の処理を開始するために、アプリケーションに与えることができるパラメータがあります。 これらの引数は、コマンドを開始する最も典型的な方法であり、どのように実行させたいかの詳細を示し、メニューシステムの必要性を回避するものである。 この例では:
ruby -e "puts RUBY_VERSION"
Ruby は実行されるコマンドライン プログラム、-e
はフラグと呼ばれ、"puts RUBY_VERSION"
はフラグに指定される値です。 この場合、Rubyのフラグ-e
は、以下をRubyのコードとして実行することを意味します。
引数入力
引数入力、またはパラメータとは、コマンドの後にスペースで区切って入力される余分なテキストを指します。 ほとんどすべてのコマンドは、ヘルプフラグの引数を許可している。 フラグ引数には前にダッシュが1つまたは2つ付いている。
標準的なヘルプフラグは -h
と --help
である。 MSDOS時代は/?
でした(今もそうかもしれません)。 Windowsでのフラグのトレンドが、フラグマーカーとしてフォワードスラッシュを使い続けているかどうかは知りませんが、最近のクロスプラットフォームのコマンドラインスクリプトはダッシュを使います。
Rubyでは、コマンドラインからの入力は2種類の変数に入れられるようになっています。 ARGV と ARGF です。 ARGVは入力パラメータを文字列の配列として扱い、ARGFはデータのストリームを扱うためのものです。 ARGVを直接パラメータに使うこともできますが、これは必要以上に手間がかかるかもしれません。
OptionParser
OptionParser は Ruby に含まれているため、外部依存がないという利点があります。 OptionParser は、どのようなコマンドラインオプションが利用可能かを表示し、その入力を好きな Ruby オブジェクトに処理する簡単な方法を提供します。 以下は、私のコマンドラインツール 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!
この例では、各 opts.on
ブロックに、コマンドラインで渡されたフラグの場合に実行するコードが記述されています。
最初のものは on
のパラメータの 1 つとして Array
が与えられているので、このフラグの入力は Ruby 配列に変換され、options
というハッシュに filters
というキーで格納されます。
OptionParser はデフォルトで -h
と --help
フラグも内蔵しているので、車輪を再発明する必要はありません。 上記のコマンドライン ツールに対して dfm -h
と入力するだけで、役立つ説明をうまく出力してくれます:
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 はコマンドライン定義を書き出すとき、少し冗長です。 Slopというgemは、より少ない労力で独自のコマンドライン解析が書けるようにするために設計されています。 フラグ定義のコードブロックを提供する代わりに、Slop は単にオブジェクトを作成し、アプリケーションでフラグが指定されたかどうか、どのような値が指定されたかを問い合わせることができるようにします。
# 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 }
この単純さは、コード ベースとテスト スイートを単純化し、開発時間を短縮するのに役立ちます。
出力 (STDOUT)
簡単なコマンド ライン ツールを書く場合、しばしば実行結果の出力を望むことがあります。 このツールのターゲットが何であるかを念頭に置くことにより、出力をどのように見せたいかが大きく決まります。
出力データを単純な文字列、リスト、ハッシュ、入れ子配列、JSON、XML、または消費される他の形式のデータとしてフォーマットすることができます。 データがネットワーク接続を介してストリーミングされる場合、データをきっちりした文字列にパックすることをお勧めします。
多くの既存の Linux/Mac コマンドライン ツールは、値のペアまたはセットで詳細を出力できます。 情報は、コロン、スペース、タブ、およびインデント ブロックで区切ることができます。 それが正確にどのように使用されるかもわからない場合、データを提示する最もシンプルで見栄えのする方法に進みます。
考慮すべき対象の一例として、API テスト ツールが挙げられます。 多くのAPIはJSONレスポンスを提供し、curl
のようなコマンドラインツールでアクセスすることができます。 帯域幅が気になる場合は、JSON の to_json
メソッドを使用しますが、ローカルマシンでの作業用であれば、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"# }# }
同様に、データ用に YAML を使用しても良いでしょう。
require 'yaml'puts x.to_yaml# ---# hello: world# :this:# apple: 4# :tastes: delicious
より複雑な出力をきれいにフォーマットさせたい場合、私は awesome_print gem の使用を強くお勧めします。 これは、出力時のプレゼンテーションの特定の制御を行います。
標準エラー (STDERR)
これは、コマンドライン アプリケーションから発生する可能性がある別の種類の出力です。 何かが間違っている、またはうまくいかないとき、コマンドライン・ツールはSTDERRとして知られる出力に書き込むのが通例です。 これは通常の出力として表示されますが、他のツールはコマンドが成功しなかったことを確認できます。
STDERR.puts "Oops! You broke it!"
より一般的な方法は、エラーに関する特定の詳細を書き込むために Logger を使用することです。 コマンドラインの STDERR 出力や、場合によってはログファイルにルーティングすることもできます。
ユーザーインターフェース (STDIN と STDOUT)
ユーザーインターフェースを書くことは、最も達成感のあることのひとつでしょう。 それは、いくつかの芸術的なデザインを適用し、ユーザーが対話するためのさまざまな方法を提供することができます。
最小のユーザー インターフェイスは、入力を待つプロンプトがあるテキストの表示です。
Who is your favorite super hero /Batman/Wonder-Woman ?
上記は質問、オプション、およびユーザーが何も入力せずにエンター キーを押すことを選択した場合のデフォルト オプションを示しています。 もし UI を書くのであれば、highline や tty のような gem を試してみることを強くお勧めします。
highline gem を使用すると、上記のように書くことができます:
require 'highline/import'favorite = ask("Who is your favorite hero Superman/Batman/Wonder Woman?") {|question| question.in = question.default = "Superman"}
ここで、highline は質問を表示し、デフォルトを表示し、不正なオプションを入力すると、ユーザーが提供されたオプションのいずれかを選択していないことを通知するようにキャッチします。 しかし、ここでもターゲットとするユーザーが誰であるかを考慮する必要があります。
提供する視覚的体験が多くなればなるほど、これらのツールのクロスプラットフォーム機能に注意を払う必要があります。 すべてのコマンド ラインが同じプレゼンテーション データを同じ方法で処理するわけではなく、悪いユーザー エクスペリエンスを生み出します。
Cross-platform compatibility
素晴らしいニュースは、Ruby には異なるオペレーティング システム間でのツーリングに必要なソリューションがたくさんあることです。 Ruby が任意の特定のオペレーティング システム用にコンパイルされると、RbConfig 用のソース ファイルは、ビルドされたシステムにネイティブな絶対値で生成され、ハッシュ形式で保存されます。
このファイルをテキストエディタで見るには、次の Ruby コードを実行します。
editor = "sublime" # your preferred editor hereexec "#{editor} #{RbConfig.method(:ruby).source_location}"
これですべてが見やすくなり、RbConfig::CONFIG
でハッシュを見るより良いと思います。 このハッシュには、ファイルシステムを操作するのに使ったコマンド、Ruby のバージョン、システムアーキテクチャ、Ruby にとって重要なものがどこに存在するか、などといった便利なものが含まれています。
オペレーティング システムを確認するには、次のようにします:
case RbConfig::CONFIGwhen /mingw32|mswin/ # Probably a Windows operating systemelse # Most likely a Unix compatible system like BSD/Mac/Linuxend
他のオペレーティング システム固有の値については、Gem::Platform のソース コードを見てください。
さて、ここに格納されているファイルシステム固有のコマンドは、このリソースから使用されるべきものではありません。 Ruby にはそのために Dir、File、Pathname というクラスが書かれています。
システムの PATH に実行ファイルが存在するかどうかを知る必要がある場合、MakeMakefile.find_executable を使いたいことでしょう。 Ruby は 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
コマンド ラインでビジュアル ウィンドウ メニューを描く場合、表示が更新されてもメニューが一定の位置にあることが好ましいです。 これを行う最も簡単な方法は、フルディスプレイの書き込みのたびにコマンドラインをクリアすることです。
上の例では、clear
コマンドを検索しています。 Windows では、コマンドラインをクリアするシェルコマンドは cls
ですが、これは command.com
の内部コードの一部なので、実行ファイルを見つけることはできません。
良いニュースは、Linux の clear
コマンドから出力される文字列をつかむと、このエスケープコード列が生成されるということです。 \e[3J\e[H\e[2J
. 私や他の人は Windows、Mac、Linux でこれをテストし、これはまさに私たちが望むことを行います。
Testing STDIN/STDOUT/STDERR
STDOUT や STDERR などに書き込むものでは、UI Ruby オブジェクトに内部変数を作成して、new
へのオプションのパラメータで変更するのが最も賢明でしょう。 つまり、普通にプログラムを実行すると、値はSTDOUT
になります。 しかし、テストを書くときには StringIO.new
を渡すので、簡単にテストができます。
Ruby の外で実行されるコマンドから IO を読み込もうとすると、テストは少し複雑になります。 各 IO ストリーム STDIN、STDOUT、STDERR を処理するために Open3.popen3 を調べる必要があるでしょう。
Using Bash commands
Bash is came to Windows (WSL)! これは、世界中で使用されているオペレーティング システムの大部分にわたって、bash コマンドにアクセスできることを意味します。
コマンドを互いに「パイプ」し、あるコマンドの出力をストリームとして次のコマンドに送るのは一般的なことです。
以下は、Bash の sed
コマンドが echo からパイプされた入力を受け取る正規表現置換の例です:
echo hello | sed "s/ll/ck n/g"
これは heck no
を出力します。 Bash について学べば学ぶほど、コマンドラインで物事を成し遂げることができるようになり、自分自身のより良いコマンドライン ツールを書くための準備が整います。
概要
自分自身のコマンドライン ツールを書くことになると、学ぶことがたくさんあります。 考慮しなければならない状況が多ければ多いほど、物事はより複雑になります。
手元にあるツールを学ぶのに時間をかければ、あなたが見逃していたとは知らなかった多くの報酬を手にすることができます。 あなたの成功を祈っています!
。