Ruby as a Bash replacement: Writing powerful system scripts in Ruby

Ever found yourself wrestling with complex bash scripts? You know, the ones with cryptic syntax, inconsistent behavior across systems, and error handling that feels like an afterthought? Well, there's a better way. Ruby, that elegant language known for web development, can actually be an amazing tool for system scripting tasks. In this article, I'll show you how Ruby can replace bash for many of your automation needs, with cleaner code and more powerful features.

Why consider Ruby for shell scripting?

Bash is ubiquitous and powerful, but let's be honest - it can be a pain to work with. As scripts grow in complexity, they often become hard to maintain. Variables behave strangely, error handling is clunky, and testing is difficult. Ruby offers an elegant alternative that feels natural if you're already familiar with modern programming languages.

Ruby gives you the power of a full programming language with clean syntax, excellent string manipulation, robust error handling, and a massive ecosystem of libraries. It's also available on most Unix-like systems by default, making it a practical choice for scripting.

Ruby's advantages over bash

Ruby brings several significant advantages when compared to bash:

  1. Readable syntax: Ruby code is clean and expressive, making it easier to understand and maintain.
  2. Object-oriented approach: Everything in Ruby is an object, giving you powerful abstraction capabilities.
  3. Rich standard library: Ruby comes with batteries included for file operations, networking, and more.
  4. Powerful string manipulation: Regular expressions and string methods are first-class citizens.
  5. Better error handling: Exceptions in Ruby are more robust than bash's error handling.
  6. Testability: You can use testing frameworks like RSpec for your scripts.

Getting started with Ruby for system tasks

To use Ruby for system scripting, you don't need much setup. Just make sure Ruby is installed (which it often is by default on macOS and many Linux distributions), and you're ready to go.

For a Ruby script to be executable like a bash script, add a shebang line at the top:

#!/usr/bin/env ruby

puts "Hello from a Ruby script!"

Make it executable with chmod +x script.rb and you can run it like any bash script: ./script.rb

Example: Bulk folder compression

Let's dive into a practical example. Say you need to compress multiple project folders, but want to skip certain ones. In bash, this might involve some awkward looping and conditionals. In Ruby, it's clean and straightforward:

#!/usr/bin/env ruby

# Define the directory containing folders to compress
source_dir = "/home/kamil/projects"

# Define folder names to skip
skip_folders = ['2024', '2025']

# Collect all directories in the source directory
folders = Dir.glob('*', base: source_dir).select { |f| File.directory?(File.join(source_dir, f)) }

# Change to the source directory
Dir.chdir(source_dir)

folders.each do |folder|
  # Skip folders in the skip_folders list
  if skip_folders.include?(folder)
    puts "Skipping folder '#{folder}' as requested"
    next
  end

  # Define output zip filename
  output_file = "#{folder}.zip"

  # Use zip command to compress the folder
  `zip -r -q #{output_file} #{folder}`

  puts "Compressed #{folder} into #{output_file}"
end

Let's break down what's happening here:

  • We use Dir.glob and File.directory? to find all subdirectories
  • The .select method filters our results to only include directories
  • We use Dir.chdir to change the working directory
  • We iterate through folders with each, skipping those in our exclusion list
  • The backtick syntax (`command` ) lets us execute shell commands from Ruby
  • String interpolation ("#{variable}") makes it easy to build dynamic strings

This script elegantly handles a task that would be more cumbersome in bash, especially when it comes to checking if an item is in the skip list.

Working with processes in Ruby

Ruby makes it easy to work with system processes. Here's a simple example that finds all Ruby processes and displays them:

#!/usr/bin/env ruby

# Find all Ruby processes
ruby_processes = `ps aux | grep ruby | grep -v grep`.split("\n")

puts "Found #{ruby_processes.count} Ruby processes running:"
ruby_processes.each_with_index do |process, index|
  parts = process.split
  pid = parts[1]
  command = parts[10..-1].join(' ')
  puts "#{index + 1}. FOUND PID: #{pid} - Command: #{command}"
end

This script uses a shell command through backticks, then processes the output using Ruby's string manipulation capabilities. The result is much cleaner than the equivalent bash script.

File and system operations

Ruby's standard library includes powerful modules for file and system operations:

#!/usr/bin/env ruby
require 'fileutils'

# Create directory structure
FileUtils.mkdir_p('output/reports/2025')

# Copy files with a pattern
Dir.glob('data/*.csv').each do |file|
  FileUtils.cp(file, 'output/reports/2025/')
end

# Read a file and process it
File.readlines('config.txt').each do |line|
  next if line.start_with?('#') # Skip comments
  puts "Config line: #{line.strip}"
end

# Get system information on MacOS
puts "CPU cores: #{`sysctl -n hw.logicalcpu`.strip}"
puts "Memory: #{`sysctl -n hw.memsize`.to_i / (1024**3)} GB total"
puts "Disk space: #{`df -h /`.strip.split("\n").last}"

This example shows how Ruby can work with files and system commands in an intuitive way.

Making Ruby scripts executable

To make your Ruby scripts truly feel like system tools, add this to the top of your script:

#!/usr/bin/env ruby

if ARGV.empty?
  puts "Usage: #{File.basename($0)} [options]"
  exit 1
end

# Rest of your script...

This gives you a usage message if no arguments are provided, similar to command-line tools. You can read more here about putting arguments.

When to choose Ruby vs. Bash

While Ruby is fantastic for scripting, bash still has its place:

  • Use bash for simple scripts with a few commands, especially for things like environment setup
  • Use Ruby when you need complex logic, better error handling, or more maintainable code

As a general rule, if your bash script exceeds 50 lines or contains multiple functions, it's worth considering Ruby instead.

Summary

Ruby offers a powerful, elegant alternative to bash scripting with better readability, maintainability, and features. With its rich standard library and intuitive syntax, Ruby enables you to write system scripts that are both powerful and easy to understand. Next time you're about to write a complex bash script, consider reaching for Ruby instead - your future self will thank you when you need to modify that script months later!

Happy scripting!