대규모 코드 디렉터리에서 문자열 목록을 효율적으로 검색하는 방법

대규모 코드 디렉터리에서 문자열 목록을 효율적으로 검색하는 방법

문자열 목록이 있고 각 문자열에 대해 그것이 큰 소스 코드 디렉토리에 나타나는지 확인하고 싶습니다.

내가 원하는 것을 제공하는 GNU grep 솔루션을 찾았습니다.

for key in $(cat /tmp/listOfKeys.txt); do
    if [ "$(grep -rio -m 1 "$key" . | wc -l)" = "0" ]; then
        echo "$key has no occurence"; 
    fi
done

그러나 일치하는 항목을 조기에 찾더라도 항상 디렉터리 아래의 모든 파일을 grep하기 때문에 비효율적입니다. 찾아야 할 키도 많고, 검색해야 할 파일도 너무 많기 때문에 그대로 작동하지 않습니다.

"표준" 유닉스 도구를 사용하여 이를 효율적으로 수행하는 방법을 알고 있습니까?

답변1

최소한 다음과 같이 단순화할 수 있습니다.

set -f # needed if you're using the split+glob operator and don't want the
       # glob part

for key in $(cat /tmp/listOfKeys.txt); do
   grep -riFqe "$key" . ||
    printf '%s\n' "$key has no occurrence"
done

이렇게 하면 첫 번째 발생 후 검색이 중지되고 key키를 정규식( 또는 에 가능한 옵션 grep)으로 간주하지 않습니다.

파일을 여러 번 읽는 것을 방지하고 키 목록이 한 줄에 하나의 키라고 가정하려면(위 코드에서처럼 구분된 공백/탭/줄바꿈 대신) GNU 도구를 사용할 수 있습니다.

find . -type f -size +0 -printf '%p\0' | awk '
  ARGIND == 2 {ARGV[ARGC++] = $0; next}
  ARGIND == 4 {a[tolower($0)]; n++; next}
  {
    l = tolower($0)
    for (i in a) if (index(l, i)) {
      delete a[i]
      if (!--n) exit
    }
  }
  END {
    for (i in a) print i, "has no occurrence"
  }' RS='\0' - RS='\n' /tmp/listOfKeys.txt

keya가 보이면 검색을 중지하고, 모든 키를 찾으면 중지하고, 파일을 한 번만 읽도록 최적화되어 있습니다 .

키가 에 있다고 가정합니다 listOfKeys.txt. 키는 소문자로 출력됩니다.

위의 GNUism은 NUL로 구분된 레코드를 처리하는 기능 -printf '%p\0'과 같습니다 . 처음 두 가지 문제는 다음을 통해 해결될 수 있습니다.ARGINDawk

find . -type f -size +0 -exec printf '%s\0' {} + | awk '
  step == 1 {ARGV[ARGC++] = $0; next}
  step == 2 {a[tolower($0)]; n++; next}
  {
    l = tolower($0)
    for (i in a) if (index(l, i)) {
      delete a[i]
      if (!--n) exit
    }
  }
  END {
    for (i in a) print i, "has no occurrence"
  }' step=1 RS='\0' - step=2 RS='\n' /tmp/listOfKeys.txt step=3

세 번째 문제도 비슷한 트릭을 사용하여 해결할 수 있습니다.이것, 그러나 아마도 노력할 가치가 없을 것입니다. 바라보다맨발 IO 솔루션문제를 완전히 우회할 수 있는 방법을 찾고 있습니다.

답변2

GNU grep(및 내가 알고 있는 대부분의 변형)은 -f정확히 필요한 작업을 수행하는 옵션을 제공합니다. 이 fgrep변형은 입력 줄을 정규식 대신 일반 문자열로 처리합니다.

fgrep -rio -f /tmp/listOfKeys.txt .

일치하는 항목이 하나 이상 있는지 테스트하려면 -q옵션을 추가하세요. Stéphane의 의견을 바탕으로 어떤 문자열이 필요한지 알아야 하는 경우아니요찾은 후에는 -h옵션을 추가한 다음 다음 일반적인 awk 관용구를 통해 파이프합니다.

fgrep -h -rio -f /tmp/listOfKeys.txt . |
awk '{$0=tolower($0)}; !seen[$0]++' |
fgrep -v -i -x -f - /tmp/listOfKeys.txt

두 번째는 fgrep이제 첫 번째(대소문자를 구분하지 않는 고유한 출력)의 출력을 가져와 fgrep의미를 바꾸고 키 파일에 일치하지 않는 문자열을 표시합니다.

답변3

Stéphane Chazelas의 gawk 메소드에 대한 이식 가능한 POSIX 호환 번역:

find . -type f -exec cat {} + |
awk '
    FNR==NR {keys[tolower($0)]; n++; next}
    {
        s = tolower($0)
        for (k in keys) 
            if (index(s, k)) {
                delete keys[k]
                if (!--n)
                    exit
            }
    }
    END {
        for (k in keys) print k, "has no occurrence"
    }
' /tmp/listOfKeys.txt -

이름이 항상 내용보다 길다는 점에서 소스 파일이 특이하지 않은 한, 파이프를 통해 전송되는 데이터가 적기 때문에 Stéphane의 솔루션이 더 효율적입니다(여기에는 커널을 통해 두 프로세스의 버퍼 간 복사가 포함됩니다).

관련 정보