Gorio Tech Blog search

GitHub 사용법 - 05. branch 기본 2

|

주의: 이 글을 읽는 여러분이, 만약 git을 많이 써 봐서 익숙한 것이 아니라면, 반드시 손으로 직접 따라 칠 것을 권한다. 눈으로만 보면 100% 잊어버린다.

저번 글에서 작업하던 것을 이어서 한다. 저번 글에서 1st-branch를 만들고 master에 merge한 뒤 삭제하는 작업을 진행했었다.


Branch 생성, checkout, 강제 삭제

현재 작업 트리의 상황은 다음과 같다. 1st-branch가 삭제되어 있다.

01

이제 브랜치 2개를 더 만들어 보자. 다음 명령을 입력한다.
2nd-branch는 여러분이 사용하는 브랜치이고, 3rd-branch는 여러분 말고 다른 팀원이 사용하는 branch라 생각하자.
물론 사람을 데려오긴 어렵기 때문에, 여러분이 직접 두 개를 다 만들도록 한다.

git branch 2nd-branch
git checkout -b 3rd-branch

첫 번째 명령은 다 알 것이라고 생각하고, 두 번째 명령은 조금 다르다.
git checkout은 원래 다른 브랜치로 이동할 때 쓰는데, -b 옵션을 주면 해당 브랜치를 만들면서 checkout하는 효과를 갖는다. 즉, 여러분은 현재 3rd-branch 브랜치에 위치해 있다.

02_create

물론 위와 같이 이미 있는 이름으로 만드려고 하면 이미 존재한다고 오류를 뱉어낸다.

이미 완전히 삭제해버린 브랜치 이름(1st-branch)로 만드는 것은 오류를 뱉어내지 않는다.
그러나 뭔가 확실하지 않은 부분이 있다면 똑같은 이름으로 재생성하는 것은 별로 추천되지 않는다.

옵션: Branch 재생성, 강제 삭제

하지만 예시를 보여주기 위해 만들어 보겠다.

git branch 1st-branch
git checkout 1st-branch

그리고 first.py 파일을 생성하고 마지막 줄에 다음을 입력한다. 곧 삭제할 것이기 때문에 아무거나 입력해도 괜찮다.

print('Dummy!')

다음으로 여러분은 이 수정 사항이 쓸모 있는 것이라고 생각하고 다음 명령까지 진행했다고 하자.

git add first.py
git commit -m “dummy!”

커밋 메시지가 너무 단순하다고 생각된다면, 다음 명령을 입력하라.

git commit –amend

그러면 (아마도 vim이라고 하는) 편집기가 뜬다. 이 편집기의 사용법은 나중에 좀 더 자세히 다룰 텐데, 지금은 다음 과정만 따른다.

  1. 처음 편집기에 들어왔을 때는 ‘명령 모드’이다. 여기서 i를 누르면 INSERT 모드(명령창의 맨 아래에 뜬다)가 되어, 커밋 메시지를 수정할 수 있다.
  2. 다음으로 커밋 메시지를 바꾼다. Dummy commit!
  3. 그리고 다음 키들을 차례로 입력한다. 하나도 틀려서는 안 된다.
    1. ESC
    2. : (콜론)
    3. wq (영문자 두 개이다)
    4. Enter

그러면 작성했던 커밋 메시지가 수정된다.

03_1st_branch

04_dummy_commit

여기까지는 문제가 없다. 그러나 추천되지 않는 행동이기에, 바로 삭제하려고 한다.
여러분도 프로젝트를 진행하다 보면 브랜치를 만들고 작업을 하다가 필요가 없어져서 삭제하려고 하는 상황이 올 것이다.

2nd-branch로 checkout을 하고 삭제 명령을 입력한다.

git checkout 2nd-branch
git branch -d 1st-branch

그러나 에러가 뜬다. 변경된 작업사항을 push하거나 merge하지 않았기 때문에, 수정사항을 잃어버릴 수도 있다고 경고하는 것이다.
물론 여러분은 이것이 쓸모없는 브랜치인 것을 알기 때문에, 강제로라도 삭제하면 된다.

친절하게도 강제 삭제 명령을 git에서 알려 주었다.

git branch -D 1st-branch

05_D

이제 2nd-branch에서 다시 시작하자.


Non fast-forward 병합

이번엔 두 개의 서브 브랜치(참고로 티켓 브랜치라고도 부른다)에서 모두 master에 변경사항을 만들어 볼 것이다.
두 명의 사람이 각각 자신의 브랜치를 만들어서 master에 적용하려는 상황을 생각하라.
참고: 같은 컴퓨터에서는 명령창을 두 개 놓고 다른 브랜치에서 작업하는 것은 불가능하다.

우선 2nd-branch에서, second.py 파일의 끝에 다음을 추가한다.

print("This is the 1st sentence written in 2nd-branch.")

그리고 다음 명령들을 차례로 입력한다. 이 부분은 fast-forward 병합이다.
참고: 명령창에서 second.py를 입력할 때 ‘s’ 정도만 타이핑한 후 Tab 키를 누르면 파일(또는 디렉토리) 이름이 자동완성된다.

git add second.py
git commit -m “This is the 1st commit written in 2nd-branch”
git checkout master
git merge 2nd-branch

파일 수정 시 빈 줄을 몇 개나 넣었느냐에 따라서 수정 사항(1 file changed, 3 insertions(+), 1 deletion(-)) 이 달라 보일 수 있으나, 여기선 별 상관 없다.
참고: 마지막 두 줄은 git merge master 2nd-branch로 가능하다. 그러나 해당 브랜치에서 추가 작업이 필요한 경우 별로 편리한 명령은 아니다.

06_fast

그리고 이제 커밋 코드를 확인해 보자.

07_log

16진수 코드가 같다. fast-forward 방식으로 병합되었기 때문임은 설명했다.

이제 3rd-branch로 이동한다.
그리고 3rd-branch에서는 first.py에 다음을 추가한다.

print("This is the 1st sentence written in 3rd-branch.")

그리고 다음 명령들을 입력한다. 조금 전과 매우 유사하다.

git add first.py
git commit -m “This is the 1st commit written in 3rd-branch”
git checkout master
git merge 3rd-branch

세 브랜치의 로그를 확인해 보면 위와는 조금 차이가 있다.

08_log

작업 트리를 그려보면 다음과 같다. master로 checkout했을 때를 기준으로 하였다(HEAD).
또한 fast-forward와 non fast-forward를 구분하여 그렸다.

