Menu

문서정보

목차

보강 해야 할 것들

소개

책을 사기는 귀찮고, 언어에 대한 기본적인 이해는 있다고 생각되니 Tutorial로 쉽게 언어를 익히는게 좋겠다고 생각. Tutorial 문서를 찾아서 구글 검색을 해봤더니.. Ruby Tutorial이 첫번째로 나온다.

이것 저것 생각하기 귀찮아서 이 문서로 공부 해 보기로 했다.

공부 환경

언제나 그렇듯이 우분투 리눅스환경이다. 현재(2013년 7월) 우분투 버전은 13.04 Ruby 버전은 1.9.4다.
$ cat /etc/issue
Ubuntu 13.04 \n \l
$ ruby --version
ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-linux]
우분투 데스크탑 버전을 설치했더니, 기본 패키지로 깔려있다. 리눅스짱.

Hello World

루비는 Perl, Python같은 interpreted language다. 컴파일 과정없이 해석기를 이용해서 즉시 실행하고 그 결과를 확인해 볼 수 있다.. 먼저 hello world를 찍었다.
#!/usr/bin/ruby
print "Hello World\n"
print는 문자열을 표준출력하기 위해서 사용하는 ruby 내장 함수다.

실행 방법은 두 가지다.
  1. ruby를 호출해서 hello.rb를 실행하는 방법
  2. 두번째는 hello.rb에 실행권한을 준다. 파일에 실행권한이 있으면, shell은 첫번째 라인에 명시된 프로그램을 호출해서 코드를 실행한다. 이 경우에 /usr/bin/ruby 를 호출한다.
    # ruby hello.rb
    
    # chmod +x hello.rb
    # ./hello.rb

변수

루비에서 사용하는 변수다. 다른 언어들과는 변수의 사용 법에 차이가 있으니 간단히 훑어보고 넘어가도록 하자.

일반 변수들

$ 전역 변수
@ 인스턴스 변수 이해하기 쉽게 클래스 멤버 변수
![a-z_!] 지역 변수
![A-Z!] 상수
루비는 변수의 이름 형태만으로, 변수영역을 구분할 수 있다. 익숙해지면, 상당히 편리한 변수명명법이라는 걸 알 수 있다.
NAME = "KIM"
$name = 'yundream'
def foo 
    name="shawn"
    puts name
    puts $name
    puts NAME
end

foo

실행 결과
shawn
yundream
KIM

변수 NAME은 상수이기 때문에, 다음과 같이 사용할 경우 에러가 발생한다.
NAME = "KIM"

def foo
    NAEM ="YUN"
end

foo

실행 결과
./foo.rb:6: dynamic constant assignment
        NAEM ="YUN"

특수 변수들

$! 마지막 에러 메시지
$@ 에러 위치
$_ 가장 최근에 gets로 읽은 문자열
$. 코드의 줄 번호
$& regexp로 마지막에 매칭된 문자열
$~ the last regexp match, as an array of subexpressions
$n n번째로 매칭된 문자열
$= case-insensitivity flag
$/ input record separator
$\ output record separator
$0 실행 프로그램 이름
$* 명령 행 인자들
$$ 프로세스 아이디
$? 최근 실행한 자식 프로세스의 종료 번호
ARGV[n] n 번째 명령 행 인자
$DEBUG 디버깅 메시지 -d를 키면 활성화
$stderr 표준에러
$stdin 표준입력
$stdout 표준출력
$: 스크립트가 로드한 모듈 이름들
예제 프로그램
#!/usr/bin/ruby

print "Script Name : ", $0, "\n"
print "Process ID : ", $$, "\n"

ARGV.each do |a| 
  print "Argument ", a, "\n"
end

x = 10
puts defined? x

$x = 10
puts defined? x

주석

"#" 이후의 문자들은 주석으로 처리한다.
presidents = ["Ford", "Carter", "Reagan", "Bush1", "Clinton", "Bush2"]
presidents.each {|prez| puts prez} # 배열내의 모든 원소를 가져온다. 

"=begin"과 "=end"를 이용해서 일정 블럭을 주석처리할 수 있다.
=begin
이 코드는 테스트 코드다.
만든 사람 : ooo
함수이름 : foo
하는 일 : foo 문자열 출력 
=end
def foo
  puts "foo"
end

Iterators and Blocks

Iterator는 데이터를 순환하기 위한 순환객체로 이해하면 되겠다. 루비는 배열도 Iterator을 가지고 있습니다. 앞서 살펴봤던 .each다.
#!/usr/bin/ruby
presidents = ["Ford", "Carter", "Reagan", "Bush1", "Clinton", "Bush2"]
presidents.each {|prez| puts prez}

실행 결과
Ford
Carter
Reagan
Bush1
Clinton
Bush2

아래와 같이 사용할 수도 있다.
#!/usr/bin/ruby
presidents = ["Ford", "Carter", "Reagan", "Bush1", "Clinton", "Bush2"]
presidents.each do |prez|
	puts prez
end
개인적으로 do/end를 주로 사용한다. C/C++/Perl/Bash를 사용해 왔기 때문에 좀 더 명료해 보이기 때문이다.

