본문 바로가기

Linux & Mac

Vi 사용법 2 (ex 모드 : 검색과 치환)

반응형

먼저 기본 설명과 명령 모드에 대한 설명은 이전 글을 참조해 주시기 바랍니다.

2021.04.13 - [Linux & Mac] - Vi 사용법 1 (명령 모드)

 

Vi 사용법 1 (명령 모드)

 Vi의 사용법에 대한 글은 10여 년 전에 이미 올렸었습니다. 그 후 계속 다양한 환경에서 Vi를 사용하면서 꾸준히 사용하는 기능과 사용하지 않는 기능, 그리고 여러 가지 팁들도 생겨서 업데이트

sbinroom.tistory.com

 ex 모드는 명령 모드에서 ':', '/', '?' 로 이용합니다.

도움말

 사실 노하우를 남기기 위한 글로서 열기, 닫기 부터 해야 하겠지만, 중요도를 생각해서 도움말로 시작 합니다. 명령모드에서 ":help" 를 입력하면 터미널 화면이 분할 되며, 도움말이 나오게 됩니다. 아래 스냅숏은 ":help"를 쳤을때 결과 입니다. 왼쪽과 같은 터미널 화면이 ":help"가 입력되면 터미널이 상하로 분할 되며, "help.txt" 파일을 보여 줍니다.

 조금더 자세한 설명을 원하시면, ":help [arg]"의 형태로 실행 하셔도 되고, 문서의 중반부 부터 나오는 추가 파일의 이름에서 "gf"를 눌러 해당 파일을 열어도 됩니다.

 Vi의 화면 분할기능은 방법에 따라 유용 할 수 있습니다. 하지만 저는 Vi의 분할 기능 보다, 다수의 터미널을 띄워놓고 터미널 사이를 이동 하며 사용하는 것을 선호하는 편입니다. 제가 사용하지 않는 기능 이라서 자세한 설명은 어렵다고 판단되어, 화면 분할에 대한 자세한 설명을 건너 뛰겠습니다. 원하는 비율을 지정하거나 해서 사용자가 원하는 대로 최적화가 가능한 부분 임으로 필요하시면 찾아 보시는 것도 좋은 방법 일것 같습니다.

파일 열기, 저장, 닫기(Vi 종료)

 vi를 열때는 보통 "vi [arg]"의 방식으로 열게 됩니다. 예를 들면 "vi main.cpp src/*.cpp" 과 같은 방식으로 관련 폴더에 소스코드를 모두 여는 방식 이죠. 이렇게 다수의 파일을 하나의 vi에디터에서 제어 하려면 buffer explorer와 같은 플러그인이 필요 합니다. 이런 플러그 인에 대한 내용은 3편이나 4편에서 다룰 예정 입니다.

 이외에 열려 있는 에디터에서 파일을 추가 하고 싶으시면 ":e [arg]"의 명령으로 열수 있습니다. "[arg]"에 상대 경로 이던, 절대 경로이던 원하는 파일명을 입력하시면 되며, [tab] 키를 이용해서 자동 완성 기능을 이용하실수 있습니다. 단 이때 열려 있는 파일이 편집 되었다면, 저장이 완료 되어야 합니다. 만약 저장 되지 않았다면 아래 스냅숏과 같은 에러 메시지가 출력 됩니다.

 이 경우 저장을 원하시면 아래의 저장 명령(":w")으로 저장 하시거나, 'u' 키를 이용해 변경 사항을 전부 되돌리시면 됩니다.

 파일을 저장 할 때에는 ":w"을 이용하시면 됩니다. 명령어 입력과 동시에 파일 시스템의 파일이 덮어 씌워 짐으로 최종본임이 확실할 때 저장해주도록 합시다. 아니면 백업을 자주 해야겠죠. 만약 이전 파일로 되돌리고 싶으시면 'u'키를 이용해서 편집한 내역을 되돌린 후 다시 저장하는 방식으로 되돌릴수 있습니다. 다만 레지스터 용량의 한계를 넘어서는 되돌릴 수 어쩔수 없으니 주의 하시기 바랍니다.

 다른 파일명으로 저장 하고 싶으시면 ":w [arg]"로 저장해 주시면 됩니다. 하지만 개인적으로 이 방식은 추천하지 않습니다. 이유는 사람은 실수 하기 때문 입니다. 편집기에 따라서 다른이름으로 저장 명령이 내려지면, 이후 편집기 상태는 두가지 중 하나가 됩니다. 예를 들면, A파일을 편집 하다가, B파일로 다른이름 저장하기 명령을 수행 한다면, 편집기는 B파일을 저장 한 후, A파일을 편집하거나, B파일을 편집 합니다. 많은 수의 오피스 문서 관리 툴 들은 저장후 편집 내역이 B파일에 적용 됩니다. 하지만 Vi는 A파일과 똑같은 B파일을 만들어 줄 뿐, 이후 편집 내역은 A파일에 적용 됩니다. 많은 경우 혼란을 초래 할 가능성이 있죠.

 실수를 미연에 방지하기 위해서, 추천 드리는 방법은 ":w [arg]"를 쓰는 대신 다른 터미널 에서 "cp [arg] [arg]"를 통해 파일을 복사 하고, ":e [arg]" 명령으로 파일을 여는 방법입니다.

 마지막으로 닫기 입니다. 닫기는 ":q" 로 하시면 됩니다. ":e"와 마찬기지로 편집중인 파일에서 변경한 사항이 있다면 종료 되지 않습니다. 만약 변경사항을 저장하지 않고 종료 하고 싶으시면 강제 종료 명령인 ":q!"를 입력 하세요. vi에서 '!'는 강제를 의미하는 키워드 입니다. 저장하고 종료하고 싶으시면 ":wq"를 입력하시면 됩니다.

 만약 다수의 파일을 열어서 편집 중 이셨다면, 편집한 모든 파일을 저장해 주어야 종료 됩니다. 저장하고 싶지 않으시면, 강제 종료 하시구요. 

