파일 내에서 특정 문자열을 찾아 바꾸고 싶은데.. 두번째.

이 글은, 바로 전 글, ‘첫번째’에 비해서 훨씬 복잡하고, 따라서 헷갈린다.
그러나, findfile, 나아가서 awk 에 대해 공부해볼 수 있는 기회가 되긴 했다.

다시 말해서, file 명령을 사용할 일이 없다면, 굳이 이 방법을 고려할 필요는 없다.

특정 경로 밑에 있는 어떤 파일을 찾아, 어떤 작업을 하고 싶다면?
이게 이번 글의 주제가 되겠다.
‘어떤’을 좀더 특정 지어보자면, 지난 번 글 내용과 같다.

특정 경로 밑에 있는 텍스트 파일을 찾아, 특정 문구를 다른 문구로 바꾸고 싶다면?

지난 번에는 이 작업을 grep 와 sed 로만 했으나, 여기선 find, file, awk 등등을 사용한다. 결과를 위해서라기보다는, 그저 학습을 위한 꽃삽질이라고 생각하는게 속이 편하겠다.


먼저 생각해야 할 것은 두가지. ‘특정 경로 밑.’, ‘텍스트 파일’.
grep 는 위 두 개를 한꺼번에 해결해주니 일석이조가 되지만, 이제부터 사용할 find 와 file 로는 각각 알아서 작업을 해줘야 한다.

먼저, 특정 디렉토리 아래에 있는 ‘파일’을 찾고, 그 파일의 종류를 조사해보자.

#find /path -type f -exec file {} +
find /usr/bin -type f -exec file {} +
/usr/bin/xsetwacom:                           ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=e0a811232d81c40cf7f51fa37f67f2abd37ff045, stripped
/usr/bin/xman:                                ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=7999dfb32292aef3bc016d1e6690829c45f0c933, stripped
/usr/bin/pnmtofiasco:                         ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=cc24deb40639b004110a2f9d081dfa8e135dd7bd, stripped
/usr/bin/python3.6:                           ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=9dae0eec9b3f9cb82612d20dc0c3088feab9e356, stripped
/usr/bin/aplay:                               ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=a74a46ec25bec8a424910ae5d2c701825147956d, stripped
/usr/bin/lpr:                                 ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=2e3f865b8586fd2542e4d031215ac326a087ebd4, stripped
/usr/bin/pnmtoxwd:                            ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=c3a9c0ffcad33d47fb03d53d9905f312c54cf4c0, stripped
/usr/bin/pbmpscale:                           ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=e5ac75955a8ca4d799622991640ef63457e3b553, stripped
/usr/bin/x86_64-pc-linux-gnu-pkg-config:      ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=09c70d0d6020d440400ea5c0d8dd63a24379e6cf, stripped
/usr/bin/st4topgm:                            ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=50807cc87de3e1691189c7a921fba41f8f4d90ac, stripped
/usr/bin/perl5.26.1:                          ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=5c3a1fcd909585500e92276fd2093f7707783957, stripped
/usr/bin/rarian-example:                      ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=a78f29c64ed86f3f430b5b796a5e511afc931858, stripped
/usr/bin/lscpu:                               ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=aae9f67670237238920935c22b6c54e4f201f06a, stripped
/usr/bin/fcitx-autostart:                     POSIX shell script, ASCII text executable
/usr/bin/dpkg-source:                         Perl script text executable
/usr/bin/apt-cdrom:                           ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=414b948466d202f8f32fd0f6c79975b9d4038342, stripped
/usr/bin/ppdmerge:                            ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=28e1342caa05d4bde51ba2cce25c964bba926498, stripped
/usr/bin/dbus-launch:                         ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=20d7d82bbccf1c79a558d5f21062d97a43535ad4, stripped
/usr/bin/dh_installmanpages:                  Perl script text executable
/usr/bin/ispell-wrapper:                      Perl script text executable
/usr/bin/flock:                               ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=cbf6f1e285cf54c8bea8732820560adf7db3b2cf, stripped
.....

위 결과 처럼 파일을 분석해서, 단순히 텍스트 파일인지 바이너리 파일인지만을 알려주는데 그치지 않고, 보다 정확한 정보를 알려준다.
-exec 뒤에 {} + 로 끝을 맺었는데, {} \; 로 할 수도 있다. 단, 이 경우처럼 결과가 많이 나오는 경우, + 로 끝맺는 편이 처리 시간이 더 짧게 소요된다.

file 에서 출력하는 일반 텍스트 파일에 대한 정보는 대충 아래와 같다.

ASCII text, with very long lines, with no line terminators
ASCII text, with no line terminators
UTF-8 Unicode text, with no line terminators
...

이거 말고도 더 많은데, 어쨌든 ‘text’ 가 들어간다고 보면 큰 무리는 없을 듯 하다.
또, file 은 정보를 출력할 때, 파일명과 파일형식 사이를 콜론(:)으로 구분한다. 따라서, 콜론 뒤에 나오는 정보에 ‘text’ 라는 문구가 있는지를 찾아냄으로써, 그 파일이 텍스트파일인지 아닌지 여부를 알아낼 수 있겠다.