i=-100
puts "Before i : "+i.to_s
(1...10).each{|i| puts i}
puts "After i :"+i.to_s
모든걸 객체로 다루기 때문에 이런 응용도 가능하다. 예컨데 (1...10)이런 것도 객체로 다룰 수 있다.

Before: -99
1
2
3
4
5
6
7
8
9
10
After : 10

{}과 do/end의 차이

요것 {} 과 do/end 사이에 차이가 있다.
#!/usr/bin/ruby
my_array = ["alpha", "beta", "gamma"]
puts my_array.collect { |word|
    word.capitalize
}
puts "======================"
puts my_array.collect do |word|
    word.capitalize
end
.capitalize는 문자열의 첫 번째 글자를 대문자로 변환한다.
Alpha
Beta
Gamma
======================
alpha
beta
gamma

{} 는 Iterator의 결과를 적용한다고 합니다. do/end를 사용하면 Iterator에만 적용을 한다고 합니다.

Iterator에 적용한 결과를 new_array에 복사하도록 수정했습니다.
my_array = ["alpha", "beta", "gamma"]
puts my_array.collect {
    |word|
    word.capitalize
}
puts "======================"
new_array = my_array.collect do
    |word|
    word.capitalize
end
puts new_array
Alpha
Beta
Gamma
======================
Alpha
Beta
Gamma
음.. 그래서 iterator의 수행 결과를 직접 적용하려면 {}를 적용한 결과를 따로 저장하려면 do/end를 사용하는게 더 나을 까요 ? 라는 생각이

루프

루프를 이용해서 작업을 반복할 수 있다.

while Loop

while conditional [do]
   code
end
while이라면 너무 친숙하다. C/C++/perl/bash/python을 비롯한 모든 언어에서 지원하는 그 while이다. conditional이 참인동안 code를 실행한다.

#!/usr/bin/ruby

$i = 0
$num = 5

while $i < $num  do
   puts("Inside the loop i = #$i" )
   $i +=1
end

Inside the loop i = 0
Inside the loop i = 1
Inside the loop i = 2
Inside the loop i = 3
Inside the loop i = 4

while modifier

code while condition

혹은

begin
  code
end while condition 

#!/usr/bin/ruby

$i = 0
$num = 5
begin
   puts("Inside the loop i = #$i" )
   $i +=1
end while $i < $num

until

until conditional [do]
   code
end

for

for variable [, variable ...] in expression [do]
   code
end
expression이 참인 동안 code를 수행한다. 이때 expression의 반환 값은 변수 variable에 저장되며, code 에서 사용할 수 있다.

for ss in 1...10
	print "NUMBER : ", ss,"\n" 
end
"..."은 범위를 나타내기 위해서 사용한다. 1...10은 "1에서 9까지"를 의미한다."

for문은 each문으로 표현할 수도 있다.
(expression).each do | variable | 
	code 
end

예제
(1...10).each do | i |
	print "NUMBER : ", i,"\n" 
end
위의 for 문을 사용한 것과 같은 결과를 출력한다.

break

루프를 종료하고 빠져나가기 위해서 사용한다.
#!/usr/bin/ruby

for i in 0..5
   if i > 2 then
      break
   end
   puts "Value of local variable is #{i}"
end

Value of local variable is 0
Value of local variable is 1
Value of local variable is 2

next

루프를 건너뛰고 다음 순환을 시작한다.
#!/usr/bin/ruby

for i in 0..5
   if i < 2 then
      next
   end
   puts "Value of local variable is #{i}"
end

Value of local variable is 2
Value of local variable is 3
Value of local variable is 4
Value of local variable is 5

redo

redo를 만나면 루프를 다시 시작한다.
#!/usr/bin/ruby

for i in 0..5
   if i < 2 then
     puts "Value of local variable is #{i}"
     redo
   end
end

Value of local variable is 0
Value of local variable is 0
....

조건문

루비는 현대적인 언어가 제공하는 모든 조건문을 제공한다. 여기에서는 루비에서 사용하는 조건문들을 설명한다.

if

if conditional [then]
	code ...
elsif conditional [then]
	code ...
else
	code ...
end
ifconditional을 평가해서 참(true)이면 코드를 실행한다. 평가 결과가 거짓(false)혹은 nil이라면 else에 있는 코드를 실행한다. elsif를 이용해서 여러개의 조건을 평가할 수 있다.

#!/usr/bin/ruby

x=1
if x > 2
   puts "x is greater than 2"
elsif x <= 2 and x!=0
   puts "x is 1"
else
   puts "I can't guess the number"
end

x is 1

if modifier

code if condition
condition이 참이면 code를 실행한다. 펄을 경험했다면, 익숙한 문법일 것이다.

$debug = 1
puts "Debug" if $debug

unless

unless conditional [then]
   code
[else
   code ]
end
conditional 평가가 거짓일 경우 코드를 실행하고, 참일경우 else 코드를 실행한다. if와 반대라고 보면 된다.

x=1
unless x>2
   puts "x is less than 2"
 else
  puts "x is greater than 2"
end

x is less than 2

unless modifier

