find/bash : 원하는 문구가 들어있는 파일/디렉토리만 제외하고 배열로.

워낙에 find 는, 찾고자 하는 문자열이 들어있는 파일을 찾으라고 있는거지만, 그 반대도 충분히 가능하다.

즉, 그 문자열을 포함하지 않는 파일들만 찾고 싶다면?

발단 : 일단은 찾아봐

방법은 여러가지겠지만, 일단 쉽게 파이프라인을 생각해볼 수 있다.

find <경로> -type 'd' | egrep -v "(문구1|문구2|문구3)"

여기서 find 는 역할이 미미한데, ‘검색’ 본연의 역할은 상당히 적고, 그저 디렉토리만 찾아주는 소일거리로 시간만 보낸다.

grep(egrep) 에서 -v 는, invert-match 의 뜻으로, 저 문구들을 포함하면 제외하라, 즉, 저 문자열이 없는 결과만을 취하라는 뜻이 된다.

두번째로, find 에 전권을 위임하는 방법도 있다.

find <경로> -type 'd' -regextype egrep ! -iregex "(.*13$|.*12$)"

regex 를 본격 도입하여, 같은 결과를 냈다. 13, 12 로 끝나는 문자열을 제외한 모든 디렉토리명을 얻을 수 있게 됐다.

  • -regextype egrep : find 는 여러가지 regex 문법을 지원하는데, 그 중 egrep 를 사용하라는 뜻. (findutils-default’, ‘ed’, ‘emacs’, ‘gnu-awk’, ‘grep’, ‘posix-awk’, ‘awk’, ‘posix-basic’, ‘posix-egrep’, ‘egrep’, ‘posix-extended’, ‘posix-minimal-basic’, ‘sed’.)
  • -regex pattern : pattern 에 매칭하고자 하는 문자열을 넣는다.

이렇게 해야, 즉, find 로만 결과를 얻어야 다음 진도로 나갈 수 있다.

전개 : 찾은 걸 배열화 하고 싶은데..

파일을 애초에 단순히 찾기만 하려했던게 아니고, 그 결과로 스크립트를 만들 생각이었다. 따라서, find 로 찾은 문자열을 일단은 배열에 넣어야 하는데..

여기서 Shell Script 의 고질병에 부딪히게 된다.
꼭 Shell script 만의 문제는 아닐 수 있지만, 바로, 파일명에 특수문자, 특히나 ‘공백’이 포함되었을 경우 처리 문제를 고려해야만 한다.

readarray 를 사용해서 결과를 배열로 바꾼다.

readarray findings < <(find <경로> -mindepth 3 -type 'd' -regextype egrep ! -iregex "(.*13$|.*12$)"

위에 쓰인 문법(Process Substitution)이 낯선가? (아마도..)

여기서 find 는 한 단계 더 나아가서, -mindepth 를 사용했다. 이건 또 뭘까?
차이를 눈으로 보면 이렇다.

➜  ~ find /카메라\ 녹화 -type 'd' -regextype egrep ! -iregex "(.*13$|.*12$)" 
/카메라 녹화
/카메라 녹화/2020
/카메라 녹화/2020/10
/카메라 녹화/2020/10/10
/카메라 녹화/2020/10/11
/카메라 녹화/2020/10/14
/카메라 녹화/2020/09
/카메라 녹화/2020/09/30
/카메라 녹화/2021
/카메라 녹화/2021/01
/카메라 녹화/2021/01/01

➜  ~ find /카메라\ 녹화  -mindepth 3 -type 'd' -regextype egrep ! -iregex "(.*13$|.*12$)"
/카메라 녹화/2020/10/10
/카메라 녹화/2020/10/11
/카메라 녹화/2020/10/14
/카메라 녹화/2020/09/30
/카메라 녹화/2021/01/01

내가 원하는 결과는 아래처럼, /카메라 녹화/2020/10/10 형식인데, -mindepth 없이 그냥 검색하면 /카메라 녹화, /카메라 녹화/2020 등도 검색이 돼버린다. 이걸 방지하기 위해서 조건에 -mindepth 3 을 추가했다.

그리고, 위 작업을 마치면, $findings 에 배열로 디렉토리명이 들어가게 된다.
readarray(또는 mapfile) 는 Bash 내부 명령어로, Standard Input/File 으로부터 입력받은 문자열을 개행문자(\n)로 구분하여 배열에 저장해주는 역할을 한다. (Zsh 에는 없다!!)
확인해보진 않았지만, readarray 는 Stdin 파일에, mapfile 은 StdIn. 에 사용되는 듯? 적어도 설명에는 그렇게 나와있다.

  • readarray: readarray [-d delim] [-n count] [-O origin] [-s count] [-t] [-u fd] [-C callback] [-c quantum] [array]
    Read lines from a file into an array variable.
  • mapfile: mapfile [-d delim] [-n count] [-O origin] [-s count] [-t] [-u fd] [-C callback] [-c quantum] [array]
    Read lines from the standard input into an indexed array variable.

이렇게 하면 이론 상 별 문제가 없어야 되는데.. 개행문자로 구분을 하다보니, 가끔 잡아내가 어려운 오작동이 발생할 때가 있다. 이럴 때를 대비해서, 구분자를 다른 것으로 쓸 필요가 있다.

위기 : 개행대신 Null 로.

적어도 지금 내 상황에선 오류가 발생하진 않았다.
다만, 추후에 그럴 가능성은 있다. 따라서, 미연에 방지하고자, find 결과를 -print0 으로 구분하고자 한다.

➜ ~ find /카메라\ 녹화 -type 'd' -print0 -regextype egrep ! -iregex "(.*13$|.*12$)"
/카메라 녹화/카메라 녹화/2020/카메라 녹화/2020/10/카메라 녹화/2020/10/10/카메라 녹화/2020/10/11/카메라 녹화/2020/10/12/카메라 녹화/2020/10/13/카메라 녹화/2020/10/14/카메라 녹화/2020/09/카메라 녹화/2020/09/30/카메라 녹화/2021/카메라 녹화/2021/01/카메라 녹화/2021/01/01%  

결과는 이런 식으로, 인간의 눈(?)에는 줄구분이 안된 상태로 어지러이 보여진다.
이게 과연 더 나은 결과라고??

이걸 배열로 넣으려면 역시나 readarray 를 사용하면 되는데, 대신 조건이 하나 더 붙는다.

readarray -d '' findings < <(find <경로> -mindepth 3 -type 'd' -regextype egrep ! -iregex "(.*13$|.*12$)" -print0)

find 에서 결과값을 -print0 으로 나눠준 효과를 보기 위해, -d, 즉 Delimiter(구분자)를 ”(아무 문자도 없으므로, Null 문자를 뜻한다.)로 해줬다. 이제 정말로 잘 나눠졌는지 확인해본다.

절정과 결말

readarray -d '' findings < <(find /카메라\ 녹화/ -mindepth 3 -type 'd' -regextype egrep ! -iregex "(.*13$|.*12$)" -print0)

for i in "${findings[@]}"; do
        printf "i = $i\n"
done

이걸로 끝. find 결과를 배열로 무사 이전.

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