줄 변환 (커서 이동)

 이 명령은 짧은 내용 이지만, 사용 빈도가 많은 명령 이라서, 굳이 분류하여 작성합니다. ":[N]" 의 형식으로 ':'이후 숫자를 입력하면, 커서가 [N]행으로 이동하게 됩니다. 이 명령은 이전 글에서 설명한 "[N]G" 로도 이용 할 수 있습니다. 아래 스냅숏은 동일 파일을 열어 준 뒤, 왼쪽 부터 각각 "G10", ":20", ":25"를 입력 했을 때 입니다. 

 Vi 의 하단 오른쪽(보라색 네모)에는 커서의 현재 위치가 행렬로 표기 됩니다. 해당 정보를 보시면 입력한 행으로 커서가 이동 했음을 확인 하실 수 있습니다.

검색과 치환

 Vi의 검색과 치환은 매우 빠르고, 편리 합니다. 먼저 검색 부터 보시죠. 검색은 '/''?'로 이용 합니다. 아래 스냅숏을 통해 설명 하겠습니다.

 세개의 터미널 모두 동일한 파일을 열어서 커서의 위치를 (9,5)에 배치했습니다. 그후 두번째 터미널에서 "/arr"을 입력 하고, 세번째 터미널에서 "?arr"을 입력 했습니다. 붉은색 네모를 보시면 입력한 커맨드를 확인 하실수 있습니다. 그 결과로 두번째와 세번째 터미널에서 검색 결과인 arr이 음영 처리 되었습니다. 그럼 '/'와 '?'의 차이는 검색 방향 입니다. 보라색 네모의 커서 위치를 확인해 보시죠. "/arr"을 입력한 두번째 터미널의 커서 위치는 (10, 12)이고, "?arr"을 입력한 세번째 터미널의 커서 위치는 (8, 48-55)입니다. '/'는 정방향 검색 이고, '?'는 역방향 검색 입니다. 위와 같은 검색 기술은 논리식과 여러가지 수식으로 그 장점을 극대화 할 수 있습니다.

 검색후에는 'n''N' 키를 이용해 다음 결과를 확인 하실수 있습니다. 'n'키는 정방향, 'N'키는 역발향 입니다.

 치환은 ":[범위]s/[찾으려는 문자열]/[변경하려는 문자열]/[옵션]" 형식의 명령을 이용합니다. 찾으려는 문자열과 변경하려는 문자열은 단어의 뜻 그대로 지정하는 문자열을 넣어 주면 됩니다. 아래 설명을 참조해 주세요.

범위 : 절대 지정, 상대 지정, 전체 지정이 가능합니다.

  • 5s : 5열에서 치환
  • 1,10s : 1열 부터 10열 중에서 치환
  • +3s : 커서의 위치 +3열에서 치환
  • -1, +3s : 커서의 위치에서 -1열 부터 +3열 까지 치환
  • %s : 문서 전체에서 치환
  • '<,'>s : 지정한 영역에서 치환 (특수 케이스 : 키보드로 입력하는게 아닙니다. 명령 모드에서 'v'키를 이용해 영역을 지정한 후, ":s"를 누르면 자동으로 지정 됩니다. ) 아래 스냅숏을 참조 하세요. 

 비쥬얼 모드에서 영역을 지원한 상태에서 ':'키를 누르면 자동으로 : 뒤에 "'<,'>" 가 붙게 됩니다. 그 후 원하는 커맨드("s/arr/Arr/g")를 입력 하면 영역 안에서만 해당 명령을 실행하는 것이죠. 위 스냅숏에서는 fibonacci 함수에서 변수 arr을 Arr로 변경 합니다. 이 방식은 치환이 아닌 다른 ex명령어에도 적용 됩니다.

 

