Rubish: Bridging the Gap Between Bash and Ruby
For decades, the Unix shell has been the primary interface for system administration and automation. While Bash is the industry standard, its syntax can become cumbersome for complex logic, leading developers to often jump between shell scripts and higher-level languages like Python or Ruby. Enter Rubish, a Unix shell written in pure Ruby that seeks to provide the best of both worlds: full Bash compatibility and deep Ruby integration.
Rubish is not merely a shell implemented using Ruby; it is a hybrid environment where shell syntax is parsed and compiled into Ruby code, then executed by the Ruby VM. This architecture allows users to run existing Bash scripts without modification while gaining the ability to seamlessly mix shell commands with Ruby's powerful object-oriented features.
The Hybrid Experience: Bash Compatibility Meets Ruby Power
At its core, Rubish maintains a strict commitment to Bash compatibility. The project aims to support all Bash features, treating any incompatible script as a bug. This ensures that users don't have to abandon their existing toolchains to experiment with the new shell.
However, the real value of Rubish lies in its "features beyond Bash," which allow developers to break out of traditional shell constraints:
Ruby-Powered Logic and Iteration
Instead of wrestling with complex shell conditionals, Rubish allows the use of Ruby expressions within if, while, and until blocks by wrapping them in curly braces. Shell variables are automatically bound as local variables within these expressions:
COUNT=5
if { count.to_i > 3 }
echo 'count is greater than 3'
end
Furthermore, Rubish introduces Ruby iterator blocks (.each, .map, .select), enabling line-by-line processing of command output with the elegance of a Ruby collection:
ls.select { it.end_with?('.rb') }.each { |f| puts f.upcase }
A New Way to Chain Commands
While the traditional pipe (|) remains available, Rubish introduces method chaining via dot notation. By opening a command with parentheses, users can chain operations as if they were Ruby methods:
# Equivalent to: cat file.txt | grep error
cat(file.txt).grep(/error/)
Inline Ruby Evaluation
For those who use the shell as a REPL, Rubish allows any line starting with a capital letter to be evaluated as direct Ruby code. This removes the need for special escape characters when performing quick calculations or querying the Ruby standard library:
rubish$ Time.now
=> 2025-01-01 12:00:00 +0900
rubish$ Dir.glob('*.rb').sort
=> ["Gemfile", "Rakefile"]
Advanced Shell Engineering
Rubish includes several sophisticated features designed for the modern developer's workflow:
- Lazy Loading: To solve the problem of slow shell startup times caused by tools like
rbenvornvm, Rubish provides alazy_loadblock. This defers initialization to a background thread, ensuring the prompt appears instantly while the environment configures itself in the background. - Custom Programmatic Prompts: Instead of static strings, prompts can be defined as Ruby functions, allowing for dynamic content (like current Git branches) to be rendered programmatically on every prompt refresh.
- Restricted Mode: For security,
rubish -rdisables all Ruby integration, allowing the execution of untrusted scripts using only standard shell syntax. - Zsh Compatibility: Beyond Bash, Rubish incorporates Zsh-style features, including
setopt,compinit, and abbreviated path expansion (e.g.,a/c/a<Tab>expanding to a full path).
Developer Perspectives and Trade-offs
The project has sparked interesting discussions within the technical community, particularly regarding the trade-offs of such a deep integration.
The "Vibe-Coding" Debate
Some contributors have noted the influence of LLMs in the project's development. One commenter, @ciconia, expressed concern over the "impenetrable" nature of some of the code, questioning whether the use of coding agents leads to methods that are too long or lack clear interface boundaries, potentially making it harder for human developers to contribute without similar tools.
Performance and Portability
As with any shell written in a high-level language, performance is a point of contention. While some users noted that Ruby is faster than Python, others questioned how Rubish compares to minimal shells like the Almquist shell. There is also the practical hurdle of remote environments; as @freedomben pointed out, the necessity of having a Ruby runtime installed on every remote server makes Rubish less portable than a statically linked binary shell.
The Appeal of "Objects in the Shell"
Despite the critiques, there is a strong appetite for this approach. Users coming from PowerShell have noted that the desire for "Bash but with objects" is a common craving, and the ability to use method_missing to handle shell commands dynamically is seen as a perfect application of Ruby's meta-programming strengths.
Embedding Rubish
One of the most powerful aspects of Rubish is its public API. Because it is written in Ruby, it can be embedded into other Ruby programs without the need for fork+exec or JSON serialization. This allows IDE plugins or terminal emulators (like the sibling project Echoes) to drive a Rubish session in-process, providing advanced syntax highlighting and programmatic control over command execution.