code unless conditional
conditional이 거짓이면 code를 실행한다.

$var =  1
print "1 -- Value is set\n" if $var
print "2 -- Value is set\n" unless $var

$var = false
print "3 -- Value is set\n" unless $var

case

case expression
[when expression [, expression ...] [then]
   code ]...
[else
   code ]
end
case를 expression으로 평가해서 실행한다. 평가 할 때는 "=="이 아닌 "==="을 사용하는 것에 주의해야 한다.

expression에는 컴마(,)를 이용해서 하나 이상의 평가식을 넣을 수 있다. 이때 평가식끼리는 or 연산을 수행한다. 예를 들어
case expr0
when expr1, expr2
   stmt1
when expr3, expr4
   stmt2
else
   stmt3
end
를 if 문으로 풀어보면 아래와 비슷하다.
_tmp = expr0
if expr1 === _tmp || expr2 === _tmp
   stmt1
elsif expr3 === _tmp || expr4 === _tmp
   stmt2
else
   stmt3
end

#!/usr/bin/ruby

$age =  5
case $age
when 0 .. 2
    puts "baby"
when 3 .. 6
    puts "little child"
when 7 .. 12
    puts "child"
when 13 .. 18
    puts "youth"
else
    puts "adult"
end

Containers

컨테이너는 데이터를 보관하기 위한 객체들을 일컫는다. 예컨데 C++의 STL 컨테이너에서 제공하는 vector, list, map, set등의 자료구조로, 자료구조를 일반화 한 거라고 생각하면 되겠다.

루비는 배열, hash 두개의 기본 컨테이너를 제공한다.

배열 - Array

ruby에서 배열은 객체다. 배열에서 제공하는 몇 가지 메서드들만 알면 쉽게 배열을 조작할 수 있다.
#!/usr/bin/ruby
presidents = ["Ford", "Carter", "Reagan", "Bush1", "Clinton", "Bush2"]
last = presidents.pop
presidents.pop
presidents.pop
presidents.each { |i| print i, "\n"}
puts "==========="
print "Last : ",last,"\n"
pop 메서드는 배열의 마지막 원소를 꺼낼 때 사용한다. pop로 꺼낸 원소는 다른 변수에 저장할 수도 있다.
Ford
Carter
Reagan
===========
Last : Bush2

unshift를 이용하면 고정크기의 queue에 원소를 집어 넣는 작업을 할 수 있다.
#!/usr/bin/ruby
presidents = ["Ford", "Carter", "Reagan", "Bush1", "Clinton", "Bush2"]
presidents.pop
presidents.pop
presidents.pop
presidents.unshift("Nixon")
presidents.unshift("Johnson")
presidents.unshift("Kennedy")
presidents.each { |i| print i, "\n"}
unshift를 하면 첫번째 배열로 값이 들어가고, 뒤의 모든 배열들은 하나씩 밀립니다 . 크기가 고정돼 있기 때문에 마지막 배열의 원소는 배열에서 제거되겠죠.

그러니 아래와 같은 결과가 나옵니다.
Kennedy
Johnson
Nixon
Ford
Carter
Reagan

이렇게 해도 되요.
#!/usr/bin/ruby
presidents = ["Ford", "Carter", "Reagan", "Bush1", "Clinton", "Bush2"]
presidents.pop
presidents.pop
presidents.pop
presidents.unshift("Kennedy", "Johnson", "Nixon")
presidents.each { |i| print i, "\n"}

이 외에 아래의 메서드를 지원합니다. 필요에 따라서 적절히 사용하세요.
메서드 설명 매개변수 반환 값
push 배열의 끝에 원소 추가 원소 배열의 모든 원소 출력
pop 배열의 마지막 원소 꺼내고 삭제함 없음 마지막 원소
shift pop의 반대, 배열의 첫번째 원소를 꺼냄 뒤의 모든 원소들은 앞으로 이동 없음 첫번째 원소
unshift 배열의 첫번째에 원소를 추가 모든 원소들은 뒤로 이동, 마지막 원소 삭제 원소 배열의 모든 원소
다른 인터프리터 언어들이 그렇듯이 배열은 자유롭게 조작할 수 있습니다. C/C++에서는 이래 저래 제약이 심하죠.
#!/usr/bin/ruby
presidents = []
presidents[2] = "Adams"
presidents[4] = "Madison"
presidents[6] = "Adams"
presidents.each {|i| print i, "\n"}
print "=======================\n"
presidents[6] = "John Quincy Adams"
presidents.each {|i| print i, "\n"}
print "\n"
배열의 원소는 순서에 관계없이 자유롭게 삽입이 가능합니다. 자유롭게 삽입할 경우 배열에 빈곳이 생길 건데, 이경우 nil로 초기화 해줍니다. nil은 사람이름이 아니고 null의 다른 이름입니다.
nil
nil
Adams
nil
Madison
nil
Adams
=======================
nil
nil
Adams
nil
Madison
nil
John Quincy Adams

배열의 일부를 다른 배열로 복사하는 코드입니다.
#!/usr/bin/ruby
presidents = ["Ford", "Carter", "Reagan", "Bush1", "Clinton", "Bush2"]
p123=presidents[1..3]
p123.each { |i| print i, "\n"}