변경하려는 문자열

  • & : 검색한 문자열 (검색하려는 문자열이 아닙니다. 검색한 문자열 입니다. 굳이 이렇게 언급한 이유는 Regular expression을 이용할 수 있기 때문입니다) 

옵션

  • g : 해당 줄에서 찾은 모든 검색 결과를 치환대상으로 지정.
  • c : 치환 여부를 사용자에게 물어봄. (검색 결과를 순회 하면서 변경할지 'y' , 유지 할지 'n', 전체 변경 할지 'a', 중단 할지 'q', ...)

 위의 내용을 종합해 보면 결국 실제 사용하게 될 치환은 "%s/[검색할 내용]/[치환할 내용]/g" 혹은 ":'<,'>s/[검색할 매용]/[치환할 내용]/g"를 이용하며, 가끔 c옵션을 이용하게 되곤 합니다. 추가로 치환을 이용해 즐거운 스마트한 vi 편집 생활을 하고 싶으시면 조금만 시간을 더 내셔서 아래 Regular expression에 대해서 읽어 보시기 바랍니다. 

Regular expression

 vi의 검색, 치환은 Regular expression을 지원 합니다. Regular expression의 용법을 굳이 기억 하실 필요는 없습니다. 간단한 것들 몇가지만 기억 하시고, 찾아가며 이용 하셔도 충분 합니다. 필요 하실때 위키피디아를 이용하시거나, 이런 치트 시트를 이용하시면 충분합니다. 링크된 시트는 참조만 하시기 바랍니다. vi는 시트의 조합을 이용하지만 문법은 조금 다를수 있습니다.

 기억 하셔야 할 것은 Regular expression을 이용하면 원하는 글을 쉽게 찾아서 변경할 수 있습니다. 그럼 검색시 중요 키워드 부터 설명 하겠습니다.

 먼저 중요 문자 입니다.

  • ^ : 줄의 시작
  • $ : 줄의 끝
  • \n : 개행문자
  • \r : 커리지 리턴
  • \t : 탭

 다음은 논리식 혹은 횟수에 이용되는 특수식 입니다.

  • [] : 괄호안의 문자들을 or연산으로 검색 합니다. 만약 "/[abc]" 라면 문서에서 a와 b와 c를 찾습니다.
  • \| : or 연산을 합니다. "/a\|b\|c" 는 "/[abc]"와 동일하게 동작합니다.
  • * : 앞의 글자가 0번이상 등장해야 합니다. 
  • \+ : 앞의 글자가 1번이상 등장해야 합니다.
  • \? : 앞의 글자가 0혹은 1번등장해야 합니다.
  • \{n\} : 앞의 글자가 n번 등장해야 합니다.
  • \{n,\} : 앞의 글자가 n번 이상 등장해야 합니다.
  • \{a,b\} : 앞의 글자가 a번 혹은 b번 등장 해야 합니다.

 다음으로 POSIX 수식과 의미하는 내용입니다.

  • [:alpha:] : 모든 문자
  • [:digit:] : 모든 숫자
  • [:alnum:] : 모든 문자와 숫자
  • [:blank:] : 공백 문자(탭 포함)
  • [:graph:] : 프린트가 가능한 모든 문자 (숫자나 기호 포함)
  • [:print:] : 프린트가 가능한 모든 문자와 공백

 위에 언급한 것 외에도 여러가지가 있으나, 일단 위의 것들만 인지 하고 계셔도 실사용에는 충분합니다.

 그럼 예제를 통해 치환에 대해 설명하겠습니다. 

for i in $(seq 1 1 99999)
do
	echo ${i}
done > test.result

 사용하는 예제는 위 스크립트를 통해 생성된 "test.result"파일 입니다. 이 파일에는 1~99999까지의 숫자가 들어 있습니다. 이 예제에서는 위 스크립트로 만들어진 숫자들 중 1로 시작해서 1로 끝나는 숫자(n)들을 a + 1 = n 으로 표현하고, 다른 숫자는 전부 삭제합니다.

 먼저 치환을 통해 1로 시작 해서 1로 끝나는 숫자를 제외하고 모두 삭제 합니다. 여러번의 시행 착오 끝에 만든 명령어는 아래와 같습니다. ( 처음엔 조건에 not 조건을 붙이고 싶었는데 문법을 모르겠어요..ㅜ,.ㅜ 혹시 아시는 분은 댓글 부탁드립니다. )

