2008년 1월 24일 목요일

루비로 쉘 명령 내리는 방법 6가지

루비를 공부하면서 종종 사용하다보니 이제는 왠만한 것도 다 루비로 하고 싶어졌달까... 예전에는 bash 스크립트로 간단하게 스크립트를 작성하던가 c언어의 execl같은 함수를 썼었는데 루비는 어떻게 할까 궁극해서 rdoc을 뒤지다 안되어 인터넷에서 찾은 방법을 정리하여 보았다.

Exec

irb에서 커널의 exec로 명령을 수행하는 방법


$ irb
>> exec 'echo "hello $HOSTNAME"'
hello nate.local
$

이 방법은 irb가 exec를 통해서 echo프로세스를 전달받아서 수행됨을 알 수 있다.간단하지만결과가성공됐는지아닌지를판별할방법이없다.

System

시스템(system) 명령을 사용하는 것인데 exec와의 차이는 프로세스가 그대로 전달되지 않고 subshell을 통해 수행된다는 점이다. 그래서 결과를 얻을 수 있는데 성공일 경우 true, 아닐때는 false를 반환한다


$ irb
>> system 'echo "hello $HOSTNAME"'
hello nate.local
=> true
>> system 'false'
=> false
>> puts $?
256
=> nil
>>

위에서 system이 전역변수인 $%를 프로세스의 exit상태 값으로 set함을 확인할 수 있다. false 명령의 exit 상태를 확인할 수 있는데(항상 non-zero 값이다) 이렇게 exit값을 확인하므로서 예외 처리가 가능할 수 있겠다.

system은 단지 명령의 성공여부를 알고 싶을 때는 최상이라 말할 수 있겠지만, 어떨때는 표준 출력 내용도 확인할 필요가 있을 텐데 그때는 위의 2 방법은 아니겠다.

Backticks (`)

Backticks (또는 “backquotes”)은 명령을 subshell에서 수행하고 표준 출력을 반환받아 출력한다.


$ irb
>> today = `date`
=> "Mon Mar 12 18:15:35 PDT 2007n"
>> $?
=> #<Process::Status: pid=25827,exited(0)>
>> $?.to_i
=> 0

이 방법은 유닉스에도 가장 많이 쓰이는 방법인데 명령을 subshell에서 수행하여 얻는 출력을 일반 string으로 재사용하기 쉽게할 수 있다. $%을 보면 단순히 상태 값을 반환하는 것은 아니나 실제로 Process::Status 객체임을 알 수 있다. 단순히 exit 상태 값만이 아니라 프로세스 id도 얻을 수 있는 것이다. Process::Status#to_i 는 integer값으로 exit 상태 값을 반환하고, #to_s는 exit상태를 string으로 반환한다. backticks를 사용하면 얻게되는 결과는 단순히 stdout(표준 출력)만이지 stderr는 얻을 수 없다는 것이다. 아래는 perl을 이용하여 string을 stderr로 출력하는 예이다.


$ irb
>> warning = `perl -e "warn 'dust in the wind'"`
dust in the wind at -e line 1.
=> ""
>> puts warning

=> nil

여기서 변수 warning이 set되지 않은 것을 알 수 있는데. 이것은 위에서 perl의 예에서처럼 backticks으로 얻지 못한 stderr 이다.

IO#popen

IO#popen 은 subprocess로 명령을 수행할 수 있는 또 하나의 방법이라 할 수 있겠다. popen은 조금의 추가적인 명령을 더 줄수 있는데 subprocess의 stdin과 stdout가 IO객체와 서로 연결가능하다는 것이다.


$ irb
>> IO.popen("date") { |f| puts f.gets }
Mon Mar 12 18:58:56 PDT 2007
=> nil

IO#popen 가 괜찮긴 하지만 아래에 소개할 Open3#popen3이 더욱 세밀한 제어가 가능할 듯 하다.

Open3#popen3

루비 표준 라이브러리에 포함된 Open3 클래스는 사용이 아주 간단하고 stdin, stdoutstderr을 다 반환한다. 아래의 예에서는 질의식(Interactive) 명령인 dc를 이용한다. dc는 RPN(Reverse Polish Notation) 계산기로 stdin으로 입력을 받는다. 2개의 숫자와 연산자를 스택에 push하고 p 명령을 이용하여 결과를 얻는 예이다.


$ irb
>> stdin, stdout, stderr = Open3.popen3('dc')
=> [#<IO:0x6e5474>, #<IO:0x6e5438>, #<IO:0x6e53d4>]
>> stdin.puts(5)
=> nil
>> stdin.puts(10)
=> nil
>> stdin.puts("+")
=> nil
>> stdin.puts("p")
=> nil
>> stdout.gets
=> "15n"

여기서 우리는 출력을 읽을 수 있는 것만이 아닌 입력을 쓸 수 있음을 알 수 있다. 이렇게 Open3 객체로 유연성 높은 쉘 명령 사용이 가능하겠다. popen3로 stderr도 확인이 가능한데...


# (irb continued...)
>> stdin.puts("asdfasdfasdfasdf")
=> nil
>> stderr.gets
=> "dc: stack emptyn"

다만 1.8.5버젼의 루비에서는 exit 상태 값이 $?로 제대로 반환되지 않는 문제가 있다.


$ irb
>> require "open3"
=> true
>> stdin, stdout, stderr = Open3.popen3('false')
=> [#<IO:0x6f39c0>, #<IO:0x6f3984>, #<IO:0x6f3920>]
>> $?
=> #<Process::Status: pid=26285,exited(0)>
>> $?.to_i
=> 0

0? false는 반드시 non-zero인 exit 상태값을 반환해야한다! 이 결점이 우리를 Open4 객체로 안내한다고 볼 수 있겠다.

Open4#popen4

Open4#popen4 은 루비 젬에서 Ara Howard에 의해 추가됐다. Open3와 동작이 같은데, 단지 exit 상태 값을 반환 받을 수 있다는게 다르다. popen4는 subshell의 프로세스 id를 반환하고 exit 상태 값을 waiting 상태에서 받을 수 있다. (gem install open4 로 설치가 가능)


$ irb
>> require "open4"
=> true
>> pid, stdin, stdout, stderr = Open4::popen4 "false"
=> [26327, #<IO:0x6dff24>, #<IO:0x6dfee8>, #<IO:0x6dfe84>]
>> $?
=> nil
>> pid
=> 26327
>> ignored, status = Process::waitpid2 pid
=> [26327, #<Process::Status: pid=26327,exited(1)>]
>> status.to_i
=> 256

Open4의 좋은 점은 popen4를 하나의 block으로 호출할 수 있고 자동으로 반환 상태를 기다리는 대기가 가능하다는 점이다.


$ irb
>> require "open4"
=> true
>> status = Open4::popen4("false") do |pid, stdin, stdout, stderr|
?> puts "PID #{pid}"
>> end
PID 26598
=> #<Process::Status: pid=26598,exited(1)>
>> puts status
256
=> nil

이상으로 6가지의 쉘 명령 방법을 정리하였는데 명령어의 이름에서도 볼 수 있듯이 일반적인 unix의 명령과 c의 함수와 이름이 유사하여 사용하거나 기억하기 편한 장점과 루비 특유의 순수 객체지향 언어 다움의 간결함으로 앞으로 쉘 스크립트 명령을 사용하는데에 루비의 사용이 많이 증가할 듯 하다는 생각이 든다. 출처 : http://pasadenarb.com/2007/03/ruby-shell-commands.html

댓글 없음: