「새로 입사한 개발자가 프로젝트에 기여하는 방법 한 가지」를 읽고 한 공부
들어가며
기계인간님이 마켓컬리 기술 블로그에 작성하신 새로 입사한 개발자가 프로젝트에 기여하는 방법 한 가지를 읽었습니다. 부끄럽게도 중간중간에 나오는 셸 명령어 중에 모르는 것이 많더군요. 지금 일하는 곳에선 셸을 쓸 일보다는 주로 코드를 뚫어지게 들어다보며 자신의 멍청함을 한탄하는 시간이 많았거든요. 하지만 저도 셸을 아예 사용하지 않는 것은 아니고, 알아두면 언젠가 더 멋진 것을 할 수 있지 않을까요? 이런 이유로 부족하게나마 기계인간님의 글에서 다룬 명령어에 대하여 조사해 보았습니다.
이하 소제목과 명령어는 기계인간님의 글에서 그대로 가져왔습니다.
코딩 스타일 가이드를 읽다가 작성한 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/){/) {/'
위에서 모두 나왔던 용례로 파일 내 ){를 ) {” 바꾸는 명령입니다.
마치며
앞으로 언젠간 분명 활용할 일이 있을 것 같은 예제를 가지고 공부하니 오랜만에 신이 났습니다. 막연하게 셸로 많은 것을 할 수 있지만 사용법은 복잡하겠지 하고 생각해 왔었는데 알고 나니 조금 고민하면 여기저기 써 먹을 수 있겠다는 자신감이 붙네요. (물론 쓸 일이 생길 때 쯤엔 이 글을 다시 찾아봐야 하겠지만요…) 항상 좋은 글을 써 주셔서 기계인간님께는 감사할 따름입니다.