If you want to run an external command from Ruby…
…like wkhtmltopdf to convert an HTML file into a PDF.
There are a few Ruby methods you can use.
Depending on the method you use you’ll get different results.
Let’s explore these methods together!
The Ruby System Method
The Ruby system method is the simplest way to run an external command.
It looks like this:
system("ls")
Notice that system
will print the command output as it happens.
Also system will make your Ruby program wait until the command is done.
Try this:
system("sleep 2")
There are ways to run commands in the background as we’ll see later.
System can return 3 possible values:
true
if the command workedfalse
if the command returns an error codenil
if command execution fails (command not found)
You can get the exit status code of the last external command that you ran with the $?
global variable. This status code can give you more information about why the command failed.
What is Shell Expansion?
When you run an external command with system
you’ll get the “shell expansion” effect.
Shell expansion is when the shell (sh/bash/zsh) takes some special characters (*
, ?
, and [
) from your command & expands them into a file list.
Example:
system("echo *")
This will print every file & folder in the current directory.
The *
is replaced with the list of files before the command runs.
If you want to use these special characters as they are pass the arguments as the 2nd argument of system
.
Like this:
system("echo", "*")
Now the output will be *
.
How to Use Environment Variables
If you want to pass a specific environment variable to an external command you can use a hash as the first argument.
Here’s an example:
system({"rubyguides" => "best"}, "ruby", "-e p ENV['rubyguides']") # "best"
This will not change the current environment of your Ruby application.
%x / Kernel#`
If you want to get the output from the command you’re running, instead of displaying it then you can use %x
or the Kernel#`
method.
They do the same thing.
Here’s an example:
`ls`
Another with %x
:
%x|ls|
These two examples will return a string with the output of the ls
command.
Notice that you still have to wait for the command to finish unless you run it inside a thread.
How to Use Fork + Exec To Run External Commands On a Separate Process
Forking makes a copy of your current process (your Ruby app) then you can replace that copy with another process using exec.
Note: The fork method is not available on Windows.
This is a popular pattern in the Unix world.
Here’s how it works:
fork { exec("ls") }
This will run ls
on another process & display its output.
Because this command is running in another process it will not block your Ruby app from running like the system
method or %x
.
Important:
If you use exec
without fork
you’re going to replace your current process.
This means your Ruby program will end.
How to Use the Popen Method For Two Way Communication With An External Program
If you need:
- More control over the process
- Two-way communication
Then the IO.popen
method is what you are looking for.
In the following example I use the popen method to launch an irb
process, then send some input to it & read the output.
Here the example:
r = IO.popen("irb", "r+") r.write "puts 123 + 1\n" 3.times { puts r.gets } r.write "exit\n"
This r
variable is an IO object, this means that you can write & read from it like a regular file.
Using popen
also means that your external command will run on its own process, so you don’t have to run it on a thread.
There is also a popen3
method in the standard library.
The difference with the regular popen
is that it will split regular output & error messages into separate IO objects.
Conclusion
You have learned about the different ways that you can interact with external commands in Ruby.
Be careful with these!
Don’t pass any kind of user input into these commands unless you want a security issue in your Ruby app.
If you learned something new please share this article so more people can see it.
Thanks for reading 🙂
Now, i understand, why my rails app was terminated, when i invoked exec from controller action without fork. Thanks!
Thanks for reading! 🙂