sed 사용법 5 : 정규 표현 (RegEx) (3) 소괄호 (), 마침표(.) , 별(*)표 사용법, 그리고 전화번호 데이터 추출 실전
키보드에 있는 괄호는 크게 세가지가 있습니다. ()는 소괄호 {}는 중괄호 []는 대괄호라고 부릅니다.
정규표현(RegEx)에서 중괄호 {}는 그앞에 있는 글자가 몇번 연달아 나오는지를 표시합니다. 만약에 z{3}는 z가 연달아 세번 나오는 것을, y{2,5}는 y가 연달아 두번 이상 다섯번 이하로 나오는 것을, x{4,}는 x가 연달아 네번 이상 나오는 것을 표시합니다.
대괄호 []는 나와도 되는 글자의 집합을 표시합니다. [abcd]{3}는 a, b, c, d중에 아무거나 연달아 세번 나오는 것을, [a-z]{2,5}는 알파벳 소문자 아무거나 연달아 두번 이상 다섯번 이하 나오는 것을, [0-9]{4,} 는 0에서 9까지 숫자 아무거나 연달아 네번 이상 나오는 것을 표시합니다.
만약에 소문자 대문자 관계없이 알파벳 아무거나하고 0에서 9까지 숫자 아무거나 연달아 10번 이상 나오는 것을 표시하려면 [a-zA-Z0-9]{10,}라고 표시하면 됩니다.
이제 남을 괄호는 소괄호()입니다. 정규표현에서 ()를 쓰면, ()안에 표시한 패턴이 나오면 이를 찾아 저장합니다. 그리고 자장한 것은 나중에 \1 로 찾아서 쓸 수 있습니다. 만약에 패턴을 찾는데 두개의 소괄호를 써서 패턴을 찾았다면 첫번째 소괄호안에 있는 것은 \1로 두번째 괄호안에 있는 것은 \2로 찾아쓸 수 있습니다.
다음의 예를 보죠.
echo "My phone number is 404-123-4567." | sed -r "s/([0-9]{3}-[0-9]{3}-[0-9]{4})/\1 \1/"
위의 커맨드를 실행한 결과는 아래과 같습니다.
My phone number is 404-123-4567 404-123-4567.
원래는 전화번호가 한번 밖에 안나오는데, 이 전화번호를 찾아 두번 연달아 쓴 걸로 바꿉니다. 소괄호안에 있는 전화번호 패턴을 찾아서 \1에 저장해 놓고 이를 다시 \1 \1로 두번 반복한 텍스트로 바꾸는 겁니다.
이번엔 마침표(.)의 사용법을 알아보겠습니다. 마침표(.)는 어떤 글자도 상관없다는 것을 표시할때 씁니다. 숫자, 공백 특수기호 다 포함합니다. .+ 라고 쓰면 뭐든지간에 한자 이상 있는 것을 의미합니다. 아래의 커맨드를 실행해 보겠습니다.
echo "My phone number is 404-123-4567." | sed -r "s/.+/X/"
결과는 아래와 같습니다.
X
뭐든 써있기만하면 .+에 다 해당하기 때문에 그줄의 모든것을 X로 바꿉니다. 아래와 같이 쓰면 아얘 다 지워 버릴 수도 있습니다.
echo "My phone number is 404-123-4567." | sed -r "s/.+//"
여러줄의 텍스트를 echo커맨드를 써서 을 화면에 출력하려면 echo 커맨드 다음에 -e를 쓰고 텍스트안서는 \n를 추가하면 됩니다. 아래와 같이 치면 My phone …이라고 쓴 줄 앞뒤로 아무 것도 안써있는 빈 줄이 추가 됩니다.
echo -e "\nMy phone number is 404-123-4567.\n"
이렇게 화면에 출력되는 것을 파이프(|)로 sed 커맨드의 입력으로 넘겨보겠습니다.
echo -e "\nMy phone number is 404-123-4567.\n" | sed -r "s/.+/X/"
빈줄은 패턴으로 찾지 못해 X를 출력 안합니다. 다시 말해 .+로는 아무 것도 안써있는 줄을 잡아내지 못합니다. 그런데 .+대신 .*를 사용하면 아무 것도 안써있는 줄도 잡아낼 수 있습니다.
echo -e "\nMy phone number is 404-123-4567.\n" | sed -r "s/.*/X/"
빈 줄까지 패턴으로 인식해서 잡아내 X로 바꿔 출력합니다. 그 이유는 *가 아무것도 없거나 아니면 아무거나 한 개 이상 반복되는 것을 의미하기 때문입니다. .+는 .{1,}과 같고, .*는 .{0,}과 같다고 보면 되겠습니다.
이제 전화번호만 출력하는 것을 시도할 준비가 되었습니다. 전화번호 앞뒤에 뭐가 오던 .*로 다 잡아낼 수 있습니다. 따라서 아래의 커맨드를 쓰면 나머지 글자는 다 지우고 전화번호만 출력할 수 있습니다.
echo -e "\nMy phone number is 404-123-4567.\n" | sed -r "s/.*([0-9]{3}-[0-9]{3}-[0-9]{4}).*/\1/"
소괄호()안에 있는 전화번호 패턴 앞뒤로 .*가 있기때문에 전화번호뿐만아니라 전화번호 앞뒤에있는 모든 텍스트까지 잡아냅니다. 그중에 소괄호안에 있는 전화번호만 저장했다가, 전화번호가 저장된 \1 로 찾아낸 모든 텍스트를 지우고 전화번호로 바꿉니다.
그런데 여전히 빈줄도 그대로 출력합니다. sed가 아무것도 쓰는 것이 없어도 각 줄마다 한 줄씩 넘어가는 것은 여전히 출력하기 때문입니다. 이 때 쓸 수 있는 것이 -n 옵션입니다. sed를 쓸때 -n 옵션을 붙이면 아무것도 출력하지 않습니다. 대신 “s/패턴/텍스트/” 뒷부분에 p를 붙여 “s/패턴/텍스트/p”로 쓰면 패턴을 찾아 뭔가를 쓸때만 출력하게 합니다. 따라서 아래와 같이 쓰면 전화번호를 찾은 줄에서만 전화번호를 출력하고 다른 줄에서는 빈줄을 포함해 아무것도 출력하지 않습니다.
echo -e "\nMy phone number is 404-123-4567.\n" | sed -r -n "s/.*([0-9]{3}-[0-9]{3}-[0-9]{4}).*/\1/p"
빈줄에도 뭔가를 쓰는 경우를 보겠습니다.
echo -e "My name is Anakin Skywalker.\nMy phone number is 404-123-4567.\nMay the force be with you."
위의 커맨드를 실행하면 다음과 같은 텍스트를 화면에 출력합니다.
My name is Anakin Skywalker. My phone number is 404-123-4567. May the force be with you.
이제 전화번호만 찾는 sed 커맨드로 파이프(|)를 통해 넘겨주겠습니다.
echo -e "My name is Anakin Skywalker.\nMy phone number is 404-123-4567.\nMay the force be with you." | sed -r -n "s/.*([0-9]{3}-[0-9]{3}-[0-9]{4}).*/\1/p"
빈줄없이 전화번호만 줄력합니다.
그런데 전화번호를 (404) 123-4567 형식으로 쓰는 데도 많습니다. 그런데 괄호는 장규표현에서 이미 특수용도로 쓰기때문에 이를 글자로 인식하게 하려면 역슬래쉬(\)기호를 써서 \(와 \)로 써야합니다. 그러면 전화번호 패턴은 \([0-9]{3}\) [0-9]{3}-[0-9]{4} 가 됩니다. 이를 커맨드로 적용해 보겠습니다.
echo -e "My name is Anakin Skywalker.\nMy phone number is (404) 123-4567.\nMay the force be with you." | sed -r -n "s/.*(\([0-9]{3}\) [0-9]{3}-[0-9]{4}).*/\1/p"
국번뒤의 공백이 있는 경우도 있고 없는 경우도 있습니다. 이를 대비하려면 공백이 있고 없고를 표현하는 ” *”를 중간의 공백대신 써서 \([0-9]{3}\) *[0-9]{3}-[0-9]{4} 를 쓰면 됩니다.
echo -e "My name is Anakin Skywalker.\nMy phone number is (404)123-4567.\nMay the force be with you." | sed -r -n "s/.*(\([0-9]{3}\) *[0-9]{3}-[0-9]{4}).*/\1/p"
전화번호만 뽑아내는 커맨드는 sed -r -n “s/.*([0-9]{3}-[0-9]{3}-[0-9]{4}).*/\1/p” 나 sed -r -n “s/.*(\([0-9]{3}\) *[0-9]{3}-[0-9]{4}).*/\1/p” 가 되겠습니다.
이제 실제의 경우에 적용해 보겠습니다.
웹브라우저를 열어 yelp.com으로 가보겠습니다. 조지아주 둘루스 지역에 있는 한국식당을 찾아보겠습니다. 그럴려면 Find란에 Korean Restaurant라고 쓰고, 옆의 Near란에는 Duluth, GA라고 쓴다음 돋보기 모양의 검색 아이콘을 누르면 됩니다. 업소이름, 주소, 전화번호들이 출력됩니다. 전화번호는 (404) 123-4567 형식으로 나옵니다. 이 전화번호들만 출력해 보려고 합니다.
curl 커맨드를 써서 검색결과를 보여주는 웹페이지 소스를 먼저 출력해 보겠습니다. Enter를 치지 말고 curl -s “”를 터미널에 쓴 다음에, 따옴표 사이에 웹브라우저 주소창의 주소를 복사해 붙입니다.그러면 다음과 같은 커맨드가 됩니다.
curl -s "https://www.yelp.com/search?find_desc=Korean+restaurant&find_loc=Duluth,+GA"
상당히 긴 html 소스를 화면에 출력합니다. 이를 파이프(|)를 써서 전화번호를 잡아내 출력하는 sed커맨드의 입력으로 넘겨 실행해 보겠습니다.
curl -s "https://www.yelp.com/search?find_desc=Korean+restaurant&find_loc=Duluth,+GA" | sed -r -n "s/.*(\([0-9]{3}\) *[0-9]{3}-[0-9]{4}).*/\1/p"
그러면 아래와 같이 전화번호만 잡아내 화면에 출력합니다.
(678) 395-6226 (678) 475-9170 (470) 299-6150 (770) 814-2310 (678) 473-1190 (770) 680-4371 (770) 946-1000 (770) 622-7780 (678) 417-6644 (770) 497-9412 (770) 623-8411 (678) 417-6780 (470) 875-9000 (770) 497-1155 (770) 232-8881 (678) 395-4762 (770) 814-7747 (678) 580-0302 (678) 584-1103 (770) 232-1141 (678) 417-1131 (470) 227-1431 (770) 622-1300 (678) 205-0555 (770) 813-8202 (770) 232-1944 (678) 417-7769 (770) 932-7753 (770) 232-7500 (470) 268-8435
위의 결과를 phonenumber.dat 라는 파일에 기록하려면, 위의 코맨드 맨뒤에 > phonenumber.dat 만 붙여서 실행하면됩니다. 그러면 화면에 출력하는 내용을 모두 phonenumber.dat 파일에 저장합니다.
curl -s "https://www.yelp.com/search?find_desc=Korean+restaurant&find_loc=Duluth,+GA" | sed -r -n "s/.*(\([0-9]{3}\) *[0-9]{3}-[0-9]{4}).*/\1/p" > phonenumber.dat
만약에 Chinese restaurant 검색결과 첫번째 페이지의 전화번호를 phonenumber.dat에 추가하려면, chinese restaurant을 검색하는 yelp주소를 사용하되, 마지막 > phonenumber.dat는 >> phonenumber.dat 로 바꿔쓰면 됩니다.
curl -s "https://www.yelp.com/search?find_desc=Chinese+restaurant&find_loc=Duluth,+GA" | sed -r -n "s/.*(\([0-9]{3}\) *[0-9]{3}-[0-9]{4}).*/\1/p" > phonenumber.dat
>기호 하나만 쓰면 파일을 새로 만들고 처음부터 다시 기록합니다. 이전에 이 파일에 어떤 내용을 담고 있다면, 싹 지우고 다시 씁니다. 하지만, >> 를 쓰면 같은 이름의 파일이 이미 존재할때 원래 내용은 그대로 놔두고 파일 맨끝에 새로운 내용을 덛붙혀 씁니다.