Ruby에서 쉘 명령을 호출하는 방법


1077

Ruby 프로그램 내부에서 쉘 명령을 어떻게 호출합니까? 그런 다음이 명령의 출력을 Ruby로 다시 가져 오려면 어떻게합니까?


3
이 질문은 유용하지만 잘 묻지 않았습니다. 루비에는 커널Open3 설명서 를 읽고 SO에서 검색 하여 잘 문서화되고 쉽게 찾을 수있는 서브 쉘을 호출하는 여러 가지 방법이 있습니다 .
Tin Man

1
슬프게도이 주제는 매우 복잡합니다. Open3( docs )는 대부분의 상황, IMO에 가장 적합한 선택이지만 이전 버전의 Ruby에서는 수정 된 PATH( bugs.ruby-lang.org/issues/8004 ) 존중하지 않으며 args를 전달하는 방법에 따라 (특히 키워드가 아닌 opts 해시를 사용하면 깨질 수 있습니다. 그러나 이러한 상황에 부딪히면 꽤 진보 된 일을하고 있으며의 구현을 읽으면 어떻게 해야할지 알 수 있습니다 Open3.
Joshua Cheek

3
아무도 언급하지 않은 것에 놀랐습니다 Shellwords.escape( doc ). 쉘 명령에 사용자 입력을 직접 삽입하고 싶지는 않습니다. 먼저 피하십시오! 명령 삽입 도 참조하십시오 .
Kelvin

답변:


1319

이 설명은 주석을 기반으로합니다 내 친구의 루비 스크립트 를 . 스크립트를 개선하려면 링크에서 자유롭게 업데이트하십시오.

먼저 Ruby가 쉘을 호출 할 때 일반적으로 /bin/sh, 하지 배쉬. 일부 /bin/sh시스템에서 일부 Bash 구문이 지원되지는 않습니다 .

쉘 스크립트를 실행하는 방법은 다음과 같습니다.

cmd = "echo 'hi'" # Sample string that can be used
  1. Kernel#` 일반적으로 백틱이라고합니다. `cmd`

    이것은 Bash, PHP 및 Perl을 포함한 다른 많은 언어와 같습니다.

    쉘 명령의 결과 (표준 출력)를 반환합니다.

    문서 : http://ruby-doc.org/core/Kernel.html#method-i-60

    value = `echo 'hi'`
    value = `#{cmd}`
  2. 내장 구문 %x( cmd )

    x문자 뒤에는 분리 문자가 있으며, 이는 임의의 문자 일 수 있습니다. 구분 기호는 문자 중 하나 인 경우 (, [, {, 또는 <, 문자 그대로 중첩 구분 쌍을 고려하여, 일치하는 닫는 구분 기호까지의 문자로 구성되어 있습니다. 다른 모든 구분 기호의 경우 리터럴은 다음에 나오는 구분 기호 문자까지의 문자로 구성됩니다. 문자열 보간 #{ ... }이 허용됩니다.

    백틱과 마찬가지로 쉘 명령의 결과 (예 : 표준 출력)를 반환합니다.

    문서 : https://docs.ruby-lang.org/en/master/syntax/literals_rdoc.html#label-Percent+Strings

    value = %x( echo 'hi' )
    value = %x[ #{cmd} ]
  3. Kernel#system

    주어진 명령을 서브 쉘에서 실행합니다.

    반환 true명령, 발견하고 성공적으로 실행 된 경우false .

    문서 : http://ruby-doc.org/core/Kernel.html#method-i-system

    wasGood = system( "echo 'hi'" )
    wasGood = system( cmd )
  4. Kernel#exec

    지정된 외부 명령을 실행하여 현재 프로세스를 대체합니다.

    아무것도 반환하지 않으면 현재 프로세스가 교체되고 계속 진행되지 않습니다.

    문서 : http://ruby-doc.org/core/Kernel.html#method-i-exec

    exec( "echo 'hi'" )
    exec( cmd ) # Note: this will never be reached because of the line above

다음은 몇 가지 추가 조언입니다 $?같은 인 $CHILD_STATUS, 당신은 역 따옴표를 사용하는 경우 마지막 시스템 실행 된 명령의 상태를 액세스, system()또는 %x{}. 그런 다음 exitstatuspid속성에 액세스 할 수 있습니다 .

$?.exitstatus

자세한 내용은 다음을 참조하십시오.


4
프로덕션 서버에 실행 파일의 출력을 기록해야하지만 방법을 찾지 못했습니다. puts #{cmd}와 logger.info ( #{cmd})를 사용했습니다. 생산물에 출력물을 기록하는 방법이 있습니까?
Omer Aslam

5
그리고 IO # popen () 및 Open3 # popen3 (). mentalized.net/journal/2010/03/08/…
hughdbrown

6
완전성을 위해서 (내가 처음 생각했던이 또한 루비 명령 것) : 레이크는 "시스템 명령을 실행 않습니다 cmd여러 인수가 주어지면 명령이 커널로 (쉘과 동일한 의미를 실행되지 않습니다 ::. exec and Kernel :: system) "를 참조하십시오.
sschuberth

40
백틱은 기본적으로 STDERR을 캡처하지 않습니다. 캡처하려면 명령에`2> & 1`을 추가하십시오
Andrei Botalov

14
backticks와 % x가 주어진 명령의 "결과"가 아닌 "출력"을 반환한다고 말하면이 대답은 약간 개선 될 것이라고 생각합니다. 후자는 종료 상태로 오인 될 수 있습니다. 아니면 저뿐입니까?
skagedal

275

24
와우 이것이 존재해야하지만 매우 유용합니다
Josh Bodah 2009

보조 노트로서, 나는 스폰 () 메소드 (많은 다른 장소에서 발견 예를 들어 찾을 수 KernelProcess가장 다재다능한로 그것은 더 많거나 적은와 동일합니다. PTY.spawn()하지만,보다 일반적인.
Smar

160

내가 좋아하는 방법은 %x리터럴을 사용하여 명령에서 따옴표를 사용하는 것이 쉽고 읽기 쉽습니다.

directorylist = %x[find . -name '*test.rb' | sort]

이 경우 현재 디렉토리 아래의 모든 테스트 파일로 파일 목록을 채우고 예상대로 처리 할 수 ​​있습니다.

directorylist.each do |filename|
  filename.chomp!
  # work with file
end

4
%x[ cmd ]배열을 당신에게 반환 합니까 ?
x-yuri

2
위의 내용은 저에게 효과적이지 않습니다. ``<메인> ': 정의되지 않은 방법 each' for :String (NoMethodError) 어떻게 작동합니까? 내가 사용하고 ruby -v ruby 1.9.3p484 (2013-11-22 revision 43786) [i686-linux]당신이 배열 명령에서 반환 확신 그래서 루프 것 실제로 작동하는지?
Nasser

% X [cmd를] .split ( "\ n") : 비록 목록을 반환
이안 엘리스

65

루비에서 쉘 스크립트를 실행하는 것에 대한 제 의견으로는 " 루비에서 쉘 명령을 실행하는 6 가지 방법 "이 있습니다.

출력 만 가져 오려면 백틱을 사용하십시오.

STDOUT 및 STDERR과 같은 고급 기능이 필요했기 때문에 Open4 gem을 사용했습니다. 거기에 설명 된 모든 방법이 있습니다.


2
여기에 설명 된 게시물은 %x구문 옵션에 대해서는 다루지 않습니다 .
Mei

Open4의 경우 +1 나는 spawn이것을 발견했을 때 이미 자체 버전의 메소드 를 구현하려고 시도 했습니다.
Brandan

40

내가 가장 좋아하는 것은 Open3

  require "open3"

  Open3.popen3('nroff -man') { |stdin, stdout, stderr| ... }

2
나는 또한 된 Open3, 특히 Open3.capture3 같은 : ruby-doc.org/stdlib-1.9.3/libdoc/open3/rdoc/... -> stdout, stderr, status = Open3.capture3('nroff -man', :stdin_data => stdin)
세브린

Ruby std-lib의 Open3 또는 다른 Open으로 스펙 및 유닛 테스트를 수행하는 방법에 대한 문서가 있습니까? 현재 이해 수준에서 셸 아웃을 테스트하기가 어렵습니다.
FilBot3

29

이러한 메커니즘 중에서 선택할 때 고려해야 할 사항은 다음과 같습니다.

  1. 당신은 stdout을 원합니까 아니면 stderr도 필요합니까? 아니면 심지어 분리?
  2. 출력이 얼마나 큽니까? 전체 결과를 메모리에 저장 하시겠습니까?
  3. 서브 프로세스가 여전히 실행 중일 때 일부 출력을 읽으시겠습니까?
  4. 결과 코드가 필요하십니까?
  5. 프로세스를 나타내는 Ruby 객체가 필요하고 필요할 때 프로세스를 종료 할 수 있습니까?

당신은 (``), 간단한 역 따옴표에서 아무것도해야 할 수도 있습니다 system(), 그리고 IO.popen날려-전체에 Kernel.fork/ Kernel.execIO.pipeIO.select.

하위 프로세스를 실행하는 데 시간이 너무 오래 걸리면 믹스에 타임 아웃을 던질 수도 있습니다.

불행히도, 그것은 매우 달려 있습니다.


25

하나 더 옵션 :

때를:

  • stdout뿐만 아니라 stderr 필요
  • Open3 / Open4를 사용할 수 없습니다 / 사용할 수 없습니다 (내 Mac의 NetBeans에서 예외를 throw합니다. 이유는 모르겠습니다)

쉘 리디렉션을 사용할 수 있습니다.

puts %x[cat bogus.txt].inspect
  => ""

puts %x[cat bogus.txt 2>&1].inspect
  => "cat: bogus.txt: No such file or directory\n"

2>&1구문 은 MS-DOS 초기부터 Linux , Mac 및 Windows 에서 작동합니다 .


25

나는 루비 전문가는 아니지만, 한 번 보여줄 것이다.

$ irb 
system "echo Hi"
Hi
=> true

또한 다음과 같은 작업을 수행 할 수 있어야합니다.

cmd = 'ls'
system(cmd)

21

위의 답변은 이미 훌륭하지만 다음 요약 기사를 공유하고 싶습니다. " Ruby에서 셸 명령을 실행하는 6 가지 방법 "

기본적으로 그것은 우리에게 알려줍니다 :

Kernel#exec:

exec 'echo "hello $HOSTNAME"'

system그리고 $?:

system 'false' 
puts $?

백틱 (`) :

today = `date`

IO#popen:

IO.popen("date") { |f| puts f.gets }

Open3#popen3 -stdlib :

require "open3"
stdin, stdout, stderr = Open3.popen3('dc') 

Open4#popen4 -보석 :

require "open4" 
pid, stdin, stdout, stderr = Open4::popen4 "false" # => [26327, #<IO:0x6dff24>, #<IO:0x6dfee8>, #<IO:0x6dfe84>]

15

Bash가 정말로 필요한 경우 "최상의"답변의 메모에 따라.

루비 쉘에게 호출 할 때 첫째, 주, 그것은 일반적으로 호출하는 /bin/sh, 하지 배쉬. 일부 /bin/sh시스템에서 일부 Bash 구문이 지원되지는 않습니다 .

Bash를 사용해야하는 경우 bash -c "your Bash-only command"원하는 호출 방법 안에 삽입 하십시오.

quick_output = system("ls -la")
quick_bash = system("bash -c 'ls -la'")

테스트하려면 :

system("echo $SHELL")
system('bash -c "echo $SHELL"')

또는 다음과 같은 기존 스크립트 파일을 실행중인 경우

script_output = system("./my_script.sh")

루비 shebang을 존중 해야 하지만 항상 사용할 수 있습니다.

system("bash ./my_script.sh")

/bin/sh실행 하는 데 약간의 오버 헤드가있을 수 있지만 확인하지 못할 수도 있습니다 /bin/bash.


11

Perl과 비슷한 백틱 연산자 (`)를 사용할 수도 있습니다.

directoryListing = `ls /`
puts directoryListing # prints the contents of the root directory

간단한 것이 필요하면 편리합니다.

어떤 방법을 사용하고 싶은지는 정확히 달성하려는 것에 달려 있습니다. 다른 방법에 대한 자세한 내용은 문서를 확인하십시오.


10

여러 가지 방법으로 달성 할 수 있습니다.

를 사용하면 Kernel#exec이 명령이 실행 된 후에는 아무것도 수행되지 않습니다.

exec('ls ~')

사용 backticks or %x

`ls ~`
=> "Applications\nDesktop\nDocuments"
%x(ls ~)
=> "Applications\nDesktop\nDocuments"

Kernel#system명령을 사용 true하면 성공 false하면 성공하고 실패 nil하면 명령 실행에 실패합니다.

system('ls ~')
=> true


9

여기에 있고 Mihai의 답변에 연결된 답변을 사용하여 다음 요구 사항을 충족시키는 기능을 구성했습니다.

  1. STDOUT과 STDERR을 깔끔하게 캡처하여 콘솔에서 스크립트를 실행할 때 "누설"되지 않도록합니다.
  2. 인수를 배열로 쉘에 전달할 수 있으므로 이스케이프에 대해 걱정할 필요가 없습니다.
  3. 명령의 종료 상태를 캡처하여 오류가 발생했을 때 명확 해집니다.

보너스로, 이것은 쉘 명령이 성공적으로 종료되고 (0) STDOUT에 무엇이든 넣는 경우 STDOUT을 리턴합니다. 이 방식에서는와 다르며 system단순히true 됩니다.

코드는 다음과 같습니다. 구체적인 기능은 system_quietly다음과 같습니다.

require 'open3'

class ShellError < StandardError; end

#actual function:
def system_quietly(*cmd)
  exit_status=nil
  err=nil
  out=nil
  Open3.popen3(*cmd) do |stdin, stdout, stderr, wait_thread|
    err = stderr.gets(nil)
    out = stdout.gets(nil)
    [stdin, stdout, stderr].each{|stream| stream.send('close')}
    exit_status = wait_thread.value
  end
  if exit_status.to_i > 0
    err = err.chomp if err
    raise ShellError, err
  elsif out
    return out.chomp
  else
    return true
  end
end

#calling it:
begin
  puts system_quietly('which', 'ruby')
rescue ShellError
  abort "Looks like you don't have the `ruby` command. Odd."
end

#output: => "/Users/me/.rvm/rubies/ruby-1.9.2-p136/bin/ruby"

9

spawn지정된 명령을 실행하기위한 백그라운드 프로세스를 작성하는 명령을 잊지 마십시오 . Process클래스와 반환 된 클래스를 사용하여 완료를 기다릴 수도 있습니다 pid.

pid = spawn("tar xf ruby-2.0.0-p195.tar.bz2")
Process.wait pid

pid = spawn(RbConfig.ruby, "-eputs'Hello, world!'")
Process.wait pid

의사는 말합니다 :이 방법은 비슷 #system하지만 명령이 끝날 때까지 기다리지 않습니다.


2
Kernel.spawn()다른 모든 옵션보다 훨씬 다재다능한 것 같습니다.
Kashyap

6

당신이 처리 할 수없는 일반적인 경우보다 더 복잡한 경우가있는 경우 ``, 다음 체크 아웃 Kernel.spawn() . 이것은 루비가 외부 명령을 실행하기 위해 제공하는 가장 일반적이고 완전한 기능인 것 같습니다.

당신은 그것을 사용할 수 있습니다 :

  • 프로세스 그룹을 작성하십시오 (Windows).
  • 파일 / 각 오류로 리디렉션, 출력, 오류.
  • env vars, umask를 설정하십시오.
  • 명령을 실행하기 전에 디렉토리를 변경하십시오.
  • CPU / data / etc에 대한 리소스 제한을 설정합니다.
  • 다른 답변에서 다른 옵션으로 수행 할 수있는 모든 작업을 수행하지만 더 많은 코드를 사용하십시오.

루비 문서는 충분한 예제가 있습니다 :

env: hash
  name => val : set the environment variable
  name => nil : unset the environment variable
command...:
  commandline                 : command line string which is passed to the standard shell
  cmdname, arg1, ...          : command name and one or more arguments (no shell)
  [cmdname, argv0], arg1, ... : command name, argv[0] and zero or more arguments (no shell)
options: hash
  clearing environment variables:
    :unsetenv_others => true   : clear environment variables except specified by env
    :unsetenv_others => false  : dont clear (default)
  process group:
    :pgroup => true or 0 : make a new process group
    :pgroup => pgid      : join to specified process group
    :pgroup => nil       : dont change the process group (default)
  create new process group: Windows only
    :new_pgroup => true  : the new process is the root process of a new process group
    :new_pgroup => false : dont create a new process group (default)
  resource limit: resourcename is core, cpu, data, etc.  See Process.setrlimit.
    :rlimit_resourcename => limit
    :rlimit_resourcename => [cur_limit, max_limit]
  current directory:
    :chdir => str
  umask:
    :umask => int
  redirection:
    key:
      FD              : single file descriptor in child process
      [FD, FD, ...]   : multiple file descriptor in child process
    value:
      FD                        : redirect to the file descriptor in parent process
      string                    : redirect to file with open(string, "r" or "w")
      [string]                  : redirect to file with open(string, File::RDONLY)
      [string, open_mode]       : redirect to file with open(string, open_mode, 0644)
      [string, open_mode, perm] : redirect to file with open(string, open_mode, perm)
      [:child, FD]              : redirect to the redirected file descriptor
      :close                    : close the file descriptor in child process
    FD is one of follows
      :in     : the file descriptor 0 which is the standard input
      :out    : the file descriptor 1 which is the standard output
      :err    : the file descriptor 2 which is the standard error
      integer : the file descriptor of specified the integer
      io      : the file descriptor specified as io.fileno
  file descriptor inheritance: close non-redirected non-standard fds (3, 4, 5, ...) or not
    :close_others => false : inherit fds (default for system and exec)
    :close_others => true  : dont inherit (default for spawn and IO.popen)

6

백틱 (`) 메소드는 Ruby에서 쉘 명령을 호출하는 가장 쉬운 방법입니다. 쉘 명령의 결과를 리턴합니다.

     url_request = 'http://google.com'
     result_of_shell_command = `curl #{url_request}`

5

다음과 같은 명령이 주어집니다 attrib:

require 'open3'

a="attrib"
Open3.popen3(a) do |stdin, stdout, stderr|
  puts stdout.read
end

이 방법이 기억에 남는 것은 아니지만

system("thecommand")

또는

`thecommand`

백틱에서는 다른 방법과 비교 하여이 방법에 대한 좋은 점은 백틱은 puts내가 명령을 변수로 실행하고 싶은 명령을 실행 / 저장 system("thecommand")하지 않고 출력을 얻지 못하는 것 같습니다. 이 방법으로 두 가지 작업을 모두 수행 할 수 있으며 stdin, stdout 및 stderr에 독립적으로 액세스 할 수 있습니다.

" ruby에서 명령 실행 "및 Ruby의 Open3 문서를 참조하십시오 .


3

이것은 실제로 대답은 아니지만 누군가가 유용하다고 생각할 수도 있습니다.

Windows에서 TK GUI를 사용하고 rubyw에서 쉘 명령을 호출해야하는 경우 항상 성가신 CMD 창이 1 초 미만으로 나타납니다.

이를 피하기 위해 다음을 사용할 수 있습니다.

WIN32OLE.new('Shell.Application').ShellExecute('ipconfig > log.txt','','','open',0)

또는

WIN32OLE.new('WScript.Shell').Run('ipconfig > log.txt',0,0)

둘 다 ipconfig출력을 내부에 저장합니다log.txt 하지만 창은 나타나지 않습니다.

당신은해야합니다 require 'win32ole' 당신의 스크립트 안에 .

system(), exec()spawn()TK와 rubyw을 사용하는 경우 모든 성가신 창이 나타납니다.


-2

다음은 OS X의 루비 스크립트에서 사용하는 멋진 것입니다 (그래서 창을 토글 한 후에도 스크립트를 시작하고 업데이트 할 수 있습니다).

cmd = %Q|osascript -e 'display notification "Server was reset" with title "Posted Update"'|
system ( cmd )
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.