대략 감이 잡힐 겁니다. presidents 배열의 1,2,3 원소를 p123에 복사하라는 명령입니다. "..."은 범위를 의미합니다.
Carter
Reagan
Bush1

"..."은 점을 무려 3개나 찍어야 합니다. 이게 귀찮다면 ","를 써도 됩니다.
#!/usr/bin/ruby
presidents = ["Ford", "Carter", "Reagan", "Bush1", "Clinton", "Bush2"]
p123=presidents[1,3]
p123.each { |i| print i, "\n"}

자유로운 배열 조작을 이용해서 배열의 일부를 치환하는 코드도 간단히 만들 수 있습니다.
#!/usr/bin/ruby
numbers = ["one", "two", "3", "4", "5", "seven"]
numbers.each { |i| print i, "\n"}
print "=====================\n"
numbers[2,3]=["three", "four", "five", "six"]
numbers.each { |i| print i, "\n"}
number에는 one, two, 3, 4, 5, seven의 원소가 들어가 있다. 3, 4, 5를 three, four, five로 치환하는 코드다. 참 쉽다.

아래는 배열의 중간에 추가하는 코드입니다.
#!/usr/bin/ruby
numbers = ["one", "two", "five"]
numbers.each { |i| print i, "\n"}
print "=====================\n"
numbers[2,0]=["three", "four"]
numbers.each { |i| print i, "\n"}

치환이 아닌 삽입임.
one
two
five
=====================
one
two
three
four
five

괄호를 이용한 범위를 배열에 입력할 수도 있습니다.
#!/usr/bin/ruby
myArray = (0..9)
myArray.each{|i| puts i}

중요한 것은 이러 이러하게 사용할 수 있다는게 아닙니다. 자유스럽게 사용할 수 있다는 게 중요하죠. 상상력을 발휘하세요. 0 1 2 3 4 5 6 7 8 9

루비는 모든 것을 객체로 다룹니다. 배열도 마찬가지구요. 그래서 아래와 같은 코드를 만들 수 있습니다. 객체를 직접 만듦으로서 좀 더 객체지향적으로 보이는 코드입니다.
numbers = Array.new
numbers[3] = "Three";
numbers[4] = nil

print "Class =", numbers.class,"\n"
print "Length = ", numbers.length, "\n"
numbers.each{ |i| print i, "\n"}

Hash

Python은 hash를 원시 자료형으로 지원합니다. hash는 {key=>value}의 쌍으로 이루어지는 자료구조인데, 배열과는 달리 문자열 객체를 사용할 수 있죠. 문자열 뿐만 아니라 다른 객체들도 사용할 수 있을 겁니다. 아마도 ?. 이건 확인을 해봐야 할것 같습니다.

Hash 사용은 간단합니다. PHP, Perl, Python.. 언어를 막론하고 Hash 사용방법이 대동소이하거든요.
litt = {"lname"=>"Litt", "fname"=>"Steve", "ssno"=>"123456789"}
print "Lastname              : ", litt["lname"], "\n"
print "Firstname             : ", litt["fname"], "\n"
print "Social Security Number: ", litt["ssno"], "\n"
print "\n"
litt["gender"] = "male"
litt["ssno"] = "987654321"
print "Corrected Social Security Number: ", litt["ssno"], "\n"
print "Gender                          : ", litt["gender"], "\n"
print "\n"
print "Hash length is ", litt.length, "\n"
print "Hash class  is ", litt.class, "\n"
Lastname              : Litt
Firstname             : Steve 
Social Security Number: 123456789

Corrected Social Security Number: 987654321
Gender                          : male

Hash length is 4
Hash class  is Hash

위의 hash는 개인의 정보를 저장합니다. 아주 간단한 데이터베이스라고 할 수 있을 건데요. 데이터베이스라고 하기엔 좀 미안하죠. 데이터 베이스라면 여러개의 레코드를 저장할 수 있어야 하겠죠. hash의 hash를 이용하면 좀더 그럴듯한 데이터베이스를 만들 수 있을 겁니다.

people = {
  "torvalds"=>{"lname"=>"Torvalds", "fname"=>"Linus", "job"=>"maintainer"},
  "matsumoto"=>{"lname"=>"Matsumoto", "fname"=>"Yukihiro", "job"=>"Ruby originator"},
  "litt"=>{"lname"=>"Litt", "fname"=>"Steve", "job"=>"troubleshooter"}
  }

keys = people.keys

for key in 0...keys.length
  print "key  : ", keys[key], "\n"
  print "lname: ", people[keys[key]]["lname"], "\n"
  print "fname: ", people[keys[key]]["fname"], "\n"
  print "job  : ", people[keys[key]]["job"], "\n"
  print "\n\n"
end
이름을 key로 하고, value를 hash로 했습니다. 이렇게 해서 여러 개의 레코드를 가지는 데이터베이스 프로그램을 만들었습니다. 사용하기 편하기는 한데.. 음.. C의 구조체 처럼 좀 명시적으로 코드를 만들면 어떨까라는 생각이 드네요. value가 객체가 되도 상관 없을 려나.. 한번 테스트를 해보기로 했습니다.
class Person
  attr_accessor :lname, :fname, :job
  def initialize(alname, afname, ajob)
    @lname =alname
    @fname =afname
    @job = ajob
  end