09

명령창에서 그래프를 그릴 수도 있다. 다음 둘 중 하나를 입력한다.

git log –graph
git log –graph –oneline

10_graph

위의 그림들에서는 설명할 것이 많다.

  1. 2nd-branch merge 시: master에 새로운 수정사항이 없는 상태에서 다른 서브 브랜치의 커밋을 merge했을 때, fast-forward 방식으로 merge된다. 작업 트리에서 보듯이 master는 해당 브랜치의 커밋을 그대로 가져올 뿐이다.
  2. 3rd-branch merge 시: master에 수정사항이 있는 경우(2nd-branch로부터의 커밋 1개), non fast-forward 방식으로 merge된다. 이 때는 master가 3rd-branch의 커밋을 그대로 가져오기는 하지만, merge commit(90ce4f2)이라는 새로운 커밋이 생성된다.

보충 설명은 다음과 같다.

  1. 빨간색 글씨로 (origin/master)이라 되어 있는 부분이 있다. 이는 remote repo의 HEAD가 현재 local repo의 어떤 커밋까지 적용되어 있는지를 의미한다. 여러분은 ‘This is 1st commit written in 1st-branch’까지만 remote repo에 push했기 때문에, 현재 remote repo는 그 위치에 멈춰 있다. 이후에 push를 하면 위치가 바뀔 것이다.
  2. master의 로그를 보면,
    1. (origin/master) 이후에 (2nd-branch)가 있다. 이는 2nd-branch로부터 (merge하여) 가져온 커밋임을 의미한다. 바로 다음의 (3rd-branch)도 마찬가지이다.
    2. (HEAD -> master)는 이전에 잠깐 설명했었는데, 현재 위치(HEAD)를 나타내는 동시에 현재 브랜치의 이름을 나타낸다.
  3. 로그를 출력한 그림의 맨 윗부분을 보면, “Your branch is ahead of ‘origin/master’ by 3 commits”라는 문구가 있다. 이는 조금 전 설명과도 일치하는 부분인데, 여러분의 local repo의 master가 remote repo의 master보다 3개의 커밋만큼 수정사항이 더 많다는 뜻이다. 3개의 커밋은 각각 (origin/master) 커밋 이후의 커밋들 3개이다.
    1. (2nd-branch) This is the 1st commit written in 2nd-branch
    2. (3rd-branch) This is the 1st commit written in 3rd-branch
    3. (HEAD -> master) Merge branch ‘3rd-branch’
  4. 그럼 왜 2nd-branch와 3rd-branch로 checkout했을 때는 그런 메시지가 없는지 알 수 있을 것이다.
    1. 2nd-branch와 3rd-branch는 push한 적이 전혀 없다. 즉, remote repo에는 해당 브랜치들에 대한 정보가 전혀 없다. 브라우저를 켜서 확인해보라.

이제 local repo의 master의 변경사항을 remote repo에 업데이트하자.

git status
git push

11_push

master에서는 파일을 직접 수정하지 않고 merge만 했기 때문에, git addgit commit 명령이 따로 필요 없다.

12_browser


Ticket branch 업데이트

티켓 브랜치(서브 브랜치)의 로그는 master의 로그보다 조금씩 적었다. 그 이유는 master에는 모든 수정사항이 적용되었지만, 2nd-branch와 3rd-branch는 각각 상대 서브 브랜치의 수정사항이 적용되어있지 않기 때문이다.

물론 2nd-branch와 3rd-branch는 각각 서로 부모 자식 간의 관계가 아니기 때문에, 서로 직접 상호작용할 이유는 없다.
상호작용은 직접 연관된 master와만 하면 된다.

master에 적용된 수정사항(자신의 것이 아닌, 팀원이 만든 커밋을 가져온다)을 가져오고 싶으면 다음을 수행하라.
바로 push까지 수행한다.

git checkout 2nd-branch
git rebase master
git push –set-upstream origin 2nd-branch

13_2nd

위의 그림에서 “Fast-forwarded 2nd-branch to master.”라는 문구를 볼 수 있다. 2nd-branch에서 더 이상의 수정사항이 발생하지 않은 상태에서 master의 커밋을 가져왔기 때문에 fast-forward 조건이 성립하고, 이를 확인할 수 있다.

다음 명령들을 수행하여 master의 로그와 비교하면 2nd-branch의 로그와 커밋 코드가 완전히 같아졌음을 볼 수 있다.

git checkout master
git log –oneline

팀원의 브랜치도 업데이트해 주자.

git checkout 3rd-branch
git merge master
git push –set-upstream origin 3rd-branch

이 경우에는 merge나 rebase나 별다른 차이가 없다.

이제 브랜치의 기본적인 내용은 끝났다.


Merge vs Rebase

브랜치 간 병합을 수행할 수 있는 명령은 merge와 rebase가 있는데, 간략히 차이를 적어 보면 다음과 같다.

Merge: git merge <branch>로 사용. 해당 브랜치의 변경사항을 현재 브랜치로 (그대로) 가지고 온다. 이때 가져온 커밋들은 현재 HEAD의 위쪽에 그대로 쌓인다. 경우에 따라 Merge commit이 남을 수 있다.
Rebase: 이름 그대로 base를 바꾸는 것이다. base가 되는 커밋의 위치를 바꾼다.

base를 예시를 들어 설명하면,

  1. 3rd-branch는 master에서 생성되었다. 이때 생성 시점은 master가 “6df3479 This is 1st commit written in 1st-branch” 커밋까지 포함한 상태였다. 여기서 3rd-branch의 base는 바로 이 커밋이다.
  2. git rebase master 명령을 실행하면, master의 tip(HEAD) 으로 base를 재설정한다. 그러면 master의 커밋들로부터 시작한 셈이 되므로, master의 커밋들을 가져온 결과와 같다.

Merge와 rebase는 사용법이 조금 다르다. 이는 나중에 설명하도록 하겠다.
사람에 따라서는 별 구분 없이 사용하기도 한다.


이제 git의 branch에 대한 기본적인 설명을 알아보았으니, 다음 글에서는 브랜치를 관리하는 법에 대해서 알아본다.


Git 명령어

GitHub 사용법 - 00. Command List에서 원하는 명령어를 찾아 볼 수 있다.

Comment  Read more

GitHub 사용법 - 04. branch 기본 1

|

주의: 이 글을 읽는 여러분이, 만약 git을 많이 써 봐서 익숙한 것이 아니라면, 반드시 손으로 직접 따라 칠 것을 권한다. 눈으로만 보면 100% 잊어버린다.

