Explain

How to call shell commands from Ruby?

Ruby offers multiple ways to call shell commands—whether you need to list files, run scripts, or integrate with system tools. The language provides clean, concise APIs for capturing outputs, handling errors, and interacting with the command’s input/output streams. Below, we explore the most common methods for executing shell commands in Ruby, along with best practices to keep your code robust and secure.

1. Using Backticks

The simplest way to execute a shell command in Ruby is by using backticks (``):

output = `ls`
puts output
  • Returns a String: Backticks execute the command in a subshell, capturing whatever is printed to stdout.
  • Easy to Use: Ideal for quick scripts that need to grab text output.
  • Security Note: Be careful not to pass unsafe user input into the backticks directly—this can expose you to shell injection attacks.

2. Using the %x Literal

Ruby also provides an alternate syntax using %x{}:

output = %x{echo "Hello from Ruby!"}
puts output
  • Same Behavior as Backticks: %x is functionally equivalent to using backticks, making it another handy option if your command needs quotes or includes backticks of its own.

3. Using system

Use system if you only need to run a command and check its success without capturing the output:

success = system("echo 'Running system command'")
puts success  # => true or false
  • Boolean Return: Returns true if the command executes successfully, false otherwise.
  • Exit Status: You can also inspect $? (an instance of Process::Status) to see the exact exit code.

4. Using exec

When you call exec, Ruby immediately replaces the current process with the specified command:

exec("echo 'This will replace the Ruby process'")
puts "This will never run."
  • No Return: exec does not come back to the Ruby script. Once called, your script’s execution ends and transfers control to the external command.
  • Use Sparingly: Ideal in cases where you truly need to switch processes, such as launching a different program.

5. Using Open3 for Advanced Control

For more complex use cases—where you might need to capture both stdout and stderr, or feed data interactively—use the Open3 module:

require 'open3'

stdout, stderr, status = Open3.capture3("ls -la")
if status.success?
  puts "STDOUT:\n#{stdout}"
else
  puts "STDERR:\n#{stderr}"
end
  • Capture Streams: capture3 returns standard output, standard error, and the exit status.
  • Real-Time Interaction: For even more control (writing to stdin or reading output as it arrives), use Open3.popen3.

Best Practices

  1. Sanitize User Input: Always validate or escape external input passed to shell commands to prevent injection attacks.
  2. Check Exit Codes: Use $? or the status object from Open3 to verify whether a command completed successfully.
  3. Choose the Right Tool:
    • Backticks / %x for quick, one-off commands where you need the output.
    • system when you only care about success/failure.
    • exec if you want to replace the current process.
    • Open3 for more advanced needs, like interactive I/O or capturing error streams.

Recommended Courses to Strengthen Your Skills

If you’re looking for a free primer or additional resources, check out the System Design Primer: The Ultimate Guide on the DesignGurus.io blog. You can also consider Coding Mock Interview or System Design Mock Interview sessions to get personalized feedback from ex-FAANG engineers.

Conclusion

Ruby makes it exceptionally straightforward to call shell commands—from simple one-liners using backticks or %x, to more advanced features using Open3. Which method you choose depends on your project’s needs, but always remember to safeguard against security pitfalls and handle errors correctly. Combine these practices with robust coding and system design skills, and you’ll be ready to tackle both day-to-day scripting tasks and high-level software engineering challenges with confidence.

Recommended Courses