end

people = Hash.new 
people["torvalds"] = Person.new("torvalds","Linus", "maintainer")
keys = people.keys

for key in 0...keys.length
  print "key      : ", keys[key], "\n"
  print "lname  : ", people[keys[key]].fname, "\n"
  print "Job      : ", people[keys[key]].job, "\n"
  print "\n\n"
end
네, 당연히 잘 되네요. hash의 hash보다 훨씬 보기가 좋습니다. 실수할 가능성도 줄어들고요.

Hash 정렬

hash는 key 혹은 value를 가지고 정렬할 수 있습니다.
#!/usr/bin/ruby -w 

h = Hash.new
h['size'] = 'big'
h['color'] = 'reg'
h['brand'] = 'ford'

av = h.sort{|a,b| a[1] <=> b[1]}
ak = h.sort{|a,b| a[0] <=> b[0]}

ak.each do
  |pair|
  print pair[0], "=>", pair[1]
  puts
end

puts "==================="

av.each do
  |pair|
  print pair[0], "=>", pair[1]
  puts
end
음.. 굉장히 간단한거 같지만 왠지 헛갈립니다. sort는 개발자가 함수를 정의해서 사용합니다. C++의 STL(:12)에서 map 이나 set 같은 컨테이너에 대한 경험이 있다면, 쉽게 이해 하실 수 있을 겁니다. 여기에 들어가는 함수는 결국 비교 연산입니다. 여기에서는 두 개의 매개 면수 a와 b를 비교하고 있습니다. av는 a[1]과 b[1]을 비교하고 있는데요. 즉 value를 가지고 비교를 하겠다 그런의미가 되겠습니다. 비교를 해서 a가 더크면 1, b가 더 크면 -1 같으면 0을 반환합니다. ruby는 이 반환값을 이용해서 정렬합니다.

사용자 정의 함수를 넣을 수 있을 건데, 그 방법은 아직 모르겠구요. 나중에 찾게 되면 테스트해보고 적을 생각입니다.

Hash 내용을 확인하고 테스트 하기

hah에 key가 있으면 true를 반환한다. hah에 value가 value인 원소가 있으면 true를 반환한다. hah가 비어 있으면 true를 반환한다. value를 가진 hah의 key를 반환한다. 만약 존재하지 않으면 기본 값을 반환한다. hah의 key와 value를 서로 바꾼다. hah의 원소 (key/value 쌍)의 개수를 반환한다.

string

Ruby는 문자열도 객체로 다음과 같은 사용이 가능하다.
"yundream".length  #=>8

대입연산자

문자열 대입과 관련된 연산을 보자.
#!/usr/bin/ruby

myname = "yundream"
myname_cpy = myname

myname_copy = myname
print "myname      = ", myname, "\n"
print "myname_copy = ", myname_copy, "\n"
print "\n=========================\n"
myname << "t"
print "myname      = ", myname, "\n"
print "myname_copy = ", myname_copy, "\n"
변수 mynamemyname_cpy모두 "yundream"으로 결과가 나온다.

string에서 대입연산자는 value의 복사가 아닌 레퍼런스의 복사이기 때문에 이런 결과가 나온다. 레퍼런스가 아닌 독립된 두 개의 객체로 다루기 위해서는 String.new를 이용해야 한다.
#!/usr/bin/ruby
myname = "Steve Lit"
myname_copy = String.new(myname)
print "myname      = ", myname, "\n"
print "myname_copy = ", myname_copy, "\n"
print "\n=========================\n"
myname << "t"
print "myname      = ", myname, "\n"
print "myname_copy = ", myname_copy, "\n"

myname      = Steve Lit
myname_copy = Steve Lit

=========================
myname      = Steve Litt
myname_copy = Steve Lit
[slitt@mydesk slitt]$

문자열 치환

#!/usr/bin/ruby
myname = "Steve was here"
print myname[6, 3], "\n"
myname[6, 3] = "is"
print myname, "\n"

was
Steve is here

문자열을 치환해 보자. 문자열의 치환은 다음의 두 단계를 거친다.
  1. 먼저 find 메서드를 이용해서 문자열의 위치를 찾는다.
  2. replace메서드를 이용해서 문자열을 대체한다.
    #!/usr/bin/ruby
    
    myname = "Steve was here"
    puts myname
    
    nidle = "was"
    start_pos = myname.index(nidle)
    myname[start_pos, nidle.length] = "is"
    puts myname

문자열 더하기

+ 연산자를 이용해서 문자열을 붙일 수 있다.
#!/usr/bin/ruby

myname = "My"+" "+"name" + " " + "is" + " " + "yundream"
puts myname

*연산도 됩니다. 문자열이 반복 복사된다.
#!/usr/bin/ruby
mystring = "Cool " * 3
print mystring, "\n"

Cool Cool Cool

형식화된 출력