저번 글에서 작업하던 것을 이어서 한다. 그때 master branch란 말을 잠깐 언급했었다.


Branch

여러분은 별다른 설정을 하지 않았지만 master branch에서 계속 작업을 하고 있었다.
master branch란 모든 repository의 기본 혹은 메인이라고 보면 된다. 일반적으로 repo의 모든 것은 master branch를 중심으로 행해진다.

큰 프로젝트든 개인 프로젝트이든 최종 결과물은 master branch에 있기 마련이며, master branch로부터 파생된 다른 branch들로부터 수정 사항을 만든 다음 master에 병합하는 과정을 거치게 된다.

여러 사람이 협업할 경우 각자 따로 브랜치를 쓰게 되며, 각 브랜치에서는 새로운 기능을 개발하거나 버그 수정이 이루어진다. 물론 완료되면 master branch에 병합되게 된다.

위의 설명이 정석적인 git repo의 운영방법이고, master branch에는 일반적으로 직접 수정을 가하지 않는다. 따라서 별다른 일이 없다면 본 글에서부터는 master branch에 직접 commit을 날리지 않고, branch를 만든 다음 병합하는 과정을 거칠 것이다.

잠깐 브라우저를 켜서 브랜치 부분을 클릭해 보자.

01_init_screen

그러면 ‘Default’로 표시되어 있는 master branch 하나가 보일 것이다.

02_master

이제 명령어 입력을 시작하자.


branch 목록 보기

목록을 보는 옵션은 여러 가지가 있다. git branch 혹은 git branch --list를 입력해 보자.

git branch

03_list

위 명령은 local repo에 저장되어 있는 branch들의 리스트를 보여 준다. 다른 branch를 만들지 않았기 때문에 master 하나밖에 보이지 않을 것이다.

이제 여러분은 다음과 같은 형태의 트리를 갖고 있다.
그렇다. branch는 tree의 것이다.


조금 더 자세하게 그리기 위해, git log --oneline을 명령창에 입력한다. 무엇을 하는 것인지 잊어버리지는 않았을 것이다.

05_log

이제 트리를 다시 그려보자.

06_branch

앞으로 위와 비슷한 그림이 자주 나올 텐데, 각각의 의미를 정확히 알고 있는 것이 앞으로 git을 이해하는 데 큰 도움이 될 것이다.

  1. 저번 글에서 간략히 언급했는데, 앞의 복잡한 숫자는 16진수 숫자로서 각 커밋의 고유한 코드이다. 유일한 값이므로 어떤 커밋인지 분간할 때 도움이 될 것이다.
    1. 여러분이 커밋 메시지를 간략하게 작성할수록 16진수 코드를 봐야 하는 상황이 많아진다.
  2. 16진수 코드 다음에는 여러분이 입력한 커밋 메시지가 나온다.
  3. (HEAD -> master, origin/master) 메시지는 이전 글에서 설명했다. 사실 1, 2번도 전부 설명했다.

그런데 origin이 무엇인지 궁금하지 않은가?


옵션: origin

저번 글에서는 일반적으로 remote repo의 이름은 origin으로 둔다고 하였다. 그러나 이는 사용자 마음이다.

dummy repo를 하나 만들자.

08_dummy

dummy local repo도 만들어 올려보자. 이때 origin 대신 dummy_origin으로 입력해 보자.

10_dummy

그리고 파일을 수정하고 추가한 뒤 git push origin master를 입력하면 오류가 뜬다.
dummy_origin으로 입력하면 잘 되는 것을 확인할 수 있다.
즉 origin은 add나 commit 명령처럼 정해져 있는 것이 아니라 사용자가 마음대로 정하는 것이지만, 별다른 이유가 없다면 origin으로 두는 것이 괜찮다.

12_dummy

참고로 git status -sgit status보다 간략한 버전이다.

이제 dummy는 dummy니까 갖다 버리면 된다. Settings 탭의 맨 아래로 가보면 다음과 같은 부분이 있다.

14_dummy

삭제하면 된다.

15_dummy


원격 branch 목록 보기

이제 dummy repo는 잊어버리고, 원래 하던 것으로 돌아오자.

local repo 말고 remote repo의 브랜치를 알고 싶다면 다음 중 하나를 입력한다.

git branch -r
git branch –remote

local이랑 remote 전부 보고 싶으면 다음을 입력한다.

git branch -a
git branch –all

07_branch_op

-r 옵션과 -a 옵션의 remote repo 표기가 조금 다르다.
-a 옵션은 local repo와 remote repo를 구분하기 위해 ‘remotes/’를 remote repo 앞에 붙인다.
-r 옵션은 remote repo만 보여주기 때문에 ‘remotes/’ 표시가 필요 없다.

이제 local repo와 remote repo에 무엇이 있는지 알았으니, 브랜치를 새로 만들어 보자.
원래는 서브 브랜치를 새로 생성할 메인 브랜치(master일 필요는 없다)로 이동하는 과정이 우선되어야 하지만, 지금은 master branch 하나뿐이므로 그럴 이유가 없다.

git branch 1st-branch
git branch -a

16_create_branch

현재 있는 브랜치 앞에는 ‘*‘이 있다. 여러분의 컴퓨터 환경에 따라 다를 수는 있으나, Windows cmd 기준으로는 현재 있는 브랜치는 초록색, remote repo의 브랜치는 빨간색으로 표시된다.

branch 이동: checkout

이제 새로운 브랜치로 이동해 보자.

git checkout 1st-branch

17_checkout

first.py에 다음을 추가한다. 이제부터 여러분은 first.py를 수정한 다음, 커밋을 만들고, master branch에 병합하는 과정을 거칠 것이다.
앞서 설명했듯 서브 브랜치를 만들어서 그곳에서 작업한 후 master branch에 병합하는 것이 정석적인 방법이다.

print("This is the 1st sentence written in 1st-branch.")

그리고 3종 세트를 입력한다. 싫으면 옵션에서 다루었던 push.bat을 입력해도 상관없다.

18_1st_commit

사실 그냥은 안 된다. remote repo 기준에서 여러분이 로컬에 만든 1st-branch라는 브랜치는 전혀 알 수 없는 것(정확히는 local repo에 upstream branch가 없는 것이다)이며, 연결 작업이 필요하다.
다행히 upstream branch(local branch와 연결할 remote branch)를 설정하는 방법을 명령창에서 친절히 알려 준다.

