Yarsh

Yet Another Ruby SHell is a mix of Ruby and standard Shell REPL. Priority is given to Ruby but if the variable or method is not found or if it is a syntax error, it will be passed to the default shell environment. It enable the best of both worlds, the power of ruby and the simplicity of the standard Shell.

This project was made just for fun, to remember the basis of Ruby and to discover new features. To provide a good user experience, it uses reline under the hood which already implement all we want for a REPL: History, completion, etc.

I wanted a simple version but already working version. If I have time, more features will come. For example, non interactive with a file as argument. I dream of a shell syntax we can do:

def install_rails
  sudo apt update
  sudo apt install ruby
  gem install rails
end

install_rails

And this is now a reality: multi-line editing is supported, and shell commands work inside Ruby blocks, defs, and classes (see Multiline).

I developed this project half of the time offline, another part with the help of AI. It was a way for me to try to develop with AI. I used opencode with DeepSeek V4 Flash Free.

install

Run gem install yarsh

Usage

yarsh start the shell.

Features

Use it as a normal shell, except that you can use Ruby commands and methods. For example:

puts "Hello World! We can use Ruby or standard shell:"
Dir["**/*.rb"].each { |file| puts file }
puts "Or shell like:"
find . -name  "*.rb" -print

For the moment, it display ruby evaluation results with pretty_print.

Special keyword for mixing

You can also mix both with the special keyword -->. It will pass to ruby the result of the shell command to use in Ruby. For example:

ls -al . --> lines.each { |file| puts file }

the result of the exectued shell command is ExecOutput a subclass of String with some extra methods. For the moment, it has:

  • #lines
  • #words

You want to store the result of the ruby evaluation after a shell command? Use -->(varible_name). For example:

ls -al . -->(sorted) lines.sort {|a, b| a.length <=> b.length  }
sorted.each { |file| puts "What ever you wanna do" }

Multiline

Multi-line input is supported: the REPL keeps reading while the buffer is syntactically incomplete (missing end, unterminated string/heredoc/regexp, trailing operator, ...) and submits when the syntax is complete or on an empty line. Continuation lines get a > prompt.

The best part: you can mix Ruby and shell lines in the same block, def, or class:

def install_rails
  sudo apt update
  sudo apt install ruby
  gem install rails
end
Dir['*.rb'].each do |f|
  ls -l
  puts "Processed: #{f}"
end

The whole buffer is evaluated in the shared REPL binding, so methods, variables, and shell side-effects persist between lines and across inputs. Shell lines are executed via the usual fallback (they are evaluated as Ruby first, then as a shell command). Heredoc bodies are kept verbatim:

x = <<~EOF
  not evaluated, just a string
EOF

Known limitations:

  • Pure-bash multiline (e.g. bash if ...; then) is not supported; shell lines only work inside Ruby structures.
  • A Ruby expression that raises a real NameError inside a block may be executed as a shell command instead of being reported.

Completion

For the moment, there is autocompletion for exectuables in PATH and for file and dir path for shell commands

Errors

$rberr are set for every catched Ruby error.

$sherr are set for every catched Shell error.

Configuration

You can create a file named config in ~/.yarsh. It is plain ruby, for now, you can define aliases for shell commands, define methods that will be available in the REPL, configure the prompt looks like and some logging. Here is an example file:


sh_alias 'ls', 'exa -l --icons=always'
sh_alias 'll', 'ls -a' # Will be expanded to exa -l --icons=always -a

configure do |c|
  c.prompt = :powerline # To available prompt: :powerline or :basic
  # Or define how your own logic for the prompt. Should return a string
  c.prompt = Proc.new do
    "hello> "
  end
  c.log_level = Logger::DEBUG # Lot of unuseful lines
end

# Will be available in the REPL once the related TODO is completed
def my_awsome_method
  puts "Hello World"
end

Development

After checking out the repo, run bin/setup to install dependencies. Then, run bundle exec rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/facenord-sud/yarsh.