C 스타일의 형식화된 출력도 지원한다.
mystring = "There are %6d people in %s " % [1500, "the Grand Ballroom"]
puts mystring #=>There are   1500 people in the Grand Ballroom

문자열 비교

문자열 비교도 가능합니다.
print "frank" <=> "frank", "\n"
print "frank" <=> "fred", "\n"  
print "frank" <=> "FRANK", "\n"

0
-1
1
같으면 0, 작으면 -1, 크면 1을 반환한다.

"==" 연산자로 문자열을 비교할 수 있다. "=="연산자는 truefalse 둘 중 하나를 반환한다.
print "frank" == "frank", "\n"   #=>true
print "frank" == "fred", "\n"    #=>false
print "frank" == "FRANK", "\n"   #=>false

기타 string과 관련된 주요 메서드들이다. (카테고리 별로 따로 정리해야하겠다.)
string.capitalize 문자열의 첫번째 문자를 대문자로 변경합니다.
string.chomp 문자열 마지막의 CR LF 문자를 삭제한 문자열을 반환 합니다.
string.index(substr) substr이 처음 발견된 위치
string.rindex(substr) substr이 마지막 발견된 위치
string.reverse 문자열을 뒤집습니다.
string.split(pattern, limit) pattern으로 문자열을 나눠서 배열로 만듭니다.
string.upcase 문자열의 모든 문자를 대문자로 변환합니다.
string.downcase 문자열의 모든 문자를 소문자로 변환합니다.
string.strip 문자열의 처음과 마지막의 공백문자 CR RF를 삭제합니다.
string.to_f 문자열을 부동 소숫점 숫자로 변환
string.to_i 문자열을 정수로 변환
chomp, reverse, upcase, downcase 메서드는 각각 chmop!, reverse!, upcase!, downcase! 메서드들도 있다. 메서드 이름뒤에 !이 붙으면 in place 즉, 객체를 직접 변경한다.

정규표현

정규표현은 암호같기도 해서 한 눈에 들어오지 않는다. 코드를 돌려보기 전에는 원하는 결과 나올지 예상하기가 쉽지 않다. 한마디로 익히고 쓰기가 까다롭다. 하지만 일단 정규표현을 익히고 나면, 정규표현 없이 어떻게 문자 데이터를 처리해왔나라는 생각이 들게 마련이다. 여기에서는 정규표현식에 자체에 대한 내용은 다루지 않는다. 정규표현 관련 내용은 링크를 참고한다. 정규표현은 언어를 막론하고 대략 비슷해서, 한번 익혀 놓으면 두고두고 써먹을 수 있다. 특히 Perl을 주로 사용해왔다면 쉽게 사용할 수 있다.

#!/usr/bin/ruby
string1 = "Steve was here"
print "find pattern : e.*e\n" if string1 =~ /e.*e/
print "find pattern : Sh.*e\n" if string1 =~ /Sh.*e/
간단히 설명하자면 "e다음에 아무 문자가 하나이상 오고 그다음에 e가 오면 만족하는 패턴"이다. ee, e123e, eabce 이런 매턴에 일치한다.

문자열 패턴을 찾아서 조건을 분기해서 처리하는 일반적인 방법이다.
#!/usr/bin/ruby
string1 = "I will drill for a well in walla walla washington."
if string1 =~ /(w.ll)/
    print "Matched on ", $1, "\n"
else
    puts "NO MATCH"
end
일치된 패턴을 가진 문자열은 $1, $2... $n으로 가져올 수 있다.
Matched on will

Regexp 클래스를 이용 정규표현식을 다룰 수도있다.
#!/usr/bin/ruby
string1 = "I will drill for a well in walla walla washington."
regex = Regexp.new(/w.ll/)
matchdata = regex.match(string1)
if matchdata
  puts matchdata[0]
  puts matchdata[1]
else
  puts "NO MATCH"
end
위 코드는 그리 깔끔하지는 않다. 매칭된 문자열의 개수만큼 루프를 돌면서 출력하는게 낫겠다. for 루프를 이용해서 코드를 다듬었다. 그리고 정규표현식도 좀 다양하게 준비했다. 어떤 결과가 나오는지 직접 실행해서 확인해 보기 바란다
#!/usr/bin/ruby
string1 = "I will drill for a well in walla walla washington."
regex = Regexp.new(/(w.ll).*(in).*(w.ll)/)
matchdata = regex.match(string1)
if matchdata
	for ss in 0...matchdata.length
		puts matchdata[ss]
	end
else
	puts "NO MATCH"
end

will drill for a well in walla wall
will
in
wall
배열의 0번째 값은 매턴매칭된 문자열이다. 1, 2, 3은 각각 괄호에 매칭된 값들이다. 특정한 패턴의 문자열을 꺼낼 때 유용하게 사용할 수 있다.

/w.ll/에 매칭되는 모든 문자열을 가져오고 싶다면 아래와 같이 하면 된다. perl 정규표현식으로 하자면 /w.ll/g 정도가 되겠다.
#!/usr/bin/ruby
string1 = "I will drill for a well in walla walla washington."
regex = Regexp.new(/w.ll/)
matchdata = regex.match(string1)
while matchdata != nil 
  puts matchdata[0]
  string1 = matchdata.post_match
  matchdata = regex.match(string1)