git push –set-upstream origin 1st-branch

remote repo 이름이 origin이고 current branch의 이름이 1st-branch이기 때문에 저렇게 입력해주면 된다.

19_upstream

브라우저를 확인해보자.

20_push

브랜치가 2개가 되었음을 확인할 수 있다.

이제 다음과 같은 상태가 되었다.

21_log

22_branch

참고로 1st-branch의 커밋들의 16진수 코드는 master branch의 같은 커밋의 16진수 코드와 똑같다.
master branch로부터 생성했으니 당연한 말이다.

방금 수정했기 때문에, first.py의 현재 상태는 다음과 같을 것이다.

print("Hello, git!") # instead of "Hello, World!"
print("Hi, git!!")

print("This is the 1st sentence written in 1st-branch.")

그럼 이제 master branch로 다시 돌아가 본다.

git checkout master

그리고 first.py를 다시 확인해 보라.

print("Hello, git!") # instead of "Hello, World!"
print("Hi, git!!")

마지막 문장이 사라졌을 것이다. 편집기를 사용하고 있었다면 ‘다시 로드’를 클릭하라.

여러분은 1st-branch에서 작업했을 뿐이다. master branch로부터 서브 브랜치를 생성하는 순간부터, 어떤 새로운 상호작용을 하기 전까지 1st-branch는 master branch와는 ‘거의’ 독립적인 공간에 가깝다. 따라서 branch를 checkout하는 순간 1st-branch에서 수정했던 사항이 보이지 않게 되는 것이다.

물론 진짜로 사라진 것은 아니다. 다시 1st-branch로 checkout하면 내용이 돌아올 것이다.
아무튼 다시 master branch로 이동하자.

브랜치 병합: merge

글의 윗부분에서 프로젝트 진행 과정을 설명하면서 다음과 비슷한 말을 했었다.

  1. 서브 브랜치를 만들어 작업한다.
  2. 메인 브랜치(master branch)로 이동한다.
  3. 서브 브랜치의 내용을 메인 브랜치에 병합한다.

위 과정 중 여러분은 1, 2번을 완료했다. 이제 3번을 하기만 하면 된다.
현재 브랜치가 master branch인지 꼭 확인한 후 다음을 진행해야 한다.

git merge 1st-branch
git push

참고로 merge한다고 remote repo에 절대로 자동으로 올라가지 않는다. 따라서 push를 해 주어야 한다. 이

23_merge

그리고 first.py를 확인하면 1st-branch에서 추가했던 문장이 들어 있는 것을 확인할 수 있다.
또한 커밋의 16진수 코드도 1st-branch의 것과 같음을 확인할 수 있다.

24_log

즉 다음과 같은 상태이다.

25_branch

위와 같이 병합하는 것을 fast-forward라 한다. fast-forward는 master branch에서 서브 브랜치(1st-branch)를 하나 만든 뒤 master에는 별다른 수정 사항이 없을 때 위 그림처럼 master branch에는 같은 코드의 커밋이 적용되며, 빨리감기하듯 따라간다는 점에서 이러한 이름이 붙었다.

branch 삭제, 목록 업데이트

그런데 그림을 보면 master와 1st-branch는 모두 살아 있는 branch이다. 실제로 1st-branch에 커밋을 만들고 push하면 1st-branch만 업데이트된다.
이는 Merge 명령은 non-destructive operation으로, master branch에서 1st-branch의 내용을 가져간다 해도 1st-branch는 전혀 변경되지 않는다.
다른 블로그들에서는 완전히 같은 곳을 가리키는 것처럼 말하지만, 실제로는 “정말로” 같은 레퍼런스인 것은 아니다. git은 모든 걸 자동으로 해주지는 않기 때문이다.
그러나 bugfix같이 잠시 만들었다가 없앨 목적으로 만든 branch라면, 이 브랜치를 없앨 필요가 있다.
물론 이 예제의 경우 bugfix는 아니지만, 몇 줄짜리 코드에 bug가 있으면… 흠.

브랜치를 제거할 때는 두 가지를 확인해야 한다. local branch와 remote branch이다.

git branch -d 1st-branch
git push -d origin 1st-branch
git fetch

당연히 -d 대신 –delete도 가능하다.

세 번째 명령 git fetch는 원격 브랜치 목록을 업데이트하는 것이다. 원격 브랜치 목록도 자동으로 업데이트되지 않기 때문이다.

26_delete

27_1

remote repo에도 master branch 하나만 남은 것을 확인할 수 있다.


다음 글에서는 branch에 대해서 더 알아본다.


Git 명령어

GitHub 사용법 - 00. Command List에서 원하는 명령어를 찾아 볼 수 있다.

Comment  Read more

파이썬 정규표현식(re) 사용법 - 08. 예제(단어, 행)

|

파이썬 정규표현식(re) 사용법 - 01. Basic
파이썬 정규표현식(re) 사용법 - 02. 문자, 경계, flags
파이썬 정규표현식(re) 사용법 - 03. OR, 반복
파이썬 정규표현식(re) 사용법 - 04. 그룹, 캡처
파이썬 정규표현식(re) 사용법 - 05. 주석, 치환, 분리
파이썬 정규표현식(re) 사용법 - 06. 치환 함수, 양방탐색, 조건문
파이썬 정규표현식(re) 사용법 - 07. 예제(숫자)
파이썬 정규표현식(re) 사용법 - 08. 예제(단어, 행)
파이썬 정규표현식(re) 사용법 - 09. 기타 기능


이 글에서는 정규표현식으로 처리할 수 있는 예제를 설명한다.

본 글에서 정규표현식은 regex와 같이, 일반 문자열은 ‘regex’와 같이 표시하도록 한다.

파이썬 버전은 3.6을 기준으로 하나, 3.x 버전이면 (아마) 동일하게 쓸 수 있다.
2.7 버전은 한글을 포함한 비 알파벳 문자 처리가 다르다.


단어

기초

문제 1: colour, Color, color 등 모든 버전의 color에 일치되는 정규식을 작성하라.

문제 1 정답보기

r'\b(?i)colou?r\b'


(?i) 모드 변경자 대신에 re.IGNORECASE 옵션을 주어도 상관없다.

다음 문제는 이전에 본 적이 있는 문제이다.

문제 2: cat에는 일치되지 않고, cat을 포함하면서 그보다 긴 단어, staccato, cats, tomcat 등에 일치되는 정규식을 작성하라.

문제 2 정답보기

r'\Bcat\B'



특정 단어 배제

