How to Execute Shell Commands in Ruby

Nurudeen Ibrahim Feb 02, 2024
  1. Use Backticks (`) to Execute Shell Commands in Ruby
  2. Use %x Syntax to Execute Shell Commands in Ruby
  3. Use the system Method to Execute Shell Commands in Ruby
  4. Use the exec Method to Execute Shell Commands in Ruby
  5. Use the Open3 Module to Execute Shell Commands in Ruby
  6. Conclusion
How to Execute Shell Commands in Ruby

Integrating shell commands within a Ruby program opens up a realm of possibilities, enabling interactions with the system, running external processes, and harnessing the power of command-line utilities. In this article, we’ll explore diverse methods to call shell commands within Ruby, providing detailed insights and practical examples for each approach.

Shell commands are a powerful tool in the hands of a developer, allowing seamless interaction with the underlying operating system. Ruby, a versatile and expressive programming language, offers several methodologies to execute these commands, each with its nuances and capabilities.

Use Backticks (`) to Execute Shell Commands in Ruby

In Ruby, backticks (`) are used to enclose shell commands within a string. When used in this manner, Ruby treats the enclosed string as a shell command and executes it.

The result of the command’s standard output is captured as a string, allowing further manipulation or processing within the Ruby program.

Basic Usage

Let’s start by exploring a basic example of using backticks to execute a simple shell command:

puts `echo 'Hello World'`

This example encapsulates the shell command echo 'Hello World' within backticks. This triggers the execution of the enclosed shell command from within the Ruby program.

The echo 'Hello World' command itself prints the text "Hello World" to the standard output of the shell. As a result, the puts method outputs the text "Hello World" to the console, effectively displaying the output of the executed shell command within the Ruby program.

Output:

Hello World

Checking Command Execution Status

Backticks also allow for handling the execution status of shell commands. The command’s execution status can be checked to determine whether it succeeded or encountered an error.

result = `echo "Hello, World!"`
if $?.success?
  puts 'Command executed successfully'
else
  puts 'Command failed'
end

Here, the echo "Hello, World!" command is executed using backticks. The success? method checks the status of the command execution.

If the command succeeds, it prints a success message; otherwise, it prints a failure message.

Output:

Command executed successfully

Handling Errors

While backticks capture the standard output of shell commands, they do not capture error messages or the command’s standard error stream. As a result, handling errors using backticks requires additional consideration.

result = `invalid_command 2>&1`
if $?.exitstatus != 0
  puts "Command failed with exit status: #{$?.exitstatus}"
  puts "Error message: #{result}"
end

In this example, an invalid command (invalid_command) is executed using backticks. The exit status of the command is checked using $?.exitstatus.

If the exit status is non-zero (indicating a failure), it displays an error message along with the captured output (result).

The code also uses the 2>&1 redirection in the command execution to handle and capture the error message when the command fails. This will redirect the standard error (stderr) to the standard output (stdout), allowing you to capture both streams.

Output:

Command failed with exit status: 127
Error message: sh: invalid_command: command not found

Security Considerations

It’s crucial to exercise caution when using backticks to execute shell commands, especially when dealing with user inputs or dynamically generated commands. Improperly sanitized inputs can lead to command injection vulnerabilities.

user_input = 'some_user_input'
sanitized_input = user_input.gsub(/[^a-zA-Z0-9_\-.]/, '')
result = `echo #{sanitized_input}`
puts result

In this example, the gsub method is used to sanitize the user_input by removing potentially harmful characters. The sanitized input (sanitized_input) is then used within the shell command to mitigate potential security risks.

Use %x Syntax to Execute Shell Commands in Ruby

The %x syntax in Ruby serves as an alternative to backticks (`) for executing shell commands. It provides a compact and readable way to run shell commands within a Ruby program.

Similar to backticks, %x executes the enclosed string as a shell command and captures its standard output.

Basic Usage

Let’s begin by examining a simple example that demonstrates the basic usage of %x syntax:

puts `echo 'Hello World'`

This example executes a shell command enclosed within the %x() syntax. Specifically, it runs the echo 'Hello World' command, which prints the text "Hello World" to the standard output.

The puts method is then used to display the output of this executed shell command ("Hello World") in the console.

Capturing Output

One of the primary advantages of using %x syntax is its ability to capture the output of shell commands. This captured output can then be utilized within the Ruby program for further processing or display.

file_count = `find . -type f | wc -l`
puts "Total number of files: #{file_count.strip}"

Here, the %x(find . -type f | wc -l) command finds all files in the current directory and counts their total number using wc -l.

The output, which includes the file count, is captured in the file_count variable. The .strip method removes trailing whitespace, ensuring clean output.

Output:

Total number of files: 1

Checking Command Execution Status

Similar to backticks, the %x syntax enables developers to check the execution status of shell commands. This facilitates error handling and conditional execution based on command success or failure.

result = `echo 'Hello World'`
if $?.success?
  puts 'Successful'
else
  puts 'Failed'
end

In this example, the echo "Hello, World!" command is executed using %x. The success status of the command execution is checked using $?.success?.

If the command succeeds, it prints a success message; otherwise, it prints a failure message.

Handling Errors

Similar to backticks, the %x syntax does not capture the standard error stream of shell commands. Therefore, managing errors using %x requires additional considerations.

result = `invalid_command 2>&1`
if $?.exitstatus != 0
  puts "Command failed with exit status: #{$?.exitstatus}"
  puts "Error message: #{result}"
end

In this example, %x(invalid_command) attempts to execute an invalid command. The exit status of the command is checked using $?.exitstatus.

If the exit status is non-zero (indicating a failure), it displays an error message along with the captured output (result).

It also uses the 2>&1 redirection in the command execution to handle and capture the error message when the command fails. This will redirect the standard error (stderr) to the standard output (stdout), allowing you to capture both streams.

Output:

Command failed with exit status: 127
Error message: sh: invalid_command: command not found

Security Considerations

Just like with backticks, caution must be exercised when using the %x syntax to execute shell commands, especially when handling user inputs or dynamic commands. Proper input sanitization is crucial to prevent command injection vulnerabilities.

user_input = 'some_user_input'
sanitized_input = user_input.gsub(/[^a-zA-Z0-9_\-.]/, '')
result = `echo #{sanitized_input}`
puts result

In this example, the gsub method is employed to sanitize the user_input by removing potentially harmful characters. The sanitized input (sanitized_input) is then used within the shell command to mitigate potential security risks.

Use the system Method to Execute Shell Commands in Ruby

The system method in Ruby facilitates the execution of shell commands while providing a mechanism to capture the execution status. Unlike backticks or %x syntax, system returns a Boolean value (true or false) based on the success or failure of the executed command.

Basic Usage

Let’s initiate our journey by examining the basic usage of the system method:

system "echo 'Hello World'"

There’s no need to use puts in this example because the method already outputs the result of the command.

Checking Command Execution Status

The primary strength of the system method lies in its ability to provide clarity regarding command execution status. It returns true upon successful execution and false otherwise, allowing for precise handling of command outcomes.

success = system('ping -c 1 google.com')
if success
  puts 'Ping successful'
else
  puts 'Ping failed'
end

In this example, system('ping -c 1 google.com') attempts to send a single ICMP packet to Google’s server using the ping command. Based on the return value of system, it outputs either a success or failure message.

Handling Command Arguments

The system method accommodates the use of multiple command arguments, providing flexibility in executing complex shell commands.

file_name = 'example.txt'
success = system('touch', file_name)
puts "#{file_name} created successfully" if success

Here, system('touch', file_name) creates a new file named example.txt using the touch command. The method accepts both the command (touch) and the filename (file_name) as separate arguments.

Handling Errors

When an error occurs during command execution, system returns false, allowing for error handling within Ruby programs.

success = system('invalid_command')
puts 'Command execution failed' unless success

In this example, system('invalid_command') attempts to execute an invalid command. As the command fails, the !success condition triggers an error message.

Security Considerations

Similar to other methods of executing shell commands, precautions must be taken when using the system method to prevent command injection vulnerabilities, especially when dealing with user inputs or dynamically generated commands.

user_input = 'some_user_input'
sanitized_input = user_input.gsub(/[^a-zA-Z0-9_\-.]/, '')
success = system("echo #{sanitized_input}")
puts 'Command executed successfully' if success

In this example, the gsub method sanitizes user_input by removing potentially harmful characters. The sanitized input (sanitized_input) is then used within the system method to mitigate security risks.

Use the exec Method to Execute Shell Commands in Ruby

The exec method in Ruby serves a distinct purpose: it replaces the current process with the execution of a specified shell command.

Unlike other methods such as system or backticks, exec does not return to the Ruby program after command execution; instead, it completely replaces the Ruby process with the executed command.

Basic Usage

Let’s initiate our exploration by examining the basic usage of the exec method:

exec('echo "Hello, World!"')
puts "This line won't be executed"

In this example, exec('echo "Hello, World!"') executes the echo "Hello, World!" command in the shell. Following the exec command, any subsequent Ruby code is not executed as the shell command replaces the Ruby process.

Complete Replacement of Process

The most distinctive characteristic of the exec method is its behavior of entirely replacing the current Ruby process with the executed shell command. This results in the termination of the Ruby program after command execution.

exec('ls -l')
puts "This line won't be executed"

Here, exec('ls -l') executes the ls -l command to list the contents of the current directory in long format. After the execution of exec, any subsequent Ruby code, such as the puts statement, is not executed.

Passing Control to External Commands

The exec method allows for the passing of control from the Ruby program to external commands. This feature enables seamless integration with various system-level utilities and programs.

file_name = 'example.txt'
exec('touch', file_name)

In this example, exec('touch', file_name) uses the touch command to create a new file named example.txt. The control is passed to the touch command, and no further Ruby code is executed afterward.

Security Considerations

Since exec completely replaces the current process with the specified command, it’s important to exercise caution when using it, especially with user inputs or dynamically generated commands, to prevent command injection vulnerabilities.

user_input = 'some_user_input'
sanitized_input = user_input.gsub(/[^a-zA-Z0-9_\-.]/, '')
exec("echo #{sanitized_input}")

In this example, the gsub method sanitizes user_input by removing potentially harmful characters. The sanitized input (sanitized_input) is then used within the exec method to mitigate potential security risks.

Use the Open3 Module to Execute Shell Commands in Ruby

The Open3 module in Ruby provides a robust framework for managing input, output, and error streams when executing shell commands.

It facilitates a higher level of control compared to methods like backticks, %x syntax, system, or exec, allowing developers to access not only the standard output but also the standard error and input streams of executed commands.

Basic Usage

Let’s commence our exploration by examining the basic usage of the Open3 module:

require 'open3'

stdin, stdout, stderr = Open3.capture3('ls -l')
puts 'Standard Output:'
puts stdout
puts 'Standard Error:'
puts stderr

In this example, Open3.capture3('ls -l') executes the ls -l command to list the contents of the current directory in long format.

The capture3 method captures the standard input, output, and error streams of the executed command, storing them in the stdin, stdout, and stderr variables, respectively. Subsequently, the standard output and error are read and displayed.

Capturing Streams

The primary advantage of using the Open3 module is its capability to capture standard input, output, and error streams separately, providing comprehensive control over command execution and its streams.

require 'open3'

input_data = 'Hello, Open3!'
stdout, stderr, status = Open3.capture3('echo', input_data)
puts 'Standard Output:'
puts stdout
puts 'Standard Error:'
puts stderr
puts 'Command Status:'
puts status.success? ? 'Command executed successfully' : 'Command failed'

Here, the capture3 method executes the echo command with the input data "Hello, Open3!". The method captures the standard output, error, and status of the executed command.

It then prints the captured output and error, along with the command execution status.

Managing Streams and Process Handling

The Open3 module facilitates more granular control over input, output, and error streams, enabling developers to interact with the underlying processes and manage their communication.

require 'open3'

Open3.popen3('cat') do |stdin, stdout, _stderr, _wait_thr|
  Thread.new do
    stdin.puts 'Hello, Open3!'
    stdin.close
  end
  puts 'Output from cat command:'
  puts stdout.read
end

In this example, popen3('cat') opens a process for the cat command. Using a thread, it writes "Hello, Open3!" to the standard input of the cat command and reads its output, effectively simulating interaction with the cat process.

Security Considerations

While the Open3 module provides comprehensive control over command execution and streams, developers must still exercise caution when handling user inputs or dynamic commands to prevent command injection vulnerabilities.

require 'open3'

user_input = 'some_user_input'
sanitized_input = user_input.gsub(/[^a-zA-Z0-9_\-.]/, '')
stdout, stderr, = Open3.capture3("echo #{sanitized_input}")
puts 'Standard Output:'
puts stdout
puts 'Standard Error:'
puts stderr

In this example, the gsub method sanitizes user_input by removing potentially harmful characters. The sanitized input (sanitized_input) is then used within the Open3 method to mitigate potential security risks.

Conclusion

This article explored various methods for executing shell commands within Ruby programs:

  1. Backticks and %x syntax: These methods execute shell commands within strings, capturing standard output but lacking robust error handling for standard errors.
  2. system method: Provides clearer command execution status (success or failure) and supports multiple arguments, suitable for handling command outcomes.
  3. exec method: Completely replaces the Ruby process with the executed command, offering no return value or status, and doesn’t allow for error handling or command output capture within Ruby.
  4. Open3 module: Offers comprehensive control over input, output, and error streams, allowing separate stream capture, advanced process handling, error management, and security measures against command injection vulnerabilities.

While each method has strengths, caution is essential, especially with user inputs or dynamic commands, to prevent security risks like command injection vulnerabilities. Proper input sanitization is crucial for the secure execution of shell commands within Ruby programs.