end
post_match 메서드를 이용해서 다음 매칭되는 패턴 문자열을 찾는 방식이다.

치환

#!/usr/bin/ruby
string1 = "I will drill for a well in walla walla washington."
string1.gsub!(/(w.ll)/){$1.upcase}
puts string1

위코드는 string1에서 패턴에 일치하는 문자열을 대문자로 바꾼다.
I WILL drill for a WELL in WALLa WALLa washington.

서브루틴

서브루틴은 def로 시작하며 end로 끝난다. return 키워드를 이용해서 값을 반환할 수 있다 . 서브루틴에서 선언되는 변수들은 모두 지역변수다.
#!/usr/bin/ruby
def passback
	howIfeel="good"
	return howIfeel
end

howIfeel="excellent"
puts howIfeel
mystring = passback
puts howIfeel
puts mystring

매개변수는 괄호로 정의하면 된다.
#!/usr/bin/ruby
def mult(one, two)
  return one * two
end

num1 = 4
num2 = 5
result = mult(4, 5)

print "num1 is ", num1, "\n"
print "num2 is ", num2, "\n"
print "result is ", result, "\n"

예외

C에서 에러 핸들은 함수의 반환 값을 검사하는 방식으로 이루어진다. 이 방식으로 에러처리하는 것은 복잡다단하기 때문에 왠만큼 부지런하지 않고서는 모든 부분에 대한 에러 처리가 쉽지가 않다. 세심하게 신경쓰지 않고서는 견고한 코드를 만들기가 여간 까다롭지가 않다.

현대적인 언어들은 예외로 에러를 처리하는 방법을 사용한다. 루비역시 마찬가지.

예외 처리에는 두 가지 방법이 있다. handle an exceptionraise an exception이 그것이다. 루비에서 제공하는 대부분의 시스템 호출은 예외를 발생하는 데, 이것이 handle an exception입니다. 이 외에 프로그래머가 직접 예외를 만들 수도 있는데 이를 rasie an exception 이라고 한다.

다음은 handle an excepiton 처리의 일반적인 방법이다.
#!/usr/bin/ruby
begin
	input = File.new("/etc/resolv.conf", "r")
	input.each {
		|i|
		puts i;
	}
	input.close()
rescue 
	print "Failed to open /etc/fstab for input. ", $!, "\n"
end	
begin 영역에 평가하려는 코드를 둔다. 이 영역에서 예외가 발생하면 rescue 영역의 코드가 실행이 된다. rescue 영역에는 적절한 에러 처리 코드를 두면 되겠다.

만약 존재하지 않는 파일을 열려고 하면 다음과 같이 에외 코드를 실행한다.
Failed to open /etc/fstab for input. No such file or directory - /etc/resolv.conf

루비는 예외도 객체로 다루며, 상황에 따라 다양한 종류의 예외를 제공한다. 다음은 주요 예외들이다. 예외 처리를 위한 일반적인 코드는 다음과 같다.
begin
	# 여기에 평가하고자 하는 코드를 둔다 
rescue SyntaxError => mySyntaxError
	print "Unknown syntax error. ", mySyntaxError, "\n"
	# 에러를 처리하기 위한 코드를 둔다 
rescue StandardError => myStandardError
	print "Unknown general error. ", myStandardError, "\n"
	# 에러를 처리하기 위한 코드를 둔다 
else
	# 에러가 발생할 경우에 실행하는 코드
ensure
	# 에러가 발생하지 않았을 때 실행하는 코드
end
다음은 루비에서 제공하는 예외의 모든 계층 구조를 보여주는 그림입니다.

http://rubyonrailsthrissur.files.wordpress.com/2011/06/exception.png?w=495&h=596

Raising an exception

프로그램을 만들다 보면, 운영체제가 감지할 수 없는 (애플리케이션 레이어에서)에러가 발생하기도 한다. 이런 경우에는 프로그래머가 직접 예외처리를 해야 한다. 65세 이상 노인을 대상으로 하는 의료보험 서비스 프로그램을 만든다고 가정을 해보자. 만약 65세 미만의 사람이 의료보험 서비스를 이용하려고 한다면, 이 프로그램을 에러를 발생해야 할거다. 이런 예외는 운영체제가 알 수 없으므로, 프로그래머가 다음과 같이 예외를 발생해야 합니다.

#!/usr/bin/ruby
age = 18
raise if age < 66
print "Age is ", age, ". This happens after the exception was raised\n"
실행결과는 다음과 같다.
./hello.rb:3: unhandled exception
알수 없는 예외가 발생했다는 메시지가 나온다. 처리를 해주자.
#!/usr/bin/ruby
age = 18
raise "Must be 65 or older for Medicare." if age < 66
print "Age is ", age, ". This happens after the exception was raised\n"
./hello.rb:3: Must be 65 or older for Medicare. (RuntimeError)