문제 3: 이번에는 특정 단어를 제외시켜 보자. reg를 제외한 모든 단어에 일치되도록 하는 정규식을 작성하라.

문제 3 정답보기

r'\b(?!reg\b)\w+'


생각하기 조금 어려웠을 것 같다.

부정 전방탐색이 빛을 발하는 순간이다. 부정 전방탐색을 씀으로써 앞에 단어 경계가 있고(\b), ‘reg’로 이루어져 있으며(reg), 바로 뒤에 \b이 나와 끝나버리는, 즉 ‘reg’에 정확히 일치되는 순간이 되면 위 정규식은 일치에 실패한다.
따라서 특정 단어 ‘reg’를 배제할 수 있게 된다. 부정 전방탐색의 일치에 실패하면, 그 다음 부분인 \w+를 갖고 일치를 시도한다. 단어면 성공할 것이다.

문제 4: 이번엔 특정 단어뿐 아니라 그 단어를 포함하는 단어도 제외시켜 보자. reg뿐만 아니라 regex, register, dreg 등은 모두 제외되어야 한다.

문제 4 정답보기

r'\b(?:(?!cat)\w)+\b'


문제 3번을 생각해냈으면 이 문제도 풀 수 있을 것이라고 생각한다.

주어진 조건은 프로그래밍이 가능한 다음 세 문장으로 압축할 수 있다.

  1. 단어 중간의 어떤 부분에서든 그 앞에 ‘reg’가 와서는 안 된다. (?!cat)\w
  2. 1번을 만족했으면, 단어 문자가 1개 이상은 있어야 한다. ( ... \w)+
  3. 물론 양끝에 단어 경계는 있어야 한다. \b ... \b

위 세 조건을 적용시키면 ‘reg’가 단어에 포함된다면 일치되지 않는다는 사실을 알 수 있다.

문제 5: 바로 뒤에 숫자가 따라오는 단어를 무시하는, 다른 모든 단어를 검색하는 정규식을 작성하라. 단어와 숫자 사이에는 비 단어 문자가 온다고 가정하자.

문제 5 정답보기

r'\b\w+\b(?!\W+\d+\b'


이번에도 부정 전방탐색을 사용하면 된다. 무엇을 제외하고 싶다면 부정 양방탐색을, 다른 조건을 더 추가해야 한다면 긍정 양방탐색을 사용한다는 것을 기억하자.


빈 행 제거

문제 6: 빈 행을 제거하자. 예컨대 ‘\n\n’ 을 ‘\n’ 하나로 치환하여 불필요한 행을 제거하는 식이다. 운영체제에 독립적으로 작성되어야 한다.

문제 6 정답보기

re.sub(r'\n+', r'\n', string)


개행 문자 말고 모든 공백 문자를 하나로 치환하려면 다음을 쓰면 된다.

re.sub('\s+', ' ', string)

가로 공백 문자만 치환하려면 \s 대신 [ \t]를 사용하라.

중복 행 제거

문제 7: 중복 행을 제거하자. 똑같은 내용의 행이 두 번 이상 반복되는 행을 하나만 남겨두고 모두 제거하자.

문제 7 정답보기

re.sub(r'^(.*)(?:(?:\r?\n|\r)\1)+$', r'\1', re.MULTILINE)


복잡해 보이지만 크게 어렵진 않다. 중복 행이란 다음과 같은 구조일 것이다.

  1. 행 내용
  2. 행 구분자
  3. 행 내용
  4. 행 구분자
  5. 행 내용 …

행 내용은 (.*)으로 간단히 해결된다. 나중에 똑같은 것을 일치시켜야 하므로 캡처 그룹을 사용한다.
그리고 행 구분자를 사용하는데, 운영체제에 상관없이 사용하기 위해 조금 복잡하게(\r?\n|\r)이 들어갔지만, 자신이 무슨 운영체제에서 프로그래밍하고 있으며 어떤 운영체제에서 작성된 파일을 갖고 있는지 안다면 하나만 써도 무방하다.
그 다음은 행 내용을 재사용하기 위한 \1, 1회 이상 반복을 위한 +, 그리고 한 행만 남겨두고 제거하기 위한 r'\1'을 사용하면 해결된다.

주의할 점으로는 re.MULTILINE 옵션을 주어야 하며, re.DOTALL은 절대로 주어서는 안 되는 옵션이라는 점이다.


조금 더 복잡한 예제를 정리해 두면 좋겠지만, 그때그때 맞게 쓰는 것이 더 나을 것 같아서 굳이 따로 정리할 필요는 없을 것 같다.

다음 글에서는 re 패키지에 포함된 다른 함수들을 다루도록 하겠다.

Comment  Read more

파이썬 정규표현식(re) 사용법 - 07. 예제(숫자)

|

파이썬 정규표현식(re) 사용법 - 01. Basic
파이썬 정규표현식(re) 사용법 - 02. 문자, 경계, flags
파이썬 정규표현식(re) 사용법 - 03. OR, 반복
파이썬 정규표현식(re) 사용법 - 04. 그룹, 캡처
파이썬 정규표현식(re) 사용법 - 05. 주석, 치환, 분리
파이썬 정규표현식(re) 사용법 - 06. 치환 함수, 양방탐색, 조건문
파이썬 정규표현식(re) 사용법 - 07. 예제(숫자)
파이썬 정규표현식(re) 사용법 - 08. 예제(단어, 행)
파이썬 정규표현식(re) 사용법 - 09. 기타 기능


이 글에서는 정규표현식으로 처리할 수 있는 예제를 설명한다.

본 글에서 정규표현식은 regex와 같이, 일반 문자열은 ‘regex’와 같이 표시하도록 한다.

파이썬 버전은 3.6을 기준으로 하나, 3.x 버전이면 (아마) 동일하게 쓸 수 있다.
2.7 버전은 한글을 포함한 비 알파벳 문자 처리가 다르다.


숫자

십진 정수

문제 1: 가장 간단한 십진 정수를 찾는 정규식을 작성하라.

문제 1 정답보기

r'\b[0-9]+\b'


문제 2: 가장 일반적인 형태의 십진 정수를 찾는 정규식을 작성하라. 맨 앞에 ‘+’ 혹은 ‘-‘ 기호가 붙어 있을 수 있으며, 기호와 숫자 사이에는 공백이 하나 있을 수 있다.

문제 2 정답보기

r'(?:[+-] ?)?\b\d+\b'

