ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [ 번역 ]손과 눈으로 기억하는 정규표현 입문 3. 공백문자를 자유자재로 활용하자
    Technique/ETC 2016. 3. 9. 14:43
    반응형

    출처 : http://qiita.com/jnchito/items/6f0c885c1c4929092578

    의 내용을 제입맛에 맞춰 번역한 내용입니다. 당연히 오역및 의역도 있을수 있기에, 일본어가 가능하신분은 원문을 읽는것을 추천해 드립니다.


    이 기사로 배우는 것
    이 기사에서는 소스코드의 -청소-나 어플리케이션 로그의 필요문자 선택을 통하여 정규표현에 대한 이하와 같은 지식을 배웁니다.
    - ^、$、\s、\t、\n 의 의미
    - |을 사용한 OR 검색
    - 환경에 따라 다른 개행 코드와 정규표현의 관계
    - 사용하는 장소에 따라 다른 ^의 의미

    지금까지의 내용이 일상적으로 자주 사용하는 정규표현의 대부분 이라고 생각됩니다.
    그렇기에 제1회부터 지금까지의 내용이 이해된다면 정규표현 초심자는 졸업이라고 할 수 있죠.
    열심히해서 꼭 마스터해 봅시다!!

    동작환경


    - Ruby
    - Javascript
    - Atom

    그외 정규표현이 사용가능한 프로그래밍 언어나 텍스트 에디터라면 본기사의 샘플코드는 거의다 같은 방식으로 움직이겠습니다만,
    정규표현의 사양이나 사용하는 메타문자가 조금씩 다른경우도 있습니다.
    뭔가 움직임이 이상하다고 생각된다면 자신이 사용하고있는 환경의 정규표현의 사양을 확인 해주세요.
    그럼 세번째기사 본문을 시작하도록 하겠습니다.

    스페이스나 탭문자가 들어간 빈행을 발견.
    다음과 같은 Ruby 스크립트가 있습니다.

    def hello(name)
      puts "Hello, #{name}!"
    end
    
    hello('Alice')
              
    hello('Bob')
    	
    hello('Carol')


    처음봤을때, 아무것도 문제없어 보입니다만, 사실은 빈행의 일부에 스페이스나 탭문자가 포함되어 있습니다.
    빈행에 스페이스나 탭 문자가 포함되어 있더라도 의미가 없으므로, 이상한 행을 찾아내볼까요
    우선 방금전에 소개한 텍스트를 Rubular에 붙여 넣어 주세요

    Kobito.VrSRhS.png



    그럼 이어서, Your regular expression란에 + (스페이스와 플러스문자) 라고 입력해 주세요.
    (이전의 기사를 읽은 분은 눈치채실거라 생각합니디만 이것은 [스페이스가 1문자이상 계속]이라는 의미의 정규표현입니다.)
    이렇게하면 Rubular는 다음과같은 매치된 문자열을 표시합니다.

    Kobito.SMReHx.png


    이렇게 보니 hello('Alice')와 hello('Bob')의 사이에 스페이스가 포함된 것 같군요.
    그러나 첫번째행이나 두번째행에 있는 정상적인 스페이스까지 검출되어 버리네요.

    여기서 아까전의 정규표현을 ^ + 로 바꿔볼까요?

    Kobito.gBAr4d.png

    그러면 매치하는 부분이 조금 바꼇습니다.

    여기서 사용한 ^는 [행의 첫부분]을 표시하는 메타문자 입니다.
    문자뿐만 아니라, 매치된 [위치]를 나타내기때문에, 이런 메타문자를 특별히 [앵커] 라고 부릅니다.

    즉 ^ + 는 [행의 첫번째부터 스페이스가 1문자 이상 연속됨] 이라는 의미가 됩니다.

    단지, 여기까지라면 두번째행에 있는 "Hello, #{name}!" 까지 매치됩니다.
    그행은 제외되어야 합니다.

    그렇기때문에 조금더 메타문자를 추가하여 ^ +$ 이라는 정규표현을 사용합니다.
    $는 ^의 반대로 [행의 끝부분]을 의미하는 메타문자 [앵커] 입니다.
    즉, & +$ 는 [행의 처음부터 끝부분 까지 공백이 1개이상 연속됨]이라는 의미가 됩니다.

    Rubular에 ^ +$로 입력해 주세요

    Kobito.ov3eqL.png


    이걸로 빈행에 들어가 있던 공백들 만이 검출 되었습니다.

    자 그럼, 공백이 포함된 빈행을 찾았습니다만, 탭문자가 들어간 빈행은 아직 찾지 못하였군요.
    여기도 정규표현을 사용하여 검출해 볼까요?

    탬 문자는 \t 이라고하는 메타문자(문자 클래스)를 사용하여 표시가능합니다.

    Rubular에 ^[ \t]+$ 라고 정규표현을 입력해 봅시다.

    Kobito.9WEHP2.png


    앗 hello('Bob')과 hello('Carol')의 사이에도 탭문자가 들어가 있엇군요!
    이걸로 공백과 탭이 들어간 빈행을 찾아 낼 수 있게 되었습니다.

    빈행에 포함된 필요없는 공백이나 탭문자를 삭제
    자 그럼, 이상한 행을 찾는건 그만두고.... 가 아니라 이 불필요한 행을 깔끔하게 삭제해 볼까요.
    정규표현이 사용가능한 텍스트 에디터라면 간단히 불필요한 공백문자를 삭제할 수 있습니다.

    방금전의 텍스트 에디터를 Atom에 붙여 넣어주세요

    def hello(name)
      puts "Hello, #{name}!"
    end
    
    hello('Alice')
              
    hello('Bob')
    	
    hello('Carol')


    그리고나선 ^[ \t]+$ 의 정규표현을 입력해주세요
    Rubular와 같이 불필요한 공백과 스페이스문자가 선택될 것 입니다.

    Kobito.2FAhWe.png


    치환할 문자열 부분에 아무것도 입력하지 않아도 됩니다.
    이대로 Replace All 버튼을 클릭하면 불필요한 공백과 스페이스 문자가 삭제됩니다.

    QqN068ESrE.gif


    Javascript에서 사용


    Javascript(js)의 경우, ^ 와 $ 를 의도한것 처럼 매치시키기 위해서는 m옵션(복수행 검색 옵션)을 붙일 필요가 있습니다.
    아래의 코드에 있는 /^[ \t]+$/gm의 m이 바로 그 옵션입니다.
    (g는 글로벌 옵션입니다. 이것은 제1회에서 소개하였습니다.)

    이것도 역시 화면상으론 차이를 알 수 없지만 불필요한 공백이나 탭 문자가 삭제되어 있습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var text = "def hello(name)\n  puts \"Hello, \#{name}!\"\nend\n\nhello('Alice')\n     \nhello('Bob')\n\t\nhello('Carol')\n";
     
    console.log(text.replace(/^[ \t]+$/gm, ''));
    // def hello(name)
    //   puts "Hello, #{name}!"
    // end
    // 
    // hello('Alice')
    // 
    // hello('Bob')
    // 
    // hello('Carol')
    cs



    문자열 끝에 들어간 불필요한 공백문자를 삭제


    방금전에 사용한 ^[ \t]+$을 사용하면 여러곳에서 편리하게 활용할 수 있습니다.
    예를 들면 다음과 같은 문장끝의 불필요한 공백이 포함되어 있다고  합시다

    def hello(name)   
      puts "Hello, #{name}!"
    end      


    문장끝에 있는 불필요한 공백은 [ \t]+$ 와 같은 정규표현으로 매치시킬 수 있습니다.
    이것은 [스페이스 또는 탭문자가 문장끝에 1문자 이상 연속]이라는 의미입니다.

    Kobito.KTY5eo.png


    다음은 방금전과 같이 텍스트 에디터에서 치환하면 문장끝의 불필요한 공백은 삭제됩니다.
    ( 사실은 처음에 사용한 예제도 [ \t]+$로 해결됩니다. )

    그렇다곤 하지만 이런 불필요한 공백문자의 삭제는 IDE나 플러그인류가 자동적으로 처리해주는 경우가 많으므로
    실무에서는 그쪽을 사용하는 편이 편리하다고 여겨집니다.
    이것은 어디까지는 정규표현의 공부용으로 생각해주세요

    인덴트가 제멋대로인 텍스트를 왼쪽으로 정리
    반대로 ^[ \t]+로 한다면 [문장의 처음부터 스페이스나 탭문자가 1개이상 연속]의 의미가 됩니다.
    다음과 같은 인덴트가 제멋대로인 텍스트도 ^[ \t]+를 사용한다면 간단히 왼쪽으로 정리됩니다

      Lorem ipsum dolor sit amet.
    Vestibulum luctus est ut mauris tempor tincidunt.
             Suspendisse eget metus
          Curabitur nec urna eget ligula accumsan congue.


    0g58CcElLT.gif


    정리되지 않는 공백을 정리
    다음은 Ruby의 해쉬리터럴을 깔끔하게 해보겠습니다.

    {
      japan:	'yen',
      america:'dollar',
      italy:     'euro'
    }



    화면상에서는 조금 알아보기 힘들겠지만, 콜론의 다음의 공백이 여러모로 마음에 들지 않군요.
    - japan : 의 뒤에는 스페이스가아니라 탭 문자가 들어가 있습니다.
    - america : 의 뒤에는 스페이스가 없습니다.
    - itally : 의 뒤에는 스페이스가 너무 많습니다.

    이런 연유로, 이것을 [스페이스 1개]로 바꿔봅시다.


    여기까지 [콜론의 뒤에  스페이스 또는 탭문자가 0개이상 ] 이란 정규표현을 사용합니다.
    즉 아래와 같은 정규표현이 됩니다.

    :[ \t]*


    다음은 치환후의 문자열을 : (콜론 + 스페이스)로 한다면 OK 입니다,
    Jn12MD8RCs.gif

    [\t]의 대신에 \s를 사용
    정규표현에는 \s라고하는 메타문자가 있습니다,.
    이것은 반각 스페이스나 탭문자, 개행문자등 눈으로는 보이지 않는 [전반적인 공백문자]를 나타내는 문자클래스 입니다.
    그렇기에 방금전 정규표현을 다음과 같이 적는것도 가능합니다

    :\s*


    보시는 것 처럼 이것도 다음과 같이 불필요한 스페이스들을 정리하는것이 가능합니다.
    oWpzrQJz8q.gif

    단지 \s를 사용하는 경우는 다음과 같은점에 주의해주세요.

    1. \s에 포함되는 문자가 언어나 호나경에 따라 다르다
    우선, 언어나 환경에 따라 \s에 포함되는 문자열이 조금씩 차이가 있습니다 ( 이것은 \s에만 해당하는 이야기가 아닙니다. )
    아래에 나타내듯이 Ruby와 Javascript에서도 \s의 내용이 다릅니다.
    Ruby의 경우

    • \s = [ \t\r\n\f]


    JS의 경우

    • \s = [ \f\n\r\t\v​\u00a0\u1680​\u180e\u2000​-\u200a​\u2028\u2029\u202f\u205f​\u3000\ufeff]


    Ruby의 경우에는 반각스페이스, 탭문자, 개행문자, 복귀문자, 개페이지 문자  뿐입니다만
    js의 경우에는 전각스페이스 같은 공백문자도 \s에 포함되어 있습니다.

    2. \s 에는 개행문자나 복귀문자도 포함된다.
    위에 나타낸것 처럼 \s에는 개행문자나 복귀문자도 포함됩니다.
    따라서 아무 생각없이 \s만 검색해서 삭제하면 개행문자도 같이 삭제되므로 전부의 행이 1행이 되어버립니다.


    wRrTsozVLF.gif


    이런 이유로 의도적으로 개행문자를 삭제하고 싶은경우가 아니라면 다른 문자열이나 메타문자를 조합하여 \s에 불즉정 다수의 공백문자가 매치되지 않도록 정규표현을 만들어야 합니다.
    덧붙여 이다음에서 개행문자를 의도적으로 삭제하는 예외가 등장합니다.

    콤마로 나눠진 문자열을 탭문자로 탭문자로 나눠진 문자열을 콤마 로 치환
    매우 소소하지만, 정규표현으로 의외로 편리한게 탭문자나 개행문자등 검색,치환용 텍스트박스의 입력이 어려운 문자를 간단히 습득하는것 입니다.
    예를 들면 다음과 같은 콤마로 나누어진 텍스트가 있다고 합시다.

    name,email
    alice,alice@example.com
    bob,bob@example.com


    이것을 콤마문자가 아니라, 탭문자로 하고싶어! 라고 생각한다면 다음과 같으 한다면 OK입니다.
    - 검색문자열 = , (이것은 정규표현이 아니라 그냥 문자입니다.)
    - 치환문자열 = \t (이것은 정규표현의 탭문자열 입니다.)

    다음은 Atom 에서의 실행결과입니다.
    Yvj0QsQb1B.gif

    탭문자로 나누어져 있다면 Excel이나 Google스프레드시트에 복사 붙여넣기로 데이터를 표시하는데 매우 편리하겠죠?


    Kobito.MERLe4.png


    역으로 탭문자열 에서 콤마 문자열로 치환하는 것도 간단합니다.
    이게 다라고 생각하겠지만 다음과같이 지정한다면 OK입니다.
    - 검색문자열 = \t
    - 치환문자열 = ,


    riwU517p69.gif

    이게다야??? 이라고 생각하시겟지만, 정규표현(의 메타문자) 를 사용하지않고 하려고한다면 엄청 고생하겟지요???( 어쩌면 포기할지도.... )

    로그에서 특정문자를 포함한 행을 삭제
    자 그럼 다음 샘플로 넘어가 볼까요?
    이번에는 로그파일 입니다.

    아래는 Heroku상에 출력된 Rails의 로그를 이번에 샘플용으로 가공한 것 입니다.

    Feb 14 07:33:02 app/web.1:  Completed 302 Found ...
    Feb 14 07:36:46 heroku/api:  Starting process ...
    Feb 14 07:36:50 heroku/scheduler.7625:  Starting ...
    Feb 14 07:36:50 heroku/scheduler.7625:  State ...
    Feb 14 07:36:54 heroku/router:  at=info method=...
    Feb 14 07:36:54 app/web.1:  Started HEAD "/" ...
    Feb 14 07:36:54 app/web.1:  Completed 200 ...


    이 어플리케이션은 브라우저로부터 웹페이지에 접속하거나, 정기적으로 기동하는 스케줄러가 동작하는 경우의 2패턴 있습니다.
    웹페이지의 접속은 app/web 이나 heroku/router라고 표시되고 ( 1행, 5~7행 ), 스케쥴러의 실행로그는 ( heroku/api 나 heroku/scheduler)와 같이 표시됩니다 (2~4행)

    지금은 웹페이지의 접속만 확인하고 싶습니다만, 스케쥴러의 실행로그가 함께 남아 있어 매우 보기 힘듭니다.
    거기서 정규ㅣ표현을 이용하여 스케쥴러의 실행로그를 삭제해 볼까요??

    우선 위의 텍스트를  Rubular에 붙여 넣어 봅시다.

    Kobito.sRDPAs.png


    우선 heroku/api가 포함된 행을 선택합니다.
    여기서는 다음과 같은 정규표현을 지정하도록 할까요?

    ^.+heroku\/api.+$


    이 정규표현의 의미는 [ 문장의 처음부터 무언가 문자가 1개이상 지속(^.+), "heroku/api"가 나타남(heroku\/api), 그후 문장끝까지 아무 문자가 1개이상 연속(.+$)]의 의미입니다.
    또, \/의 백슬러쉬(\)는 슬래쉬(/)의 에스케이프 문자입니다 (제2회의 기사 참조)

    이 정규표현을 Rubular에 입력하면 heroku/api가 포함된 행을 선택할 수 있습니다.

    Kobito.Wb9VQk.png

    같은 요령으로 이하와 같은 정규표현을 작성한다면 heroku/scheduler 이 포함된 행도 선택가능 합니다.

    ^.+heroku\/scheduler.+$


    Kobito.XEL1a3.png



    그러나, 가능하다면 heroku/api의 행과 heroku/scheduler의 행을 합쳐서 선택하고 싶죠?
    이런 경우에는 | 라고하는 메타문자를 사용합니다.
    ABC|DEF 와 같이 작성하면 [ 문자열 ABC 또는 문자열 DEF ]라고하는 OR 조건의 의미가 됩니다.
    실제로는 OR 조건의 범위를 명확히 하기 위해 (ABC|DEF)와 같은 그룹화의 () 와 함께 사용하는 경우가 많습니다.

    이런 연유로 [heroku/api 또는 heroku/scheduler 가 포함된 행 ]을 선택하고 싶은 경우에는 다음과 같은 정규표현을 사용합니다.
    ( 정규표현을 간략히 표기하기위해 (api|scheduler) 와 같은 OR 조건으로 하였습니다. )

    ^.+heroku\/(api|scheduler).+$


    Rubular 에 입력하면 기대한 것 같이 불필요한 행을 검색할 수 있습니다.

    Kobito.jSVXVD.png



    자 그럼 정규표현의 움직임을 확인가능한 지호나처리를 실행하기위해 Atom에 패턴을 넣어봅시다.
    Rubular에서 사용한 텍스트와 정규표현을 Atom에 넣어주세요.


    Kobito.Ue52iI.png

    이걸로 Replace All 버튼을 클릭한다면 필요없는 행이 삭제될 것 입니다.


    Kobito.FtBWyx.png

    흠..
    이걸로 됫다고 생각하면 그걸로 끝일지 모르겟지만 뭔가 깔끔하지 못하군요
    가능하다면 아래와같이 빈행을 남기지 않고 행을 전부 채워버리고 싶네요

    Feb 14 07:33:02 app/web.1:  Completed 302 Found ...
    Feb 14 07:36:54 heroku/router:  at=info method=...
    Feb 14 07:36:54 app/web.1:  Started HEAD "/" ...
    Feb 14 07:36:54 app/web.1:  Completed 200 ...


    괜찮습니다. 정규표현을 조금 변형시키면 제대로 빈행을 남기지 않게 삭제할 수 있습니다.

    행을 채우는것은 즉 [개행문자를 삭제하는것] 입니다.
    $는 [문장끝]을 나타내는 메타문자 입니다만, 이대로라면 개행문자는 포함되지 않습니다.
    $의 대신에 \n을 지정한다면, 검색결과에 개행문자도 포함되도록 됩니다.

    따라서, 방금전의 정규표현을 아래와같이 바꿔 주세요.

    ^.+heroku\/(api|scheduler).+\n


    이렇게한다면 빈곳을 남기지 않고 삭제가능합니다.
    (하지만, Windows 환경에서는 삭제되지 않을지도 모릅니다, 이유는 뒤에 설명하겠슶니다.)

    Tn0ImcxbXw.gif
    만일을 위하여 ^.+heroku\/(api|scheduler).+\n의 의미를 확인해 둔다면 다음과 같습니다.

    [문장 시작부분에서 무언가의 문자가 1개이상 지속됨 (^.+), "heroku/"가 나타냄 (heroku\/), api또는 scheduler가 뒤따르고, 그뒤에 무언가의 문자가 1개이상 지속(.+), 개행문자로 종료 (\n) ]

    조금 이해하기 어렵겟지만, 여기까지의 내용을 제대로 이해하셧다면 이 정규표현도 쉽게 읽을수 있을 것 입니다.
    이번에 소개해드릴 정규표현은 이상입니다.

    추가 : windows 환경과 max/linux 환경의 개행 문자의 차이를 고려


    방금전 소개한 ^.+heroku\/(api|scheduler).+\n 이라는 정규표현에서 필자의 windows 환경의 Atom에서 실행하면 제대로 치환되지 않았습니다.
    알고 계신분들이 많겠지만, windows환경의 개행코드는 CRLF(\r\n) 입니다.
    반면, mac/linux환경에서는 LF(\n) 입니다.
    그리고 어쩌면 windows환경의 Atom에 텍스트코드를 복사, 붙여넣기 하면 자동적으로 개행코드가 CRLF로 변환되는 듯 합니다.
    Atom의 윈도우에도 CRLF라고 적혀있습니다. ( mac환경에서는 LF 입니다. )
    Screen Shot 2016-02-15 at 05.15.46.png

    .+\n로 작성하면 [(\r나\n을 제외함)임의의 문자가 1개이상 연속, \n으로 종료]라는 의미가 됩니다.
    이를위해 \r\n 으로 종료되는 windows환경의 텍스트에는 매치되지 않습니다.
    이것이 제대로 치환되지 않았던 이유 입니다.


    ^.+heroku\/(api|scheduler).+\r?\n


    ?는 [ 직전의 문자가 1개, 또는 0개 ] 를 의미하는 메타문자 입니다. (제2회에서 소개하였습니다.)
    그렇기에 \n에서도 \r\n에서도 매치하게 됩니다.
    이하는 windows환경에서의 실행 예 입니다.
    제대로 불필요한 행을 삭제되네요.


    NlwlE1DlnP.gif

    이렇듯 windows와 mac/linux에서 개행코드의 차이로 정규표현의 동작에도 차이가 발생하는 것이 있으므로, 개행문자를 포함한 정규표현을 사용할 때에는
    환경의 차이에 주의하여 주세요.

    컬럼 : 사용되는 장소에 따라 역활이 다른 ^를 이해
    근데, 이번에[행의 맨앞 문자를 나타내는 메타문자]로 등장한 ^은 제2회의 기사에도 등장 하였습니다.
    기억하고 있습니까?
    [^AB]와 같이 사용할때의 ^입니다.
    [] 의 안에서 ^ 가 사용되면 [A도 아니고 B도아닌 1문자] 와 같은 []의 의미를 부정조건으로 바꾸는것 이었습니다.

    조금더 붙이자면 [AB^]로 한다면 조금더 의미가 바뀝니다.
    이것은 [ A또는 B또는 ^중 1개의 문자]의 의미가 됩니다.
    부정조건이 되는 것은 []의 선두에 ^가 올 때 뿐입니다.

    만일을 위해 Rubular상에서 확인해봅시다.
    이하의 텍스트를 검색용으로 사용합니다.

    ABCDEF
    !@#$%^&*


    [^AB]는 [A도아니고 B도 아닌 1개의 문자 (A와 B이외의 문자)] 이 해당합니다.


    Kobito.O4bYL6.png


    [AB^]는 [A또는 B또는 ^ 중 1개의 문자]입니다.


    Kobito.IUEacp.png

    덧붙여 말하자면, ^. 로 하면 [선두에 오는 임의의 1문자]의 의미가 됩니다.


    Kobito.ZCJfzr.png

    "^" 와같은 문자만을 검색하고 싶은 경우에는 \^ 와 같이 백슬러쉬 를 붙여서 에스케이프하여 사용합니다.


    "^" 와 같은 문자 그 자체를 검색하고 싶은 경우는 잘 없을것 같습니다만, 문장의 시작을 나타내는 ^와 부정조건을 만드는 [^]은 사용빈도가 높기때문에 착각하지 않도록 주의해 주세요

    정리
    본 기사에서는 다음과 같은 것을 배웠습니다.
    - ^ 는 문장의 첫 부분을 나타냄
    - $ 는 문장의 끝을 나타냄
    - \t 는 탭문자를 나타냄
    - \n 은 개행문자를 나타냄
    - \s 는 공백문자( 스페이스, 탭, 개행문자 )를 나타냄
    - ABC|DEF 는 [문자열 ABC 또는 DEF]의 OR조건을 나타냄
    - 개행코드는 환경에 따라 차이가 있음
    - ^는 문장의 첫부분을 의미가 되거나, [^]로 부정의 문자클래스를 의미하기도 한다.

    제1,2회 그리고 이번의 제3회까지의 내용을 이해하였다면 일상적으로 자주 사용하는 정규표현의 대부분이 커버될 것 입니다.
    지금까지 정규표현이 수수께기 같은 암호로 밖에 보지이 않았던 사람도, 슬슬 '정규표현 읽을만한데? 쓸 수 있겟는데?' 라고 느끼지 않으셧나요?
    그리고 간단한 문자열 검색으로는 불가능한 '정규표현만의 편리함'을 실감되기 시작했을 것 입니다.

    자 그럼, 초심자용은 이제 끝입니다. 이라고 해도 좋지만 알아두면 편리한 데크닉이나 메타문자가 아직 조금더 남아 있습니다.
    제4회에서는 그런 내용을 설명할 예정입니다.
    굳이 말하자면 [중급자용]의 내용입니다.

    반응형

    댓글

Designed by Tistory.