책을 사기는 귀찮고, 언어에 대한 기본적인 이해는 있다고 생각되니 Tutorial로 쉽게 언어를 익히는게 좋겠다고 생각. Tutorial 문서를 찾아서 구글 검색을 해봤더니.. Ruby Tutorial이 첫번째로 나온다.
이것 저것 생각하기 귀찮아서 이 문서로 공부 해 보기로 했다.
공부 환경
언제나 그렇듯이 우분투 리눅스환경이다. 현재(2013년 7월) 우분투 버전은 13.04 Ruby 버전은 1.9.4다.
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다.
중요한 것은 이러 이러하게 사용할 수 있다는게 아닙니다. 자유스럽게 사용할 수 있다는 게 중요하죠. 상상력을 발휘하세요.
0
1
2
3
4
5
6
7
8
9
루비는 모든 것을 객체로 다룹니다. 배열도 마찬가지구요. 그래서 아래와 같은 코드를 만들 수 있습니다. 객체를 직접 만듦으로서 좀 더 객체지향적으로 보이는 코드입니다.
Python은 hash를 원시 자료형으로 지원합니다. hash는 {key=>value}의 쌍으로 이루어지는 자료구조인데, 배열과는 달리 문자열 객체를 사용할 수 있죠. 문자열 뿐만 아니라 다른 객체들도 사용할 수 있을 겁니다. 아마도 ?. 이건 확인을 해봐야 할것 같습니다.
Hash 사용은 간단합니다. PHP, Perl, Python.. 언어를 막론하고 Hash 사용방법이 대동소이하거든요.
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를 이용하면 좀더 그럴듯한 데이터베이스를 만들 수 있을 겁니다.
이름을 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 내용을 확인하고 테스트 하기
has_key? : hah.has_key?(key) -> true 혹은 false
hah에 key가 있으면 true를 반환한다.
has_value? : hah.has_value?(value) -> true 혹은 false
변수 myname과 myname_cpy모두 "yundream"으로 결과가 나온다.
string에서 대입연산자는 value의 복사가 아닌 레퍼런스의 복사이기 때문에 이런 결과가 나온다. 레퍼런스가 아닌 독립된 두 개의 객체로 다루기 위해서는 String.new를 이용해야 한다.
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 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 exception과 raise 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
루비는 예외도 객체로 다루며, 상황에 따라 다양한 종류의 예외를 제공한다. 다음은 주요 예외들이다.
ArgumentError
IndexError
Interrupt
LoadError
NameError
NoMemoryError
NoMethodError
NotImplementedError
RangeError
RuntimeError
ScriptError
SecurityError
SignalException
StandardError
SyntaxError
SystemCallError
SystemExit
TypeError
예외 처리를 위한 일반적인 코드는 다음과 같다.
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를 !로 치환하게 됀다.
Contents
보강 해야 할 것들
소개
공부 환경
Hello World
변수
일반 변수들
특수 변수들
주석
Iterators and Blocks
{}과 do/end의 차이
루프
while Loop
while modifier
until
for
break
next
redo
조건문
if
if modifier
unless
unless modifier
case
Containers
배열 - Array
Hash
Hash 정렬
Hash 내용을 확인하고 테스트 하기
string
대입연산자
문자열 치환
문자열 더하기
형식화된 출력
문자열 비교
정규표현
치환
서브루틴
예외
Raising an exception
터미널 입출력
파일 입출력
루비와 객체지향
파일 다루기
히스토리
Recent Posts
Archive Posts
Tags