비 아스키 숫자를 포함하고 싶다면 \d를, 오로지 아라비아 숫자 10개만 숫자로 인식하게 하고 싶다면 [0-9]를 사용하도록 한다.
또한 문자열 전체에 일치되게 하고 싶다면 \A\Z를 사용하면 된다.


십진 정수의 leading-zero 제거

문제 3: 십진 정수의 앞에 오는 0들을 제거하는 정규식을 작성하라. 기호는 없다고 가정하자.

문제 3 정답보기

re.sub(r'\b0*([1-9][0-9]*|0)\b', r'\1', string)


leading-zero가 될 수 있는 부분과 그렇지 않은 부분을 구분하기만 하면 이 문제는 풀린다. 그리고 leading-zero를 제거하는 것이 목표이므로 캡처된 부분으로 re.sub 메서드를 쓰면 해결된다.

한 가지 주의점은 ‘0’ 또는 ‘00’ 등 진짜 0밖에 없는 경우이다. 이 경우 0을 하나 남겨두어야 하기 때문에, leading-zero 뒤 숫자 부분에서 0을 한 개 남겨 두었다(|0).


16진수

문제 4: 16진수를 찾는 정규식을 작성하라. 0-9의 숫자와 A-F 알파벳만 쓴다고 하자.

문제 4 정답보기

r'\A[0-9A-F]+\Z'


문제 5: 16진수를 찾는 정규식을 작성하라. 16진수는 아라비아 숫자 10개 또는 A-F, a-f를 사용하되 대문자와 소문자를 섞어 쓰지는 않는다. 또한, 16진수임을 나타내기 위해 숫자 바로 앞에 ‘0x’나 ‘&H’(hexa의 의미)를 붙이거나, 혹은 숫자 뒤에 ‘H’를 붙인다.

문제 5 정답보기

r'(\b0x|&H)?(?:[0-9A-F]+|[0-9a-f]+)(?(1)|H?)\b'


꽤 어려워 보일 것이다. 정규식은 복잡해지면 주석이 꼭 필요하다.

  1. (\b0x|&H)?: 보통 단어 경계를 맨 앞에 두는데, 이번엔 다자택일 중 하나에 두었다. 이유는 엠퍼샌드(‘&’)는 문자 집합에 포함되지 않으므로 단어 경계를 쓰면 안 된다.
    1. 앞에 16진수 식별자를 두는 것은 선택적이므로 ?를 쓴다.
    2. 그리고 이 부분은 첫 번째 캡처 그룹을 형성한다.
  2. (?:[0-9A-F]+|[0-9a-f]+): 16진수 숫자를 나타내는 부분만 빼고 싶다면 앞부분의 비 캡처 그룹임을 의미하는 ?:를 빼면 된다.
    1. 대소문자 혼용을 허용하지 않았으므로 대문자를 쓸 때와 소문자를 쓸 때를 분리하였다.
  3. (?(1)|H?): 조건문이다. 첫 번째 캡처 그룹에서 만약에 ‘0x’나 ‘&H’가 일치되었으면, 뒤에 또 ‘H’ 식별자를 두지는 않는다. 따라서 일치되었으면(‘맞으면’) 아무것도 없는 경우를, 일치되지 않았으면 ‘H’ 식별자를 선택적으로 일치시킬 수 있도록 한다.
  4. \b: 단어 경계이다.

참고: 지정된 범위의 숫자인지 확인

이는 정규식으로 작성하길 추천하지 않는다. 아니면 함수로 따로 빼 두는 것을 추천한다.
그냥 숫자인지만 정규식으로 확인하고, 주어진 범위인지는 다른 함수로 확인하자.

그래도 예를 하나 들어 보겠다. 얼마나 복잡한지를 느끼면 된다.

문제 6: 1 이상 365 이하의 정수인지를 확인하는 정규식을 작성하라.

문제 6 정답보기

r'(36[0-5]|3[0-5][0-9]|[12][0-9]{2}|[1-9][0-9]?)'


그냥 따로 씁시다.

number = re.search('\d+', '몇 이상 몇 이하의 수 38개')
number = int(number.group())

if 1 <= number <= 365:
    print('Yei!')

결과

Yei!

부동소수점 수

3번째 글의 끝에서 다음과 같은 문제가 있었다.

문제 7: 1.2이나 3.72e3, 1.002e-12 같은 수를 부동소수점 수 또는 과학적 표기법으로 표기한 수라고 한다. 이와 같은 수에 일치하는 정규표현식을 작성하라.

문제 7 정답보기

r'\b\d*\.\d+(e\d+)?'


조금 더 일반화한 버전을 문제로 하나 더 내 보았다.

문제 8 : 다음 조건에 맞는 부동소수점 수를 찾는 정규식을 작성하라.

  1. 부호부, 정수부, 가수부는 선택이다.
  2. 지수부 역시 선택이다.
  3. 정수부가 없으면 가수부는 필수이다.
  4. 가수부가 없으면 소수점은 선택이다.
문제 8 정답보기

r'[+-]?(\b[0-9]+(\.[0-9]*)?|\.[0-9]+)([eE][+-]?[0-9]+\b)?'


정수부와 가수부가 동시에 없으면 안 되는 조건은 조건문을 사용해도 되고, 다자택일을 사용해도 된다. 이 경우는 다자택일을 사용했다.


로마 숫자

문제 9: 현대식 로마 숫자에 일치되는 정규식을 작성하라. ‘IV’는 가능하고, ‘IIII’는 불가능하다.

문제 9 정답보기

r'\b(?=[MDCLXVI])M*(C[MD]|D?C*)(X[CL]|L?X*)(I[XV]|V?I*)\b'


  1. (?=[MDCLXVI]): 로마 숫자는 0이 없기 때문에, 문자가 하나라도 있는지 체크하는 전방탐색 구문이다.
  2. M*: 로마 숫자에서 1000은 개수만큼 집어넣는다.
  3. ` (C[MD] D?C*)`: 작은 숫자가 앞에 오는 경우를 따로 빼서 다자택일로 처리하였다. 로마 숫자의 규칙을 생각하면 그리 어려운 부분은 아니다.
  4. 나머지 부분은 3번과 같다.

다음 글에서는 단어와 행 처리에 관한 예제를 다루도록 하겠다.

Comment  Read more

파이썬 정규표현식(re) 사용법 - 06. 치환 함수, 양방탐색, 조건문

|

