Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

25行のRubyコードでシェルを書く

LinuxまたはMacを使用している場合は、ターミナルを開くたびにシェルアプリケーションを使用しています。

シェルは、システムでコマンドを実行するのに役立つインターフェースです。

シェルは環境変数をホストし、コマンド履歴やオートコンプリートなどの便利な機能を備えています。

あなたが内部で物事がどのように機能するかを学ぶのが好きな人なら、この投稿はあなたにぴったりです!

シェルはどのように機能しますか?

独自のシェルアプリケーションを構築するために、シェルが実際に何であるかを考えてみましょう。

まず、プロンプトが表示され、通常は現在のユーザーや現在のディレクトリなどの追加情報が表示されます。次にコマンドを入力し、Enterキーを押すと結果が画面に表示されます。

ええ、それはかなり基本的なことのように聞こえますが、これは何かを思い出させませんか?

pryを考えている場合 そうです!

基本的に、オペレーティングシステム用のREPL(Read-Eval-Print-Loop)のシェル。

シェルの最初のバージョンを作成できることを知っている :

prompt = "> "

print prompt

while (input = gets.chomp)
  break if input == "exit"

  system(input)
  print prompt
end

これにより、最小限の機能的なシェルが得られます。他の多くのREPLのようなアプリケーションが使用するライブラリを使用することでこれを改善できます。

そのライブラリはReadlineと呼ばれます 。

Readlineライブラリの使用

ReadlineはRuby標準ライブラリの一部であるため、インストールするものはなく、 requireするだけです。

Readlineを使用する利点の1つ コマンド履歴を自動的に保持できるということです。

また、コマンドプロンプトなどの印刷も処理できます。

これがシェルのv2で、今回は Readlineを使用しています。 :

require 'readline'

while input = Readline.readline("> ", true)
  break if input == "exit"

  system(input)
end

これは素晴らしいことです。2つのputsを削除しました プロンプトのために、そして今、私たちは Readlineからいくつかの強力な機能にアクセスできます 。たとえば、キーボードショートカットを使用して単語を削除できます( CTRL + W )または履歴を検索する( CTRL + R )!

完全な履歴を印刷するための新しいコマンドを追加しましょう:

require 'readline'

while input = Readline.readline("> ", true)
  break                       if input == "exit"
  puts Readline::HISTORY.to_a if input == "hist"

  # Remove blank lines from history
  Readline::HISTORY.pop if input == ""

  system(input)
end

おもしろい事実:このコードをpryで試すと、pryのコマンド履歴が表示されます。その理由は、pryも Readlineを使用しているためです。 、および Readline ::HISTORY 共有状態です。

これで、 histと入力できます コマンド履歴を取得するには🙂

オートコンプリートの追加

お気に入りのシェルのオートコンプリート機能のおかげで、多くの入力を節約することができます。 Readlineを使用すると、この機能をシェルに簡単に統合できます。

履歴からコマンドを自動入力することから始めましょう。

comp = proc { |s| Readline::HISTORY.grep(/^#{Regexp.escape(s)}/) }

Readline.completion_append_character = " "
Readline.completion_proc = comp

## rest of the code goes here ##

このコードを使用すると、 を押すことで、以前に入力したコマンドをオートコンプリートできるはずです。 鍵。それでは、これをさらに一歩進めて、ディレクトリのオートコンプリートを追加しましょう。

comp = proc do |s|
  directory_list = Dir.glob("#{s}*")

  if directory_list.size > 0
    directory_list
  else
    Readline::HISTORY.grep(/^#{Regexp.escape(s)}/)
  end
end

complete_proc 可能な候補のリストを返します。この場合、 Dir.glob を使用して、入力した文字列がディレクトリ名の一部であるかどうかを確認する必要があります。 。残りはReadlineが担当します!

システムメソッドの実装

これで、履歴とオートコンプリートを備えた、25行のコードに対してそれほど悪くないシェルが機能するはずです🙂

しかし、私がもっと深く掘り下げたいことがあるので、実際にコマンドを実行する舞台裏で何が起こっているのかについての洞察を得ることができます。

これはsystemによって行われます メソッド、Cでは、このメソッドはコマンドを / bin / shに送信するだけです。 、これはシェルアプリケーションです。 / bin / shを実装する方法を見てみましょう Rubyで行います。

:これはLinux/Macでのみ機能します🙂

システム方式:

def system(command)
  fork {
    exec(command)
  }
end

ここで何が起こるかは、その fork 現在のプロセスの新しいコピーを作成し、このプロセスを execを介して実行するコマンドに置き換えます。 方法。これはLinuxプログラミングで非常に一般的なパターンです。

フォークしない場合は、現在のプロセスが置き換えられます。つまり、実行しているコマンドが( ls cd または他の何か)が行われると、Rubyプログラムはそれで終了します。

ここでそれが起こっているのを見ることができます:

def system(command)
  exec(command)
end

system('ls')

# This code will never run!
puts "after system"

結論

この投稿では、シェルがREPLのようなインターフェースであることを学びました( irb を考えてください) /こじ開け )システムと対話するため。また、強力な Readlineを使用して独自のシェルを構築する方法も学びました。 ライブラリ。履歴やオートコンプリートなどの多くの組み込み機能を提供します(ただし、それがどのように機能するかを定義する必要があります)。

その後、 forkについて学びました + exec Linuxプログラミングプロジェクトで一般的に使用されるパターン。

この投稿を楽しんだら、私にお願いして、Rubyのすべての友達と共有していただけませんか。それはブログの成長を助け、より多くの人々が学ぶことができるようになります🙂


  1. Test-Commit-Revert:Rubyでレガシーコードをテストするための便利なワークフロー

    それは私たち全員に起こります。ソフトウェアプロジェクトが成長するにつれて、コードベースの一部は、包括的なテストスイートなしで本番環境に移行します。数か月後に同じコード領域をもう一度見ると、理解するのが難しい場合があります。さらに悪いことに、バグがある可能性があり、どこから修正を開始すればよいかわかりません。 テストなしでコードを変更することは大きな課題です。プロセスで何かを壊すかどうかはわかりません。すべてを手動でチェックすることは、せいぜい間違いを犯しがちです。通常、それは不可能です。 この種のコードの処理は、開発者として実行する最も一般的なタスクの1つであり、以前の記事で説明した特性テ

  2. Rubyでの静的分析

    ソースコードを解析して、すべてのメソッド、それらが定義されている場所、およびそれらが取る引数を見つけたいとします。 どうすればこれができますか? あなたの最初のアイデアはそれのために正規表現を書くことかもしれません… しかし、もっと良い方法はありますか? はい! 静的分析 は、ソースコード自体から情報を抽出する必要がある場合に使用できる手法です。 これは、ソースコードをトークンに変換する(解析する)ことによって行われます。 さっそく始めましょう! パーサージェムの使用 Rubyには標準ライブラリで利用可能なパーサーがあります。名前はRipperです。出力を操作するのは難し