1# frozen_string_literal: true 2 3# 4# = open3.rb: Popen, but with stderr, too 5# 6# Author:: Yukihiro Matsumoto 7# Documentation:: Konrad Meyer 8# 9# Open3 gives you access to stdin, stdout, and stderr when running other 10# programs. 11# 12 13# 14# Open3 grants you access to stdin, stdout, stderr and a thread to wait for the 15# child process when running another program. 16# You can specify various attributes, redirections, current directory, etc., of 17# the program in the same way as for Process.spawn. 18# 19# - Open3.popen3 : pipes for stdin, stdout, stderr 20# - Open3.popen2 : pipes for stdin, stdout 21# - Open3.popen2e : pipes for stdin, merged stdout and stderr 22# - Open3.capture3 : give a string for stdin; get strings for stdout, stderr 23# - Open3.capture2 : give a string for stdin; get a string for stdout 24# - Open3.capture2e : give a string for stdin; get a string for merged stdout and stderr 25# - Open3.pipeline_rw : pipes for first stdin and last stdout of a pipeline 26# - Open3.pipeline_r : pipe for last stdout of a pipeline 27# - Open3.pipeline_w : pipe for first stdin of a pipeline 28# - Open3.pipeline_start : run a pipeline without waiting 29# - Open3.pipeline : run a pipeline and wait for its completion 30# 31 32module Open3 33 34 # Open stdin, stdout, and stderr streams and start external executable. 35 # In addition, a thread to wait for the started process is created. 36 # The thread has a pid method and a thread variable :pid which is the pid of 37 # the started process. 38 # 39 # Block form: 40 # 41 # Open3.popen3([env,] cmd... [, opts]) {|stdin, stdout, stderr, wait_thr| 42 # pid = wait_thr.pid # pid of the started process. 43 # ... 44 # exit_status = wait_thr.value # Process::Status object returned. 45 # } 46 # 47 # Non-block form: 48 # 49 # stdin, stdout, stderr, wait_thr = Open3.popen3([env,] cmd... [, opts]) 50 # pid = wait_thr[:pid] # pid of the started process 51 # ... 52 # stdin.close # stdin, stdout and stderr should be closed explicitly in this form. 53 # stdout.close 54 # stderr.close 55 # exit_status = wait_thr.value # Process::Status object returned. 56 # 57 # The parameters env, cmd, and opts are passed to Process.spawn. 58 # A commandline string and a list of argument strings can be accepted as follows: 59 # 60 # Open3.popen3("echo abc") {|i, o, e, t| ... } 61 # Open3.popen3("echo", "abc") {|i, o, e, t| ... } 62 # Open3.popen3(["echo", "argv0"], "abc") {|i, o, e, t| ... } 63 # 64 # If the last parameter, opts, is a Hash, it is recognized as an option for Process.spawn. 65 # 66 # Open3.popen3("pwd", :chdir=>"/") {|i,o,e,t| 67 # p o.read.chomp #=> "/" 68 # } 69 # 70 # wait_thr.value waits for the termination of the process. 71 # The block form also waits for the process when it returns. 72 # 73 # Closing stdin, stdout and stderr does not wait for the process to complete. 74 # 75 # You should be careful to avoid deadlocks. 76 # Since pipes are fixed length buffers, 77 # Open3.popen3("prog") {|i, o, e, t| o.read } deadlocks if 78 # the program generates too much output on stderr. 79 # You should read stdout and stderr simultaneously (using threads or IO.select). 80 # However, if you don't need stderr output, you can use Open3.popen2. 81 # If merged stdout and stderr output is not a problem, you can use Open3.popen2e. 82 # If you really need stdout and stderr output as separate strings, you can consider Open3.capture3. 83 # 84 def popen3(*cmd, &block) 85 if Hash === cmd.last 86 opts = cmd.pop.dup 87 else 88 opts = {} 89 end 90 91 in_r, in_w = IO.pipe 92 opts[:in] = in_r 93 in_w.sync = true 94 95 out_r, out_w = IO.pipe 96 opts[:out] = out_w 97 98 err_r, err_w = IO.pipe 99 opts[:err] = err_w 100 101 popen_run(cmd, opts, [in_r, out_w, err_w], [in_w, out_r, err_r], &block) 102 end 103 module_function :popen3 104 105 # Open3.popen2 is similar to Open3.popen3 except that it doesn't create a pipe for 106 # the standard error stream. 107 # 108 # Block form: 109 # 110 # Open3.popen2([env,] cmd... [, opts]) {|stdin, stdout, wait_thr| 111 # pid = wait_thr.pid # pid of the started process. 112 # ... 113 # exit_status = wait_thr.value # Process::Status object returned. 114 # } 115 # 116 # Non-block form: 117 # 118 # stdin, stdout, wait_thr = Open3.popen2([env,] cmd... [, opts]) 119 # ... 120 # stdin.close # stdin and stdout should be closed explicitly in this form. 121 # stdout.close 122 # 123 # See Process.spawn for the optional hash arguments _env_ and _opts_. 124 # 125 # Example: 126 # 127 # Open3.popen2("wc -c") {|i,o,t| 128 # i.print "answer to life the universe and everything" 129 # i.close 130 # p o.gets #=> "42\n" 131 # } 132 # 133 # Open3.popen2("bc -q") {|i,o,t| 134 # i.puts "obase=13" 135 # i.puts "6 * 9" 136 # p o.gets #=> "42\n" 137 # } 138 # 139 # Open3.popen2("dc") {|i,o,t| 140 # i.print "42P" 141 # i.close 142 # p o.read #=> "*" 143 # } 144 # 145 def popen2(*cmd, &block) 146 if Hash === cmd.last 147 opts = cmd.pop.dup 148 else 149 opts = {} 150 end 151 152 in_r, in_w = IO.pipe 153 opts[:in] = in_r 154 in_w.sync = true 155 156 out_r, out_w = IO.pipe 157 opts[:out] = out_w 158 159 popen_run(cmd, opts, [in_r, out_w], [in_w, out_r], &block) 160 end 161 module_function :popen2 162 163 # Open3.popen2e is similar to Open3.popen3 except that it merges 164 # the standard output stream and the standard error stream. 165 # 166 # Block form: 167 # 168 # Open3.popen2e([env,] cmd... [, opts]) {|stdin, stdout_and_stderr, wait_thr| 169 # pid = wait_thr.pid # pid of the started process. 170 # ... 171 # exit_status = wait_thr.value # Process::Status object returned. 172 # } 173 # 174 # Non-block form: 175 # 176 # stdin, stdout_and_stderr, wait_thr = Open3.popen2e([env,] cmd... [, opts]) 177 # ... 178 # stdin.close # stdin and stdout_and_stderr should be closed explicitly in this form. 179 # stdout_and_stderr.close 180 # 181 # See Process.spawn for the optional hash arguments _env_ and _opts_. 182 # 183 # Example: 184 # # check gcc warnings 185 # source = "foo.c" 186 # Open3.popen2e("gcc", "-Wall", source) {|i,oe,t| 187 # oe.each {|line| 188 # if /warning/ =~ line 189 # ... 190 # end 191 # } 192 # } 193 # 194 def popen2e(*cmd, &block) 195 if Hash === cmd.last 196 opts = cmd.pop.dup 197 else 198 opts = {} 199 end 200 201 in_r, in_w = IO.pipe 202 opts[:in] = in_r 203 in_w.sync = true 204 205 out_r, out_w = IO.pipe 206 opts[[:out, :err]] = out_w 207 208 popen_run(cmd, opts, [in_r, out_w], [in_w, out_r], &block) 209 end 210 module_function :popen2e 211 212 def popen_run(cmd, opts, child_io, parent_io) # :nodoc: 213 pid = spawn(*cmd, opts) 214 wait_thr = Process.detach(pid) 215 child_io.each(&:close) 216 result = [*parent_io, wait_thr] 217 if defined? yield 218 begin 219 return yield(*result) 220 ensure 221 parent_io.each(&:close) 222 wait_thr.join 223 end 224 end 225 result 226 end 227 module_function :popen_run 228 class << self 229 private :popen_run 230 end 231 232 # Open3.capture3 captures the standard output and the standard error of a command. 233 # 234 # stdout_str, stderr_str, status = Open3.capture3([env,] cmd... [, opts]) 235 # 236 # The arguments env, cmd and opts are passed to Open3.popen3 except 237 # <code>opts[:stdin_data]</code> and <code>opts[:binmode]</code>. See Process.spawn. 238 # 239 # If <code>opts[:stdin_data]</code> is specified, it is sent to the command's standard input. 240 # 241 # If <code>opts[:binmode]</code> is true, internal pipes are set to binary mode. 242 # 243 # Examples: 244 # 245 # # dot is a command of graphviz. 246 # graph = <<'End' 247 # digraph g { 248 # a -> b 249 # } 250 # End 251 # drawn_graph, dot_log = Open3.capture3("dot -v", :stdin_data=>graph) 252 # 253 # o, e, s = Open3.capture3("echo abc; sort >&2", :stdin_data=>"foo\nbar\nbaz\n") 254 # p o #=> "abc\n" 255 # p e #=> "bar\nbaz\nfoo\n" 256 # p s #=> #<Process::Status: pid 32682 exit 0> 257 # 258 # # generate a thumbnail image using the convert command of ImageMagick. 259 # # However, if the image is really stored in a file, 260 # # system("convert", "-thumbnail", "80", "png:#{filename}", "png:-") is better 261 # # because of reduced memory consumption. 262 # # But if the image is stored in a DB or generated by the gnuplot Open3.capture2 example, 263 # # Open3.capture3 should be considered. 264 # # 265 # image = File.read("/usr/share/openclipart/png/animals/mammals/sheep-md-v0.1.png", :binmode=>true) 266 # thumbnail, err, s = Open3.capture3("convert -thumbnail 80 png:- png:-", :stdin_data=>image, :binmode=>true) 267 # if s.success? 268 # STDOUT.binmode; print thumbnail 269 # end 270 # 271 def capture3(*cmd) 272 if Hash === cmd.last 273 opts = cmd.pop.dup 274 else 275 opts = {} 276 end 277 278 stdin_data = opts.delete(:stdin_data) || '' 279 binmode = opts.delete(:binmode) 280 281 popen3(*cmd, opts) {|i, o, e, t| 282 if binmode 283 i.binmode 284 o.binmode 285 e.binmode 286 end 287 out_reader = Thread.new { o.read } 288 err_reader = Thread.new { e.read } 289 begin 290 if stdin_data.respond_to? :readpartial 291 IO.copy_stream(stdin_data, i) 292 else 293 i.write stdin_data 294 end 295 rescue Errno::EPIPE 296 end 297 i.close 298 [out_reader.value, err_reader.value, t.value] 299 } 300 end 301 module_function :capture3 302 303 # Open3.capture2 captures the standard output of a command. 304 # 305 # stdout_str, status = Open3.capture2([env,] cmd... [, opts]) 306 # 307 # The arguments env, cmd and opts are passed to Open3.popen3 except 308 # <code>opts[:stdin_data]</code> and <code>opts[:binmode]</code>. See Process.spawn. 309 # 310 # If <code>opts[:stdin_data]</code> is specified, it is sent to the command's standard input. 311 # 312 # If <code>opts[:binmode]</code> is true, internal pipes are set to binary mode. 313 # 314 # Example: 315 # 316 # # factor is a command for integer factorization. 317 # o, s = Open3.capture2("factor", :stdin_data=>"42") 318 # p o #=> "42: 2 3 7\n" 319 # 320 # # generate x**2 graph in png using gnuplot. 321 # gnuplot_commands = <<"End" 322 # set terminal png 323 # plot x**2, "-" with lines 324 # 1 14 325 # 2 1 326 # 3 8 327 # 4 5 328 # e 329 # End 330 # image, s = Open3.capture2("gnuplot", :stdin_data=>gnuplot_commands, :binmode=>true) 331 # 332 def capture2(*cmd) 333 if Hash === cmd.last 334 opts = cmd.pop.dup 335 else 336 opts = {} 337 end 338 339 stdin_data = opts.delete(:stdin_data) 340 binmode = opts.delete(:binmode) 341 342 popen2(*cmd, opts) {|i, o, t| 343 if binmode 344 i.binmode 345 o.binmode 346 end 347 out_reader = Thread.new { o.read } 348 if stdin_data 349 begin 350 if stdin_data.respond_to? :readpartial 351 IO.copy_stream(stdin_data, i) 352 else 353 i.write stdin_data 354 end 355 rescue Errno::EPIPE 356 end 357 end 358 i.close 359 [out_reader.value, t.value] 360 } 361 end 362 module_function :capture2 363 364 # Open3.capture2e captures the standard output and the standard error of a command. 365 # 366 # stdout_and_stderr_str, status = Open3.capture2e([env,] cmd... [, opts]) 367 # 368 # The arguments env, cmd and opts are passed to Open3.popen3 except 369 # <code>opts[:stdin_data]</code> and <code>opts[:binmode]</code>. See Process.spawn. 370 # 371 # If <code>opts[:stdin_data]</code> is specified, it is sent to the command's standard input. 372 # 373 # If <code>opts[:binmode]</code> is true, internal pipes are set to binary mode. 374 # 375 # Example: 376 # 377 # # capture make log 378 # make_log, s = Open3.capture2e("make") 379 # 380 def capture2e(*cmd) 381 if Hash === cmd.last 382 opts = cmd.pop.dup 383 else 384 opts = {} 385 end 386 387 stdin_data = opts.delete(:stdin_data) 388 binmode = opts.delete(:binmode) 389 390 popen2e(*cmd, opts) {|i, oe, t| 391 if binmode 392 i.binmode 393 oe.binmode 394 end 395 outerr_reader = Thread.new { oe.read } 396 if stdin_data 397 begin 398 if stdin_data.respond_to? :readpartial 399 IO.copy_stream(stdin_data, i) 400 else 401 i.write stdin_data 402 end 403 rescue Errno::EPIPE 404 end 405 end 406 i.close 407 [outerr_reader.value, t.value] 408 } 409 end 410 module_function :capture2e 411 412 # Open3.pipeline_rw starts a list of commands as a pipeline with pipes 413 # which connect to stdin of the first command and stdout of the last command. 414 # 415 # Open3.pipeline_rw(cmd1, cmd2, ... [, opts]) {|first_stdin, last_stdout, wait_threads| 416 # ... 417 # } 418 # 419 # first_stdin, last_stdout, wait_threads = Open3.pipeline_rw(cmd1, cmd2, ... [, opts]) 420 # ... 421 # first_stdin.close 422 # last_stdout.close 423 # 424 # Each cmd is a string or an array. 425 # If it is an array, the elements are passed to Process.spawn. 426 # 427 # cmd: 428 # commandline command line string which is passed to a shell 429 # [env, commandline, opts] command line string which is passed to a shell 430 # [env, cmdname, arg1, ..., opts] command name and one or more arguments (no shell) 431 # [env, [cmdname, argv0], arg1, ..., opts] command name and arguments including argv[0] (no shell) 432 # 433 # Note that env and opts are optional, as for Process.spawn. 434 # 435 # The options to pass to Process.spawn are constructed by merging 436 # +opts+, the last hash element of the array, and 437 # specifications for the pipes between each of the commands. 438 # 439 # Example: 440 # 441 # Open3.pipeline_rw("tr -dc A-Za-z", "wc -c") {|i, o, ts| 442 # i.puts "All persons more than a mile high to leave the court." 443 # i.close 444 # p o.gets #=> "42\n" 445 # } 446 # 447 # Open3.pipeline_rw("sort", "cat -n") {|stdin, stdout, wait_thrs| 448 # stdin.puts "foo" 449 # stdin.puts "bar" 450 # stdin.puts "baz" 451 # stdin.close # send EOF to sort. 452 # p stdout.read #=> " 1\tbar\n 2\tbaz\n 3\tfoo\n" 453 # } 454 def pipeline_rw(*cmds, &block) 455 if Hash === cmds.last 456 opts = cmds.pop.dup 457 else 458 opts = {} 459 end 460 461 in_r, in_w = IO.pipe 462 opts[:in] = in_r 463 in_w.sync = true 464 465 out_r, out_w = IO.pipe 466 opts[:out] = out_w 467 468 pipeline_run(cmds, opts, [in_r, out_w], [in_w, out_r], &block) 469 end 470 module_function :pipeline_rw 471 472 # Open3.pipeline_r starts a list of commands as a pipeline with a pipe 473 # which connects to stdout of the last command. 474 # 475 # Open3.pipeline_r(cmd1, cmd2, ... [, opts]) {|last_stdout, wait_threads| 476 # ... 477 # } 478 # 479 # last_stdout, wait_threads = Open3.pipeline_r(cmd1, cmd2, ... [, opts]) 480 # ... 481 # last_stdout.close 482 # 483 # Each cmd is a string or an array. 484 # If it is an array, the elements are passed to Process.spawn. 485 # 486 # cmd: 487 # commandline command line string which is passed to a shell 488 # [env, commandline, opts] command line string which is passed to a shell 489 # [env, cmdname, arg1, ..., opts] command name and one or more arguments (no shell) 490 # [env, [cmdname, argv0], arg1, ..., opts] command name and arguments including argv[0] (no shell) 491 # 492 # Note that env and opts are optional, as for Process.spawn. 493 # 494 # Example: 495 # 496 # Open3.pipeline_r("zcat /var/log/apache2/access.log.*.gz", 497 # [{"LANG"=>"C"}, "grep", "GET /favicon.ico"], 498 # "logresolve") {|o, ts| 499 # o.each_line {|line| 500 # ... 501 # } 502 # } 503 # 504 # Open3.pipeline_r("yes", "head -10") {|o, ts| 505 # p o.read #=> "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\n" 506 # p ts[0].value #=> #<Process::Status: pid 24910 SIGPIPE (signal 13)> 507 # p ts[1].value #=> #<Process::Status: pid 24913 exit 0> 508 # } 509 # 510 def pipeline_r(*cmds, &block) 511 if Hash === cmds.last 512 opts = cmds.pop.dup 513 else 514 opts = {} 515 end 516 517 out_r, out_w = IO.pipe 518 opts[:out] = out_w 519 520 pipeline_run(cmds, opts, [out_w], [out_r], &block) 521 end 522 module_function :pipeline_r 523 524 # Open3.pipeline_w starts a list of commands as a pipeline with a pipe 525 # which connects to stdin of the first command. 526 # 527 # Open3.pipeline_w(cmd1, cmd2, ... [, opts]) {|first_stdin, wait_threads| 528 # ... 529 # } 530 # 531 # first_stdin, wait_threads = Open3.pipeline_w(cmd1, cmd2, ... [, opts]) 532 # ... 533 # first_stdin.close 534 # 535 # Each cmd is a string or an array. 536 # If it is an array, the elements are passed to Process.spawn. 537 # 538 # cmd: 539 # commandline command line string which is passed to a shell 540 # [env, commandline, opts] command line string which is passed to a shell 541 # [env, cmdname, arg1, ..., opts] command name and one or more arguments (no shell) 542 # [env, [cmdname, argv0], arg1, ..., opts] command name and arguments including argv[0] (no shell) 543 # 544 # Note that env and opts are optional, as for Process.spawn. 545 # 546 # Example: 547 # 548 # Open3.pipeline_w("bzip2 -c", :out=>"/tmp/hello.bz2") {|i, ts| 549 # i.puts "hello" 550 # } 551 # 552 def pipeline_w(*cmds, &block) 553 if Hash === cmds.last 554 opts = cmds.pop.dup 555 else 556 opts = {} 557 end 558 559 in_r, in_w = IO.pipe 560 opts[:in] = in_r 561 in_w.sync = true 562 563 pipeline_run(cmds, opts, [in_r], [in_w], &block) 564 end 565 module_function :pipeline_w 566 567 # Open3.pipeline_start starts a list of commands as a pipeline. 568 # No pipes are created for stdin of the first command and 569 # stdout of the last command. 570 # 571 # Open3.pipeline_start(cmd1, cmd2, ... [, opts]) {|wait_threads| 572 # ... 573 # } 574 # 575 # wait_threads = Open3.pipeline_start(cmd1, cmd2, ... [, opts]) 576 # ... 577 # 578 # Each cmd is a string or an array. 579 # If it is an array, the elements are passed to Process.spawn. 580 # 581 # cmd: 582 # commandline command line string which is passed to a shell 583 # [env, commandline, opts] command line string which is passed to a shell 584 # [env, cmdname, arg1, ..., opts] command name and one or more arguments (no shell) 585 # [env, [cmdname, argv0], arg1, ..., opts] command name and arguments including argv[0] (no shell) 586 # 587 # Note that env and opts are optional, as for Process.spawn. 588 # 589 # Example: 590 # 591 # # Run xeyes in 10 seconds. 592 # Open3.pipeline_start("xeyes") {|ts| 593 # sleep 10 594 # t = ts[0] 595 # Process.kill("TERM", t.pid) 596 # p t.value #=> #<Process::Status: pid 911 SIGTERM (signal 15)> 597 # } 598 # 599 # # Convert pdf to ps and send it to a printer. 600 # # Collect error message of pdftops and lpr. 601 # pdf_file = "paper.pdf" 602 # printer = "printer-name" 603 # err_r, err_w = IO.pipe 604 # Open3.pipeline_start(["pdftops", pdf_file, "-"], 605 # ["lpr", "-P#{printer}"], 606 # :err=>err_w) {|ts| 607 # err_w.close 608 # p err_r.read # error messages of pdftops and lpr. 609 # } 610 # 611 def pipeline_start(*cmds, &block) 612 if Hash === cmds.last 613 opts = cmds.pop.dup 614 else 615 opts = {} 616 end 617 618 if block 619 pipeline_run(cmds, opts, [], [], &block) 620 else 621 ts, = pipeline_run(cmds, opts, [], []) 622 ts 623 end 624 end 625 module_function :pipeline_start 626 627 # Open3.pipeline starts a list of commands as a pipeline. 628 # It waits for the completion of the commands. 629 # No pipes are created for stdin of the first command and 630 # stdout of the last command. 631 # 632 # status_list = Open3.pipeline(cmd1, cmd2, ... [, opts]) 633 # 634 # Each cmd is a string or an array. 635 # If it is an array, the elements are passed to Process.spawn. 636 # 637 # cmd: 638 # commandline command line string which is passed to a shell 639 # [env, commandline, opts] command line string which is passed to a shell 640 # [env, cmdname, arg1, ..., opts] command name and one or more arguments (no shell) 641 # [env, [cmdname, argv0], arg1, ..., opts] command name and arguments including argv[0] (no shell) 642 # 643 # Note that env and opts are optional, as Process.spawn. 644 # 645 # Example: 646 # 647 # fname = "/usr/share/man/man1/ruby.1.gz" 648 # p Open3.pipeline(["zcat", fname], "nroff -man", "less") 649 # #=> [#<Process::Status: pid 11817 exit 0>, 650 # # #<Process::Status: pid 11820 exit 0>, 651 # # #<Process::Status: pid 11828 exit 0>] 652 # 653 # fname = "/usr/share/man/man1/ls.1.gz" 654 # Open3.pipeline(["zcat", fname], "nroff -man", "colcrt") 655 # 656 # # convert PDF to PS and send to a printer by lpr 657 # pdf_file = "paper.pdf" 658 # printer = "printer-name" 659 # Open3.pipeline(["pdftops", pdf_file, "-"], 660 # ["lpr", "-P#{printer}"]) 661 # 662 # # count lines 663 # Open3.pipeline("sort", "uniq -c", :in=>"names.txt", :out=>"count") 664 # 665 # # cyclic pipeline 666 # r,w = IO.pipe 667 # w.print "ibase=14\n10\n" 668 # Open3.pipeline("bc", "tee /dev/tty", :in=>r, :out=>w) 669 # #=> 14 670 # # 18 671 # # 22 672 # # 30 673 # # 42 674 # # 58 675 # # 78 676 # # 106 677 # # 202 678 # 679 def pipeline(*cmds) 680 if Hash === cmds.last 681 opts = cmds.pop.dup 682 else 683 opts = {} 684 end 685 686 pipeline_run(cmds, opts, [], []) {|ts| 687 ts.map(&:value) 688 } 689 end 690 module_function :pipeline 691 692 def pipeline_run(cmds, pipeline_opts, child_io, parent_io) # :nodoc: 693 if cmds.empty? 694 raise ArgumentError, "no commands" 695 end 696 697 opts_base = pipeline_opts.dup 698 opts_base.delete :in 699 opts_base.delete :out 700 701 wait_thrs = [] 702 r = nil 703 cmds.each_with_index {|cmd, i| 704 cmd_opts = opts_base.dup 705 if String === cmd 706 cmd = [cmd] 707 else 708 cmd_opts.update cmd.pop if Hash === cmd.last 709 end 710 if i == 0 711 if !cmd_opts.include?(:in) 712 if pipeline_opts.include?(:in) 713 cmd_opts[:in] = pipeline_opts[:in] 714 end 715 end 716 else 717 cmd_opts[:in] = r 718 end 719 if i != cmds.length - 1 720 r2, w2 = IO.pipe 721 cmd_opts[:out] = w2 722 else 723 if !cmd_opts.include?(:out) 724 if pipeline_opts.include?(:out) 725 cmd_opts[:out] = pipeline_opts[:out] 726 end 727 end 728 end 729 pid = spawn(*cmd, cmd_opts) 730 wait_thrs << Process.detach(pid) 731 r&.close 732 w2&.close 733 r = r2 734 } 735 result = parent_io + [wait_thrs] 736 child_io.each(&:close) 737 if defined? yield 738 begin 739 return yield(*result) 740 ensure 741 parent_io.each(&:close) 742 wait_thrs.each(&:join) 743 end 744 end 745 result 746 end 747 module_function :pipeline_run 748 class << self 749 private :pipeline_run 750 end 751 752end 753