그런데, 어떻게..??? 어떻게 찾아??

리눅스 생활 10년에, 이걸 하면서 awk 를 처음 써 보게 됐다. (아마도?)
awk 는 단순한 편집기를 넘어선 ‘언어‘라고 정의돼 있다. 이걸 뒤집어보면, 그만큼 사용하기에 복잡하고 어렵단 뜻인데..
그냥 날림으로 대충만 정리해본다.

# 첫시도
$ find ~/.atom -type f -exec file {} + | awk '/text/ {print}'
/home/userx/.atom/compile-cache/style-manager/e8d82c46268ba71e27df9be0bff7aab156e83d9b:                                                                         ASCII text, with very long lines, with no line terminators
/home/userx/.atom/compile-cache/style-manager/fb2d625a9a2761755be85cb75750a28d10e66e9c:                                                                         ASCII text, with very long lines, with no line terminators
/home/userx/.atom/compile-cache/style-manager/5bd47627f72fbaa4a442e8f7b1e68e12df571753:                                                                         ASCII text, with very long lines, with no line terminators
/home/userx/.atom/compile-cache/style-manager/514ed761d685396d47bde96b98195c1546feddcb:                                                                         ASCII text, with very long lines, with no line terminators
/home/userx/.atom/compile-cache/style-manager/d2a4bd0c8c00c4ba1ddc4074d0ed6465280e91af:                                                                         ASCII text, with very long lines, with no line terminators
...

먼저, file 결과를 stdin 으로 받아, awk 에서 ‘text’ 가 들어가 있는지 확인한 후, 들어있는 것만 출력을 해준다.
하지만, 원하는 결과는 ‘파일명/경로’ 뿐이다. 파일 형식 설명 부분은 필요하질 않다.

출력 내용을 보면, 경로명과 내용 사이가 콜론으로 구분이 되어 있으므로, 이것을 이용해서 두 부분으로 나눠본다.

# 두번째 시도
$ find ~/.atom -type f -exec file {} + | awk -F: '/text/ {print $1}'
/home/userx/.atom/nohup.out
/home/userx/.atom/config.cson
/home/userx/.atom/compile-cache/less/df1b7fbc57dbeed23a8f1f8de95b4dfebcc06066/imports.json
/home/userx/.atom/compile-cache/less/df1b7fbc57dbeed23a8f1f8de95b4dfebcc06066/content/740df76da91c87028df21e60f02761072e59a76c/about.json
/home/userx/.atom/compile-cache/less/df1b7fbc57dbeed23a8f1f8de95b4dfebcc06066/content/07fd21a5554eb7c78cbd96652a2d507b3f3f9e4e/accordion.json
...

이 정도면 어느 정도는 성공했다고 볼 수 있다.
단, 여기도 문제는 있다. 위 내용에선 알 수가 없지만, 경로명이나 파일명에 text 가 들어간 것도 참값이 된다.
좀 더 제대로 검사하려면, 콜론으로 나뉜 뒷 부분, 즉 $2 부분에서 ‘text’ 가 들어있는지를 찾아야 한다.

# 세번째 시도
$ find ~/.atom -type f -exec file {} + | awk -F: 'match($2,/text/) {print $1}'

awk 는 기능이 워낙 많기에, match 함수를 쓴다는 정도만 정리해놓기로 한다.

여기까지 했는데도, 지난 글에서 쓴 내용과 비교해보면, 아직 반도 못온 셈이다.

grep 'path' -rlIe 'pattern' | xargs sed 's/pattern/pattern2/g'

이 명령과 비교해보면, 이제 겨우 grep -rlI 까지만 한 셈이다. ‘pattern’ 은 아직 찾지도 못했다.

이 이후는 grep 로 일단 pattern 을 찾고, 다시 sed 를 하는 방법이 있을테고, 아니면 그냥 바로 sed 를 해도 되겠지만, 문제는 효율이 아닐런지.
즉, grep 가 더 빠른가, sed 가 더 빠른가. 주어진 모든 파일을 sed 가 훑어보게 하는 게 나은지, 아니면 grep 가 그 작업을 먼저하게 한 뒤, 해당 패턴이 있는 파일만 sed 로 치환하게 하는게 빠른지.

첫번째 방법에서는 sed 가 Text 파일만을 찾아내는 기능이 없어서 먼저 grep 에게 그 역할을 맡겼었는데, find 와 file 을 쓴 뒤로는 어떻게 해야할지??

이 글에선 거기까지 생각은 안해보련다.
아무튼, 이 뒤로는 grep + sed 를 쓰든, sed 단독을 쓰든, 원하는 대로 해주면 되겠다. 속도를 측정해보고 어떤게 더 빠른지 결정을 해도 될테고..

안녕하세요. 글 남겨주셔서 고맙습니다.