파이썬 정규표현식(re) 사용법 - 01. Basic
파이썬 정규표현식(re) 사용법 - 02. 문자, 경계, flags
파이썬 정규표현식(re) 사용법 - 03. OR, 반복
파이썬 정규표현식(re) 사용법 - 04. 그룹, 캡처
파이썬 정규표현식(re) 사용법 - 05. 주석, 치환, 분리
파이썬 정규표현식(re) 사용법 - 06. 치환 함수, 양방탐색, 조건문
파이썬 정규표현식(re) 사용법 - 07. 예제(숫자)
파이썬 정규표현식(re) 사용법 - 08. 예제(단어, 행)
파이썬 정규표현식(re) 사용법 - 09. 기타 기능


이 글에서는 정규표현식 고급 기술에 대해서 설명한다.

본 글에서 정규표현식은 regex와 같이, 일반 문자열은 ‘regex’와 같이 표시하도록 한다.

파이썬 버전은 3.6을 기준으로 하나, 3.x 버전이면 (아마) 동일하게 쓸 수 있다.
2.7 버전은 한글을 포함한 비 알파벳 문자 처리가 다르다.


정규표현식 고급: 치환 함수 사용

01

여러분은 re.sub을 쓸 때 특정 문자열 혹은 정규식 내의 일부분은 그대로 갖다 쓰는 법을 배웠다.

그런데 일치부에 나타나지도 않고, literal text에도 나타나지 않는 문자열로 치환하고 싶은 경우가 있을 수 있다.
예를 들면 문자열 안에서 소수로 표현된 숫자를 찾은 다음 퍼센티지(%)로 변환하는 것을 정규식으로 쓴다고 하자.

re.sub 메서드는 인자 repl을 받을 때 정규식 일치부나 literal text 말고도 함수를 받을 수도 있다.
이 함수는 인자로 matchObj를 받으며, 문자열을 최종적으로 반환해야 한다.

예시를 보자.

def convertToPercentage(matchObj):
    number = float(matchObj.group())
    return str(number * 100) + '%'

print(re.sub(pattern=r'\b0\.\d+\b',
             repl=convertToPercentage,
             string='Red 0.250, Green 0.001, Blue 0.749, Black 1.5'))

결과

Red 25.0%, Green 0.1%, Blue 74.9%, Black 1.5

아주 어려운 내용이라서 advanced인 것은 아니고, 활용도가 높기 때문에 고급 기능으로 분류하였다.


정규표현식 중급: 행 단위 검색

행 단위로 자르는 것은 사실 어렵지 않다.

string = '''java - Click on button until alert is present in Selenium	2017년 5월 23일
Click OK on alert Selenium Webdriver	2016년 6월 5일
python - Click the javascript popup through webdriver	2012년 5월 29일
selenium - Read Message From Alert and click on OK	2012년 5월 17일
stackoverflow.com 검색결과 더보기'''

lines = re.split('\r?\n', string)
reObj = re.compile('(\d+)년\s+(\d+)월\s+(\d+)일')

for line in lines:
    matchObj = reObj.search(line) 
    if matchObj:
        string = matchObj.group(1) + '.' + matchObj.group(2) + '.' + matchObj.group(3)
        print(string)
    else:
        print('Not exists')

결과

2017.5.23
2016.6.5
2012.5.29
2012.5.17
Not exists

그러나 한 가지 주의할 점은 운영체제(OS: Windows, Mac, Linux)별로 라인피드(개행문자)가 다르다는 것이다. Windows는 ‘\r\n’를 행 구분자로 가지며, 각각 캐리지 리턴과 라인피드이다. 유닉스 계열인 Mac과 Linux는 ‘\n’을 가진다.
이 구분이 절대적인 것은 아니지만, 운영체제의 개행문자가 뭔지 확신이 서지 않는다면 \r?\n으로 쓰는 것이 안전하다.


정규표현식 고급: 전방탐색과 후방탐색

먼저 전방탐색이란 정규표현식이 진행하는 (원래) 방향으로, 문자열 처음에서 끌으로 가는 방향으로 탐색하는 것이다.
물론 후방탐색은 그 반대이다.

전방/후방탐색, 즉 양방탐색은 그 자체로 문자열에 일치된다기보다는, 문자열이 일치되기 위한 조건을 설정하는 것이다.
이미 여러분은 실제 문자열에 일치되지 않으면서 조건 설정을 하는 정규표현식을 몇 개 봐 왔다. \b, \A, ^ 등이 그 예이다.

어쨌든 양방탐색의 종류는 네 가지이다. 각각 긍정/부정 전방/후방 탐색이다.

  1. 긍정 전방탐색은 (?=<regex>)
  2. 긍정 후방탐색은 (?<=<regex>)
  3. 부정 전방탐색은 (?!<regex>)
  4. 부정 후방탐색은 (?<!<regex>)

으로 쓴다.

<p> 태그 안의 모든 텍스트를 검사한다고 하자. 그러나 여러분은 <p> 태그까지 문자열로 뽑아내고 싶지는 않을 것이므로, 이는 정규식의 일치부에 넣지 않도록 한다. 이때 후방탐색과 전방탐색을 문자 덩어리 앞뒤에 쓰면, <p> 태그 안이라는 조건을 설정하면서 동시에 <p> 태그는 일치부에서 제외시킬 수 있다.

예시를 보자.

print(re.search('(?<=<p>)\w+(?=</p>)', 
                'Kakao <p>ryan</p> keep a straight face.').group())

결과

ryan

<p> 태그는 제외된 것을 볼 수 있다.
위의 예시를 해석하면, 단어 문자에 해당하는 연속된 글자를 찾는데(\w+), 그 앞에는 <p> 문자열이 반드시 있어야 하고, 문자열 바로 뒤에는 역시 </p> 태그가 있어야 한다는 뜻이다.

실제로 문자열이 일치되는 것은 아님을 보여주기 위해, 쓸데없이 복잡하게 만든 예시를 하나 만들어 보았다.

print(re.search('<p>(?<=<p>)(\w+)(?=</p>)</p>',
                'Kakao <p>ryan</p> keep a straight face.').group(1))

결과

ryan

이러한 양방탐색 조건을 지정하는 것도, 명시적인 캡처로 인식되지 않는다.

부정 양방탐색도 사용법은 같다. 하지만 긍정 양방탐색은 <regex>에 해당하는 문자열이 있어야만 일치에 성공하는 데 반해, 부정 양방탐색은 <regex>와 일치에 실패해야만 그 다음 비교를 이어나간다.

