「새로 입사한 개발자가 프로젝트에 기여하는 방법 한 가지」를 읽고 한 공부
들어가며
기계인간님이 마켓컬리 기술 블로그에 작성하신 새로 입사한 개발자가 프로젝트에 기여하는 방법 한 가지를 읽었습니다. 부끄럽게도 중간중간에 나오는 셸 명령어 중에 모르는 것이 많더군요. 지금 일하는 곳에선 셸을 쓸 일보다는 주로 코드를 뚫어지게 들어다보며 자신의 멍청함을 한탄하는 시간이 많았거든요. 하지만 저도 셸을 아예 사용하지 않는 것은 아니고, 알아두면 언젠가 더 멋진 것을 할 수 있지 않을까요? 이런 이유로 부족하게나마 기계인간님의 글에서 다룬 명령어에 대하여 조사해 보았습니다.
이하 소제목과 명령어는 기계인간님의 글에서 그대로 가져왔습니다.
코딩 스타일 가이드를 읽다가 작성한 Pull Request
find . -name '*.java' \
| xargs egrep '^\s*if[^\{]*\s*$' --no-filename
find
: 현재 디렉토리와 그 하위에서*.java
를 검색합니다.xargs
: 파이프로 연결하여find
의 출력이egrep
의 마지막 인자로 들어가도록 합니다.egrep
:grep -extended-regexp
또는grep -E
와 동일하며, 확장 정규식을 지원합니다. 인자로 주어진 정규식과 매칭되는 부분을 파일에서 찾아 출력할 것입니다.'^\s*if[^\{]*\s*$'
^
: 문자열의 시작\s*
: 공백 0자 이상if
:if
[^\{]*
:{
가 아닌 문자 0자 이상\s*
: 공백 0자 이상. 바로 위에서 매칭되므로 없어도 될 것 같습니다.$
: 문자열의 끝
--no-filename
:grep
은 기본적으로 한 개 이상의 파일이 주어지면--with-filename
가 적용된 것과 같이 파일 이름을 출력하는데요, 이를 보이지 않게 한 것입니다.
if, for 문에 붙은 괄호에 공백을 주자
KEYWORDS="(if|for|while|try)"
ag "$KEYWORDS\(" -l \
| xargs sed -i '' -E "s/$KEYWORDS\(/\1 (/"
"(if|for|while|try)"
:if
,for
,while
,try
중 한 단어와 매칭되도록 합니다. 괄호로 묶었으니 그룹으로 지정됩니다.- ag: the_silver_searcher, 코드 검색을 위한 툴로 빠를 뿐 아니라 여러 편리한 옵션이 있습니다.
"$KEYWORDS\("
:KEYWORDS
와(
가 붙어 있는 경우를 매칭합니다.-l
: 검색된 내용은 제외하고 파일명만 출력하도록 합니다.
sed
: Stream editor. 파일을 한 줄 한 줄 읽어서 대치 등의 명령을 수행할 수 있습니다.-i ''
: 수정 결과를 표준 출력으로 내보내지 않고 파일에 직접 씁니다. Suffix가 빈 칸으로 주어졌으므로 백업은 생성하지 않습니다.-E
: 확장 정규식을 사용할 수 있도록 합니다."s/$KEYWORDS\(/\1 (/"
:s/패턴1/패턴2/
와 같이 사용하면 패턴1을 2로 대치합니다./
를 기준으로 아래와 같이 나눠볼 수 있겠네요.s
: Substitute 명령$KEYWORDS\(
: 패턴1입니다.\1 (
: 패턴2입니다.\1
은 그룹1, 즉$KEYWORDS
를 말하며 이와(
사이에 공백을 한 칸 넣어주었습니다.
대문자로 시작하는 lambda 변수 이름을 소문자로 시작하게 바꾸자
ag '\(([A-Z]\w*)\s?\-\>\s*\1' -l \
| xargs gsed -i.orig -e '/\.filter/ s,(\([A-Z]\),(\L\1,; s,-> \([A-Z]\),-> \L\1,'
ag
'\(([A-Z]\w*)\s?\-\>\s*\1'
:(Meow -> Meow
형식의 문자열을 매칭합니다\(
:(
([A-Z]\w*)
: 대문자로 시작하는 문자열을 매칭합니다. 그룹1입니다.\s?
: 공백 0~1자\-\>
:->
\s*
: 공백 0자 이상\1
: 그룹1
gsed
: Mac OS의sed
에는 제약사항이 있어 GNU의sed
를 사용할 수 있게 만들어주는 것 같습니다.-i.orig
: 파일을 직접 수정하되 suffix가 주어졌으므로 원본 파일에.orig
을 붙여 백업합니다.-e '/\.filter/ s,(\([A-Z]\),(\L\1,; s,-> \([A-Z]\),-> \L\1,'
/\.filter/
:.filter
가 나오는 줄만 선택합니다.s,(\([A-Z]\),(\L\1,
:s
명령 다음에 나오는 single-byte 문자는 구분자로 취급된다고 합니다. 여기서는,
를 구분자로 사용했습니다. 즉s/(\([A-Z]\)/(\L\1/
와 동일한 거죠. 참고(\([A-Z]\)
:(
다음의 대문자로 시작하는 변수 한 글자를 그룹1로 지정하였습니다.(\L\1
:\L
은\E
가 나올 때까지 대문자를 소문자로 변환합니다. 그룹1에서 대문자를 소문자로 변환할 것입니다.
s,-> \([A-Z]\),-> \L\1,
-> \([A-Z]\)
:->
다음의 대문자로 시작하는 변수 한 글자를 그룹1로 지정하였습니다.-> \L\1
: 그룹1에서 대문자를 소문자로 변환합니다.
화살표 연산자 좌우에 스페이스를 1개 추가하자
find . -name '*.java' \
| xargs ag '\-\>(?=\S)|(?<=\S)\-\>' -l \
| xargs sed -i.orig -E "s,([^ ])->,\1 ->,; s,->([^ ]),-> \1,"
ag
'\-\>(?=\S)|(?<=\S)\-\>'
:|
(or)로 구분되어 있습니다.\-\>(?=\S)
:?=
는 전방탐색을 말합니다.\S
(공백 제외 문자)까지 매칭을 시키지만\S
는 소비되지 않습니다. 예를 들어->ABC
라면->A
까지 매칭 조건에 포함되지만 실제 매칭 문자열은->
만입니다.(?<=\S)\-\>
:?<=
는 후방탐색을 말합니다. 예를 들어ABC->
라면C->
까지 매칭 조건에 포함되지만 실제 매칭 문자열은->
만입니다.
sed
"s,([^ ])->,\1 ->,; s,->([^ ]),-> \1,"
: 두 개의 명령이;
로 구분되어 있습니다.s,([^ ])->,\1 ->,
: 공백을 제외한 문자 바로 다음에->
가 나오면 사이에 공백을 추가합니다.s,->([^ ]),-> \1,
:->
바로 다음에 공백을 제외한 문자가 나오면 사이에 공백을 추가합니다.
프로젝트 전체에서 탭 문자를 sed로 2 spaces로 교체하자
find . -name '*.java' \
| xargs ag '\t' -l \
| xargs sed -E -i '' "s/[[:cntrl:]]/ /g"
find . -name '*.java'
: java 파일만 찾습니다.ag '\t' -l
: Tab을 포함하고 있는 파일의 이름만 출력합니다.sed
-i ''
: 수정 결과를 표준 출력으로 내보내지 않고 파일에 직접 씁니다. Suffix가 빈 칸으로 주어졌으므로 백업은 생성하지 않습니다."s/[[:cntrl:]]/ /g"
s
: 대치[[:cntrl:]]
: 제어 문자를 말합니다. Tab이 포함되겠습니다. 참고- ` `: 2 spaces
g
: 이 플래그를 주지 않으면 각 라인에서 최초로 매칭되는 부분만 대치됩니다.
여는 중괄호 앞에 스페이스를 1개 추가하자
ag '\)\{' -l | xargs sed -i.orig 's/){/) {/'
위에서 모두 나왔던 용례로 파일 내 ){
를 ) {
” 바꾸는 명령입니다.
마치며
앞으로 언젠간 분명 활용할 일이 있을 것 같은 예제를 가지고 공부하니 오랜만에 신이 났습니다. 막연하게 셸로 많은 것을 할 수 있지만 사용법은 복잡하겠지 하고 생각해 왔었는데 알고 나니 조금 고민하면 여기저기 써 먹을 수 있겠다는 자신감이 붙네요. (물론 쓸 일이 생길 때 쯤엔 이 글을 다시 찾아봐야 하겠지만요…) 항상 좋은 글을 써 주셔서 기계인간님께는 감사할 따름입니다.