:%s/\(\(^[02-9][0-9]*$\)\|\(^[0-9]*[02-9]$\)\)//
  • %s : 치환을 합니다.
  • \(\(........\)\|\(........\)\) : 소괄호는 그룹화를 의미 합니다. 괄호안의 문자가 전부 검색되어야 합니다. "\(a\|b\)"의 구조는 a 혹은  b를 의미 합니다. 괄호와 "|"는 모두 특수한 기호 임으로 '\'를 이용해 Regular expression 수식임을 명시 합니다.
  • ^, $ : 각각 행의 시작과 끝을 의미 합니다.
  • ^[02-9][0-9]*$ : '^'행의 시작에서 "[02-9]" 0혹은 2에서 9사이의 문자가 나오고 "[0-9]*" 0에서 9 사이의 문자가 0회 이상 반복된다. '$'행이 끝나야 한다.
  • ^[0-9]*[02-9]$ : '^'행의 시작에서 "[0-9]*" 0에서 9사이의 문자가 0회 이상 반복 되고, "[02-9]$" 0혹은 2에서 9사이 문자가 나온뒤 행이 끝난다.
  • // : 치환할 내용과 옵션은 없다.

 명령이 끝나면 치환할 내용이 없음으로 당연히 검색된 내역은 삭제 됩니다. 아래 스냅숏은 명령의 실행 전과 후입니다. 

 목적대로 조건(1로 시작해서 1로 끝나는 문자열)에 부합하지 않는 모든 문자열은 삭제 되었습니다. 그런데...웁스 개행문자를 남겨 두었네요. 'u'키로 변경 내용을 복구 한뒤 명령어에 개행문자를 추가합니다. 아래 스냅숏을 참고 하세요.

 'u'키로 복구 했기 때문에 이전 검색 조건에 맞는 문자열들이 음영처리 되어 있습니다. 개행문자('\n')을 검색 조건에 추가해서 재실행 하면 우측 스냅숏과 같이 개행문자도 삭제된 결과를 얻을 수 있습니다. 이번엔 "n + 1 = n" 의 형태로 변경합니다. 명령어는 아래와 같습니다.

:%s/^[[:graph:]]\+$/& + 1 = &
  • ^[[:graph:]]\+$ : '^'행의 시작에서 "[[:graph:]]"프린트가 가능한 문자열 ([[:digit:]]이나 [[:alnum:]]으로 대체가능) '\+' 1 번이상 반복합니다. '&' 행이 끝납니다.
  • & + 1 = & : '&'검색한 문자열 뒤에 " + 1 = "을 붙이고 '&'검색한 물자열을 붙입니다.

 POSIX 수식을 이용할 때 주의점은 "[:alpha:]"와 같이 표현하면, vi는 ':', 'a','l','p','h' 를 검색합니다. "[....]" 수식이 안에 있는 문자들을 or연산으로 묶어서 검색하는 명령이기 때문이죠. 따라서 "[[:alpha:]]"와 같이 표현해 주셔야 합니다. 이때 외곽의 "[...]"은 or 연산으로 인식되기 때문에 "[[:alpha:][:digit:]]"와 같이 표현하시면, 모든 문자나 모든 숫자들을 검색하는 조건이 되어 "[[:alnum:]]"와 동일한 결과를 얻으실수 있습니다.

 위 명령어를 입력한 뒤의 결과는 아래 스냅숏과 같습니다. 

이제 마지막으로 첫번째 열의 숫자들에서 1을 빼주면 됩니다. 명령어는 아래와 같습니다.

:%s/1 /0 /

 단순하죠 그냥 "1 "을 "0 "로 치환합니다. 결과는 아래 스냅숏과 같습니다.

 결과에서 보시면 "+ 1 ="의 "1 " 부분이 음영 처리되어 있죠. 방금 실행한 검색과 조건이 같다는 의미 입니다. 만약 위 명령을 수행 할때 'g'옵션을 넣었다면 해당 내역까지 변경 됩니다. 

 

 vi는 이렇게 강력한 치환 기능을 제공합니다. 이 기능은 매우 빠르게 동작하고, 만약 실수를 해도 편집 문서를 저장 하지 않고 종료하거나, 'u'키를 이용해서 쉽게 되돌릴수 있습니다. 두려워하지 말고 한번 해보세요. vi의 매력에 느낄수 있을 것입니다.  

반응형