이번 글에서는 Ruby에서 문자를 이스케이프(escape)하는 방법에 대해 알아보겠습니다. 문자를 이스케이프하는 방법, 그 작동 원리, 그리고 일부 사용 사례에서는 아예 이스케이프를 피하는 방법까지 살펴봅니다. 백슬래시 \에 대해 모든 것을 안다고 생각하신다면, 끝까지 읽어보세요. 의외의 사실을 발견하실 수도 있습니다.
그럼 바로 문자열에서의 문자 이스케이프부터 시작해 보겠습니다.
따옴표 이스케이프하기
Ruby에서 문자열을 다루다 보면, 문자열을 정의할 때 사용한 따옴표를 문자열 내부에 그대로 넣어야 하는 경우가 있습니다. 이럴 때는 백슬래시 \ 기호로 따옴표 문자를 이스케이프하면 됩니다.
# 큰따옴표 문자열에서 따옴표 이스케이프
"Hello \"world\"!"
=> "Hello \"world\"!"
# 작은따옴표 문자열에서 따옴표 이스케이프
'Hello \'world\'!'
=> "Hello 'world'!"여기서 확인할 수 있듯이, Ruby는 출력 시 기본적으로 큰따옴표 문자열을 사용하며, 출력 결과에서는 큰따옴표만 이스케이프합니다.
이스케이프 시퀀스
따옴표 외에도 문자열에서 이스케이프할 수 있는 기호들이 더 있습니다. 예를 들어, 줄바꿈은 \n으로 표현되는데, 이를 "이스케이프 시퀀스(escape sequence)"라고 부릅니다.
"Hello\nworld"
=> "Hello\nworld"반환되는 값은 우리가 만든 문자열과 동일하지만, \n이 강조 표시된 것을 볼 수 있습니다. 이는 해당 부분이 이스케이프 시퀀스임을 나타냅니다. 이제 이 문자열을 실제로 출력하면 리터럴 \n이 그대로 출력되지 않고, 실제 줄바꿈이 적용되어 출력됩니다.
puts "Hello\nworld"
=> Hello
world그런데 이 동작은 작은따옴표 문자열에서는 작동하지 않습니다. 작은따옴표 문자열에서는 \n 시퀀스가 리터럴 그대로 해석됩니다. 큰따옴표 문자열에서 같은 결과를 얻으려면 백슬래시 자체를 이스케이프해야 합니다.
'\n'
=> "\\n"같은 방식으로 작동하는 다른 이스케이프 시퀀스의 예로는 \t(탭), \s(공백), \b(백스페이스) 등이 있습니다.
작은따옴표와 큰따옴표의 차이
Ruby에서 작은따옴표와 큰따옴표 문자열의 차이는 문자열 정의가 이스케이프 시퀀스를 처리하는 방식에 있습니다.
- 큰따옴표 문자열에서는 이스케이프 시퀀스를 작성하면 Ruby가 번역된 의미로 출력합니다. 예를 들어
\n은 실제 줄바꿈이 됩니다. - 반면 작은따옴표 문자열에서는 이스케이프 시퀀스가 이스케이프되어 리터럴 정의 그대로 반환됩니다. 즉,
\n은 여전히\n으로 남습니다.
물론 두 문자열 타입 모두 각자의 용도가 있습니다. 대부분의 경우 이스케이프 시퀀스가 번역된 의미(줄바꿈이나 탭 등)로 적용되도록 큰따옴표 문자열을 사용하게 될 것입니다.
반면 작은따옴표는 이스케이프 시퀀스 자체가 이스케이프되는 것을 방지하고 싶을 때 유용합니다. 이스케이프 시퀀스의 사용법을 보여주거나, 의도치 않게 이스케이프 시퀀스가 적용되는 것을 막고 싶을 때 활용할 수 있습니다.
puts "Line 1\nLine 2"
=> Line 1
Line 2
puts 'Using a \n we can indicate a newline.'
=> Using a \n we can indicate a newline.보간(interpolation) 이스케이프하기
Ruby는 문자열 안에서 보간(interpolation)을 지원합니다. 하지만 역시나 모든 문자열 정의가 동일하게 동작하는 것은 아닙니다. 보간은 오직 큰따옴표 문자열에서만 작동합니다.
아래 코드 예제에서 볼 수 있듯이, 큰따옴표 문자열에서는 보간이 정상적으로 작동하지만, 작은따옴표 문자열에서는 Ruby가 보간 시퀀스를 이스케이프하여 무용지물이 됩니다.
name = "world"
"Hello #{name}"
=> "Hello world"
'Hello #{name}'
=> "Hello \#{name}"이러한 동작은 거의 모든 종류의 문자열 "마법(magic)"에 적용됩니다. 큰따옴표는 지원하고, 작은따옴표는 지원하지 않습니다.
문자열을 위한 퍼센트 표기법
Ruby의 퍼센트 표기법(percent notation)은 Perl 프로그래밍 언어에서 영감을 받은 것으로, 흔히 쓰이는 몇 가지 타입 정의를 위한 축약 문법을 제공합니다.
작은따옴표와 큰따옴표 문자열의 동작 차이는 퍼센트 표기법에도 똑같이 적용됩니다.
%()와%Q()는 큰따옴표 문자열과 동일하게 동작합니다.%q()는 작은따옴표 문자열과 동일하게 동작합니다.
퍼센트 표기법의 문제점은 괄호의 균형이 맞지 않을 경우, 따옴표 대신 괄호 문자 ()를 이스케이프해야 한다는 점입니다.
그래서 Ruby는 퍼센트 표기법에 다른 기호도 사용할 수 있도록 허용합니다: %[foo], %{foo}, %-foo-, %?foo? 등이 가능하며, 심지어 %"foo"와 %'foo'도 지원합니다.
아래 예제에서 퍼센트 표기법을 사용할 때 Ruby가 문자열을 자동으로 이스케이프해 주는 것을 확인할 수 있습니다.
%(Hello "world"!)
=> "Hello \"world\""
# 괄호 균형이 맞지 않는 경우
%(Hello world\)!)
=> "Hello world)!"
# 괄호 균형이 맞는 경우
%(Hello (world)!)
=> "Hello (world)!"여기에도 일반적인 작은따옴표와 큰따옴표 문자열과 동일한 규칙이 적용됩니다. 작은따옴표 스타일의 표기법에서는 이스케이프 시퀀스와 보간이 기본적으로 이스케이프됩니다.
name = "world"
%(Hello\n "#{name}"!)
=> "Hello\n \"world\"!"
%q(Hello\n "#{name}"!)
=> "Hello\\n \"\#{name}\"!"
%Q(Hello\n "#{name}"!)
=> "Hello\n \"world\"!"정규표현식에서 문자 이스케이프하기
문자 이스케이프는 정규표현식에서도 작동합니다. 정규표현식에서는 많은 문자가 단순한 리터럴 의미 이상의 특수한 기능을 가집니다.
예를 들어 .는 임의의 문자 하나를 나타내는 와일드카드이고, 대괄호 []는 범위 또는 선택 집합을, 괄호 ()는 표현식 매칭을 의미합니다. 이런 문자들을 리터럴 그대로 사용하려면 마찬가지로 백슬래시 기호로 이스케이프하면 됩니다.
/Hello \[world\]/
=> /Hello \[world\]/
/\[world\]/ =~ "Hello [world]"
=> 6만약 대괄호를 이스케이프하지 않았다면, 대괄호 사이의 어떤 문자든 매칭하려 하기 때문에 "Hello"의 세 번째 위치에 있는 문자 l에서 첫 번째 매치를 찾게 됩니다.
/[world]/ =~ "Hello [world]"
=> 2 # zero index정규표현식을 정의할 때 사용하는 슬래시 기호 /를 이스케이프하는 것이 번거롭다면, 다른 퍼센트 표기법을 사용할 수 있습니다.
%r{/world/}
=> /\/world\//%r{}를 사용하면 슬래시를 이스케이프하지 않아도 되는 정규표현식을 정의할 수 있습니다. 결과에서 Ruby가 슬래시를 자동으로 이스케이프해 주는 것을 확인할 수 있습니다.
줄바꿈 이스케이프하기
앞서 Ruby 문자열에서 "줄바꿈" 이스케이프 시퀀스인 \n을 이스케이프하는 방법을 살펴봤습니다. 그런데 Ruby 코드 자체에서도 줄바꿈을 이스케이프할 수 있다는 것을 알고 계셨나요?
예를 들어, 메서드 호출이 너무 길어서 코드 스타일 가이드의 최대 줄 길이를 초과하는 상황을 생각해 봅시다.
ruby_method_with_many_arguments "Hello world", split: " ", join: "\n"인수들을 여러 줄에 걸쳐 배치하면 각 줄을 더 짧게 만들 수 있습니다. 이전 줄이 쉼표 ,나 덧셈 연산자 같은 연산자 +로 끝나거나, 괄호 ()로 감싸진 메서드 호출의 일부라면 줄을 나눌 수 있습니다.
ruby_method_with_many_arguments "Hello world",
split: " ",
join: "\n"
ruby_method_with_many_arguments(
"Hello world",
split: " ",
join: "\n"
)만약 괄호 ()로 감싸지 않으면서 모든 인수를 동일한 들여쓰기 수준에 정렬하고 싶다면, 백슬래시 \로 줄바꿈을 이스케이프하면 됩니다.
ruby_method_with_many_arguments \
my_string,
split: " ",
join: "\n"여기서 하는 작업은 엄밀히 말해 Ruby에게 줄바꿈을 이스케이프하라고 지시하는 것이 아니라, 해당 문장이 다음 줄에서 계속된다는 것을 알려주는 것입니다.
문자열 정의에서 줄바꿈 이스케이프하기
줄 끝을 이스케이프하는 방법은 문자열 정의에도 적용할 수 있습니다. 일반적으로 여러 줄에 걸친 문자열을 만들 때는 덧셈 기호 +를 사용해 두 줄의 문자열을 결합합니다.
"foo" +
"bar"
=> "foobar"이렇게 하면 두 줄에서 두 개의 문자열 객체가 생성된 후 하나로 결합됩니다. 즉, 실제로는 세 개의 문자열 객체가 만들어지는 셈입니다.
덧셈 기호 대신, 백슬래시로 줄바꿈을 이스케이프할 수도 있습니다.
"foo" \
"bar"
=> "foobar"결과는 동일해 보이지만, 과연 그럴까요?
첫 번째 예제에서는 실제로 새로운 문자열 두 개를 생성해 결합하는 반면, 두 번째 예제에서는 Ruby가 문자열을 하나만 생성합니다. Ruby 인터프리터는 백슬래시 \를 문자열 정의의 연속으로 인식하여, 두 줄을 기반으로 단 하나의 문자열만 만듭니다. 따라서 애플리케이션의 메모리 사용량 측면에서도 더 유리합니다.
결론
Ruby를 비롯한 많은 프로그래밍 언어에서 문자 이스케이프는 상당히 혼란스러울 수 있습니다. 어느 순간부터 특정 문자 조합이 작은따옴표 문자열 안에서와 큰따옴표 문자열 안에서 서로 다른 의미를 갖게 되니까요.
이 가이드가 Ruby에서 문자와 시퀀스를 올바르게 이스케이프하는 방법을 이해하는 데 도움이 되었기를 바랍니다. 여기서 핵심은 큰따옴표 문자열과 작은따옴표 문자열의 차이입니다.
이스케이프 시퀀스와 보간이라는 문자열 "마법"은 실질적으로 큰따옴표 문자열에서만 작동하며, 작은따옴표 문자열에서는 작동하지 않습니다.
또한 Ruby 자체에는 코드를 이스케이프하는 다른 방법들도 있습니다. 퍼센트 표기법을 활용하면 문자열과 정규표현식에서 특정 문자를 일일이 이스케이프하는 번거로움을 피할 수 있습니다.
그리고 백슬래시는 여러 줄에 걸친 문자열을 작성하거나, Ruby 코드 한 줄을 다음 줄로 이어서 작성할 때도 매우 유용합니다. 저는 코드를 깔끔하고 읽기 좋게 유지하기 위해 이 기법을 자주 사용합니다.
Ruby의 문자 이스케이프에 대해 더 알고 싶거나 구체적인 질문이 있다면 @AppSignal로 알려주세요.
재미있는 사족: 이 글은 Markdown으로 작성되었는데, Markdown 역시 Ruby처럼 특정 문자의 이스케이프를 허용하기 때문에 백슬래시 기호를 상당히 많이 사용해야 했습니다. 보통 Markdown에서는 백슬래시가 코드 블록 자체를 이스케이프해 버리기 때문에 코드 블록 안에 백슬래시를 감쌀 수 없습니다. 대신 공백을 이스케이프하는 우회 방법을 사용했습니다.
`\` # 작동하지 않음
`\ ` # 작동함 :)