처음 코드 보다 낫긴하지만 RuntimeError이라고 하니, 어떤 종류의 에러인지가 명확하지 않다. RangeError 예외를 발생하도록 해서, 에러를 명확히 명시하도록 했다.
#!/usr/bin/ruby
age = 18
raise RangeError, "Must be 65 or older for Medicare", caller if age < 66
print "Age is ", age, ". This happens after the exception was raised\n"
예외 객체 명만으로도 입력범위에 오류가 생겼음을 쉽게 알 수 있다.
./hello.rb:3: Must be 65 or older for Medicare (RangeError)
어떤 종류의 에러가 발생했는지 한눈에 파악하도록 하라 : 에러 처리의 핵심이다.

에러를 처리하는 가장 좋은 방법은 애플리케이션의 특성에 맞추어 예외 클래스를 제작해서 사용하는 거다.
#!/usr/bin/ruby
class MedicareEligibilityException < RuntimeError
end

age = 18
raise MedicareEligibilityException , "Must be 65 or older for Medicare", caller if age < 66
print "Age is ", age, ". This happens after the exception was raised\n"
./hello.rb:6: Must be 65 or older for Medicare (MedicareEligibilityException)
이제 본격적으로 예외 처리를 위한 클래스를 만들어서 프로그램에 적용해 보자.

이 프로그램은 의료보험을 받으려는 사용자의 데이터 베이스를 유지한다. 이름과 나이를 받아서 의료보험 대상인지를 처리를 하는 프로그램이다. 나이가 66세 미만이면, 예외 처리를 하는데, 이때 예외 클래스는 예외가 발생한 사용자 데이터를 넘겨 받는다. 그럼 어떤 사용자를 처리하다가 에러가 발생했는지 확인할 수 있다. 필요하다면 후 처리를 위해서 log 파일로 남길 수도 있겠다.

#!/usr/bin/ruby
class MedicareEligibilityException < RuntimeError
    def initialize(name, age)
        @name = name
        @age = age
    end
    def getName
        return @name
    end
    def getAge
        return @age
    end
end

def writeToDatabase(name, age)
    # This is a stub routine
    print "Diagnostic: ", name, ", age ", age, " is signed up.\n"
end

def signHimUp(name, age)
    # 65세 이상이면 데이터 베이스에 쓴다.
    if age >= 65
        writeToDatabase(name, age)
    else
        # 그렇지 않으면 예외를 발생한다.
        myException = MedicareEligibilityException.new(name, age)
        raise myException , "Must be 65 or older for Medicare", caller
    end
end

# Main routine
begin
    signHimUp("Oliver Oldster", 78)
    signHimUp("Billy Boywonder", 18)
    signHimUp("Cindy Centurinarian", 100)
    signHimUp("Bob Baby", 2)

rescue MedicareEligibilityException => elg
    print elg.getName, " is ", elg.getAge, ", which is too young.\n"
    print "You must obtain an exception from your supervisor. ", elg, "\n"

end

print "This happens after signHimUp was called.\n"

터미널 입출력

터미널 입출력은 표준입력, 표준에러, 표준출력 제어와 관련된 것들이다. 표준출력은 print와 puts 를 이용하면 된다.
print "This is the first half of Line 1. "
print "This is the second half.", "\n"
puts "This is line 2, no newline necessary."
print는 문자열을 그대로 출력하는 반면 puts는 개행문자를 붙여서 출력한다는 점이 다르다.

C의 printf 함수와 비슷한 일을 하는 printf를 제공한다.
#!/usr/bin/ruby
printf "There were %7d people at the %s.\n", 439, "Avalanche Auditorium"

입력은 gets 함수를 이용하면 된다.
#!/usr/bin/ruby
print "Name please=>"
name = gets
print "Your name is ", name, "\n"

파일 입출력

파일 입출력은 파일객체를 이용한다. 사용방법은 아주 단순하다. 파일 관련 루비 인터페이스를 보니 저수준의 인터페이스가 있는데, 여기에서는 고수준의 파일클래스를 이용한 것으로 살펴보려 한다.

파일을 복사하는 간단한 프로그램이다.
#!/usr/bin/ruby

if ARGV.length != 2
  print "Usage : ",$0," [src file] [dest file]\n"
  exit(1)
end

srcfile = ARGV[0]
destfile = ARGV[1]

if srcfile == destfile
  puts "src file == dest file"
  exit(1)
end

infile = File.new(srcfile, "r")
outfile = File.new(destfile, "w")

infile.each {
  |i| 
  outfile.write i
}

infile.close()
outfile.close()
C 스타일의 코드이긴 하지만, 파일 입출력 방식은 잘 보여주고 있다.

한 바이트씩 읽을 때는 아래와 같이 하면 된다.
#!/usr/bin/ruby
infile = File.new("/etc/resolv.conf", "r")
until infile.eof
	i = infile.readchar
	if i.chr == "e"
		print("!")
	else
		print(i.chr)
	end
end
infile.close
이 코드는 eof 즉 파일의 끝을 만날 때까지 until ~ end 블럭을 수행한다. readchar 메서드는 한 바이트씩 읽겠다는 의미다. 읽은 바이트가 "e"라면 !를 출력하게 했다. 결과적으로 e를 !로 치환하게 됀다.

루비와 객체지향

파일 다루기

히스토리