보통 사람들은 비밀번호를 설정할 때 영문자 몇 개, 숫자 몇 개(보통 생일과 관련이 있다), 그리고 특수문자를 넣으라는 홈페이지의 요구대로 느낌표 하나를 끝에 붙인다.
그런데 여기서는 특수문자조차 없는 비밀번호를 찾으려고 한다. 즉 마지막에 ‘!’가 없는 비밀번호를 모두 찾는다고 하자.

이를 찾는 정규식은 다음과 같다.

print(re.findall(r'[a-z]+\d+(?!!)\b', 'tube1109! gorio303 ryan416'))

결과

['gorio303', 'ryan416']

느낌표가 없는 비밀번호는 두 개이다. 그리고 이를 잘 찾아 주었다.

물론 이렇게 간단한 데 양방탐색을 지정하여 쓰면 오히려 더 귀찮다. 하지만 이 기능을 쓸 데가 언젠가는 있을 것이다.

참고로, 파이썬의 후방탐색은 좀 비효율적으로 구현되어 있다고 알려져 있다. 따라서 후방탐색을 써야만 하는 경우에는 다음과 같이 대체하는 방법도 고려할 수 있다.

조금 전 <p> 태그 예시를 대체한 것이다.

print(re.search('<p>(\w+)</p>',
                'Kakao <p>ryan</p> keep a straight face.').group(1))

결과

ryan

이 방법이 더 깔끔할 수 있다.


정규표현식 고급: 조건문

이 부분은 한빛미디어의 ‘한 권으로 끝내는 정규표현식’ 책을 참고하였다.

여러분은 프로그래밍 언어에서 삼항 연산자로 조건문을 쓰는 것을 본 적이 있을 것이다.

C++의 예시는 다음과 같다.

x = a % 2 == 1 ? 3 : 4;

파이썬은 다음과 같다.

x = 1 if a % 2 == 1 else 4

정규표현식에서의 조건문은 다음과 같다. 조건문은, 캡처 그룹을 사용해 앞에서 문자열이 일치되었는지 아닌지에 따라 다음 부분에서는 다른 일치 조건을 제시해야 할 때 쓰인다.

(?(숫자)맞으면|아니면)

맞으면 또는 아니면에는 어떤 정규식이든 사용할 수 있다. 다만 그 안에서도 다자택일(|, OR)을 사용하려면 그 전체를 ( )로 묶어주어야 한다.

참고로 (숫자)는 여러분이 알고 있는 그 캡처랑 비슷한 것이다. 재참조부를 쓸 때 \1과 같이 썼는데, 조건문에서는 단지 (1)로 바뀌었을 뿐이다.

예시로, (a)?b(?(1)c|d)를 살펴보자. 이는 abc|bd와 같다.

  1. 먼저 a를 검사한다. 만약 찾는 문자열에 ‘a’가 있으면 첫 번째 명시적 캡처에는 ‘a’가 들어간다. 만약 ‘a’가 없으면, 빈 문자열이 (1)에 저장된다.
  2. 그 다음은 b이다. 만약 ‘b’가 없으면 일치에 실패하고 다음 위치로 넘어가게 된다. ‘b’가 있다고 가정하자.
  3. 그리고 이제 조건문이다. 만약 앞에서 ‘a’가 일치되었으면, 좀 전 일치된 ‘b’ 바로 다음에 ‘c’가 있는지를 검사한다. 만약 ‘a’가 없었으면, ‘b’ 다음에 ‘d’가 있는지를 찾게 된다.

다른 굉장히 복잡한 예시를 하나 들도록 하겠다(출처는 위에서 밝힌 책이다).

  1. 콤마로 구분되고
  2. one, two, three와 일치되되
  3. 각 단어는 최소 한번씩은 등장해야 하며
  4. 각 단어가 몇 개가 있든지 일치되어야 한다.

정답은

r'\b(?:(?:(one)|(two)|(three))(?:,|\b)){3,}(?(1)|(?!))(?(2)|(?!))(?(3)|(?!))'

이다.

하나씩 살펴보면,

  1. \b: 단어 경계는 이제 알 것이다.
  2. (?:(one)|(two)|(three)): one이나 two이거나 three를 캡처한다. 각각 따로 캡처되도록 바깥쪽 캡처 그룹은 비 캡처 그룹으로 지정하였다.
  3. (?:,|\b): 문제의 조건에서 각각의 단어는 콤마(‘,’)로 구분되고 마지막 단어 뒤에는 콤마가 없을 것이므로, , 또는 \b 다자택일로 묶어 놓았다. 콤마를 캡처하고 싶지는 않으므로 역시 비 캡처 그룹이다.
  4. (?:2번 3번){3,} 너무 길어서 2번과 3번을 따로 뺐다. 위에서 설명한 2번과 3번 전체를 감싸는 비 캡처 그룹이다. 세 단어가 각각 한 번씩은 나와야 하기 때문에, 반복 횟수는 최소 3이다.
  5. (?(1)|(?!)): 이제부터 조건문이다. 이 조건문은 캡처 그룹 1번((1), ‘one’)이 일치되었으면 그냥 일치되는 부분이다(‘맞으면’ 부분이 빈 문자열이다). ‘아니면’ 부분은 빈 부정 전방탐색 (?!)인데, 빈 정규식은 항상 일치되므로(즉, (1)에) 부정 전방탐색은 항상 실패한다. 따라서 이 조건문 (?(1)|(?!))(1)에 ‘one’이 일치되거나, 아니면 그냥 일치에 실패한다.
  6. 나머지 두 개의 조건문은 각각 ‘two’, ‘three’에 대응하며, 5번 설명과 같다. 이 세 가지 조건문을 조합하면, (1), (2), (3) 중 하나라도 일치에 실패한 단어가 있다면 이 전체 정규식은 일치에 실패한다. 따라서 세 단어가 한 번이라도 나와야 한다는 조건을 만족시킨다.

조건문 안에 양방탐색을 사용할 수도 있다. 이는 보통의 정규식과 거의 비슷하게 작동한다. 정규식 대신 양방탐색 조건이 일치하면 ‘맞으면’ 부분이, 일치에 실패하면 ‘아니면’ 부분이 바로 다음 문자열에 일치되는지를 시도한다.
부정 양방탐색을 쓸 수도 있으나, ‘맞으면’과 ‘아니면’ 부분이 바뀌는 효과를 가져오므로 지양하자.


다음 글부터는 예제를 다루도록 하겠다. 7번째 글은 숫자를 처리하는 예제에 관한 글이다.

Comment  Read more