When to use each method of launching a subprocess in Ruby
Asked Answered
J

4

69

1. `` The Backtick

1. a) %x{} Percent X < alternate syntax for The Backtick

2. system()

3. fork()

4. open()

4.a. IO.popen() < behaves the same as open()

4.b. open("|-")

  • fork to a pipe

4.c. IO.popen("-") < behaves the same as open("|-")

5. Open3.popen3()

  • require 'open3'
  • stdlib Open3

6. PTY.spawn()

  • require 'pty'
  • stdlib PTY

7. Shell.transact()

  • require 'shell'
  • stdlib Shell

When should one forsake the trusty back-tick for one of the more complex methods?

Edit 1. Big thanks to Avdi Grimm for his posts describing example usage of each method: #1 (& gist); #2 (& gist); #3.

They are fantastic resources to answer How, but are not explicitly composed to answer when each should be used or Why, and as such IMHO are not complete answers to this question.

Jankowski answered 27/8, 2011 at 4:37 Comment(1)
C
62
  1. use backticks when you want to easily capture the output of a program in a variable. you probably only want to use this for short-running programs, because this will block.

  2. system is convenient in two different cases:

    a. You have a long running program and you want the output to print as it runs (e.g. system("tar zxvf some_big_tarball.tar.gz"))

    b. system can bypass the shell expansion like exec (compare the output of system "echo *" and system "echo", "*")

    system blocks until the subprocess has exited.

  3. fork has a couple different use cases as well:

    a. You want to run some ruby code in a separate process (e.g. fork { .... }

    b. You want to run a child process (or different program) without blocking progress of your script fork { exec "bash" }.

    fork is your friend if you want to daemonize your program.

  4. IO.popen is useful when you need to interact with the standard out and standard in of a program. Note that it doesn't capture standard err, so you need to redirect that with 2>&1 if you care about that.

  5. popen3 gives you a separate file descriptor for standard error (for when you need to capture that separately from standard out)

  6. PTY.spawn is necessary when you want the spawned program to behave like you are running from the terminal. See the difference of grep --color=auto pat file when spawned with system vs PTY.spawn

Cilium answered 31/8, 2011 at 21:19 Comment(0)
D
50

Here's a flowchart based on this answer. See also, using script to emulate a terminal.

enter image description here

Diurnal answered 26/5, 2015 at 16:18 Comment(1)
fork not universally available so one may still require spawnCity
S
0

In addition to the flowchart above https://mcmap.net/q/46589/-when-to-use-each-method-of-launching-a-subprocess-in-ruby, in case somebody wants it as an editable flowchart in markdown (powered by mermaid), here it is:

```mermaid
flowchart TD

A{{"Do I want to return to my ruby script, ever?"}} -- No --> B(["Use exec()"])
A -- Yes --> C{{"Is it OK to block until the process completes?"}}
C -- Yes --> D{{"Do I need the program's output to be returned?"}}
D -- Yes --> E(["Use backticks `` or %x{}"])
D -- No --> F(["Use system()"])
C -- No --> H{{"Do I need to interact with the output?"}}
H -- Yes --> J{{"Do I want STDERR?"}}
H -- No --> I(["Use fork()"])
J -- Yes --> L{{"Do I want STDERR in its own separate stream?"}}
J -- No --> K(["Use IO.popen()"])
L -- Yes --> M(["Use Open3.popen3()"])
L -- No --> N{{"Use PTY.spawn()"}}

O>"Outputs to STDOUT"] -.- F
P>"You can always emulate a terminal with the BSD utility called script"] -.- H
Q>"Separate child process; good for daemonizing"] -.- I
R>"You can still use 2>&1 to combine STDERR with STDOUT"] -.- K
S>"Emulates a terminal unconditionally"] -.- M

classDef decision fill:#f8fb99;
classDef action fill:#ffaead;
classDef note fill:#ddd,opacity:0.9,font-weight:200;

class A,C,D,H,J,L decision;
class B,E,F,I,K,M,N action;
class O,P,Q,R,S note;
```
Silurid answered 18/6, 2022 at 16:44 Comment(0)
C
0

Also, there is Process.spawn. From the docs:

This method is similar to Kernel#system but it doesn't wait for the command to finish.

The parent process should use Process.wait to collect the termination status of its child or use Process.detach to register disinterest in their status; otherwise, the operating system may accumulate zombie processes.

Cahier answered 22/7, 2022 at 20:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.