Linux는 주어진 파일 세트에서만 문자열을 검색합니다.

Linux는 주어진 파일 세트에서만 문자열을 검색합니다.

한 디렉터리에 여러 개의 파일이 있습니다. 주어진 문자열로 끝나는 모든 문자열을 검색하고 찾으려고 합니다. 디렉토리에 있는 모든 파일이 아닌 특정 파일 이름 세트만 검색하고 싶습니다. 마지막으로 출력은 각 파일 이름과 해당 파일에서 발견된 세미콜론으로 구분된 문자열의 발생 횟수여야 합니다.

단순화된 테스트 사례는 다음과 같습니다. 디렉터리에 5개의 파일이 있습니다.

file.a.txt
file.b.txt
file.c.txt
file.d.txt
file.e.txt

searchFiles.txt위 목록의 처음 3개 파일 이름을 포함하는 파일도 있습니다 . 그래서 에 나열된 파일 이름에서만 문자열을 검색하고 싶습니다 searchFiles.txt.

나는 시도했다:

for i in $(cat searchFiles.txt); do grep -o '[^ ]*_XYZ' /dev/null $i ; done | awk -F: '{a[$1]=a[$1]";"$2;} END{for (x in a) print x ":" substr(a[x],2);}'

그러나 출력은 다음과 같이 말합니다.

: No such file or directory
: No such file or directory
file.c.txt:FOUND1_XYZ;FOUND2_XYZ

따라서 어떻게든 searchFiles.txt에 지정된 마지막 파일 이름만 검색할 수 있지만 다른 초기 파일은 찾을 수 없으므로 "해당 파일 또는 디렉터리가 없습니다."라는 오류가 발생합니다.

내 예상 결과는 다음과 같습니다

file.a.txt:FOUNDSTR_XYZ
file.b.txt:FOUNDSTR1_XYZ;FOUNDSTR2_XYZ;FOUNDSTR3_XYZ
file.c.txt:FOUND1_XYZ;FOUND2_XYZ

또한 "find" 명령의 "-name" 플래그가 도움이 되는지 알아보려고 했지만 여기에서는 searchFiles.txt의 파일 목록을 정확하게 제공하는 방법을 잘 알 수 없습니다. 다음 시도가 실패했습니다.

find . -type f -name `cat searchFiles.txt` -exec grep -o '[^ ]*_XYZ' /dev/null {} \;

반품:

  • 디렉터리에는 최대 수천 개의 파일이 있을 수 있으며, searchFiles.txt에서 검색되는 파일 이름은 수백 개가 될 수 있습니다.

  • 파일 이름은 무엇이든 될 수 있으며 어떠한 패턴도 따르지 않습니다.

  • searchFiles.txt에 제공되는 파일 이름은 파일 이름 "file"의 초기 정적 부분을 의미하는 file.a.txt 대신 a.txt와 같은 부분 이름일 수 있습니다. searchFiles.txt에 존재할 수도 있고 존재하지 않을 수도 있습니다.

  • 쉘 스크립트보다는 한 줄 명령을 찾는 것이 더 좋습니다.

이에 대한 도움이 필요하신가요?

답변1

awkGNU를 사용하여 다음과 같은 모든 작업을 수행할 수 있어야 합니다 .

find . -type f -print0 |
  gawk '
    step == 1 {files[$0]; next} # record file names in "files" array
    step == 2 {
      # determine which files to look into (added to ARGV array for
      # processing in step 3)
      if ($NF in files) ARGV[ARGC++] = $0; next
    }
    NF {
      # record all matches (here in fields matched by FPAT)
      $1 = $1 # force a rebuild of $0 joining fields with OFS
      matches[FILENAME] = matches[FILENAME] \
                          (matches[FILENAME] == "" ? "" : OFS) \
                          $0
    }
    END {
      for (file in matches)
        print file ": " matches[file]
    }' step=1 searchFiles.txt \
       step=2 RS='\0' FS=/ - \
       step=3 RS='\n' FPAT='[^ ]*_XYZ' OFS=';'

위에서 파일 이름은 에 저장됩니다 searchFiles.txt. 파일의 줄이 접미사 목록인 경우 연관 배열 대신 정규 표현식을 작성할 수 있습니다.

find . -type f -print0 |
  gawk '
    step == 1 {
      gsub(/[][^$*()+{}?\\.|]/, "\\\\&") # escape regexp operators
      regex = regex sep $0; sep = "|"
      next
    }
    step == 2 {
      # determine which files to look into (added to ARGV array for
      # processing in step 3)
      if ($NF ~ ("(" regex ")$")) ARGV[ARGC++] = $0; next
    }
    NF {
      # record all matches (here in fields matched by FPAT)
      $1 = $1 # force a rebuild of $0 joining fields with OFS
      matches[FILENAME] = matches[FILENAME] \
                          (matches[FILENAME] == "" ? "" : OFS) \
                          $0
    }
    END {
      for (file in matches)
        print file ": " matches[file]
    }' step=1 searchFiles.txt \
       step=2 RS='\0' FS=/ - \
       step=3 RS='\n' FPAT='[^ ]*_XYZ' OFS=';'

난독화해야 하는 경우 한 줄에 입력할 수 있습니다.

find . -type f -print0|gawk '!s{gsub(/[][^$*()+{}?\\.|]/,"\\\\&");r=r p $0;p="|";next};s==2{if($NF~("("r")$"))ARGV[ARGC++]=$0;next};NF{$1=$1;m[FILENAME]=m[FILENAME](m[FILENAME]==""?"":OFS)$0};END{for(f in m)print f":"m[f]}' searchFiles.txt s=2 RS=\\0 FS=/ - s=3 RS=\\n FPAT='[^ ]*_XYZ' OFS=\;

파일 이름과 내용에 포함될 수 있는 문자에 대해 가정하지 않습니다. 단, 해당 문자는 로케일에서 유효한 문자여야 합니다. 접미사에는 개행 문자를 사용할 수 없지만 이는 searchFiles.txt.

답변2

나는 주석에서 논의된 DOS 스타일 줄 끝을 수정했으며 searchFiles.txt실제로 빈 줄을 포함하지 않는다고 가정합니다.

-name테스트에서는 find하나의 파일 이름 패턴만 사용합니다. 패턴에는 쉘 glob 문자가 포함될 수 있지만 쉘이 파일 이름을 조기에 생성하지 않도록 이러한 문자를 보호해야 합니다. 논리적 OR을 사용하여 이러한 테스트를 여러 개 결합할 수 있지만 -o연산자 우선 순위에 주의해야 합니다.

쉘이 배열을 지원하는 경우 이를 수행할 수 있는 한 가지 방법은 다음과 같습니다( bash여기서는 이를 사용하고 있지만 비슷한 프로세스가 다른 쉘에서도 작동해야 합니다).

files=( -false )
while IFS= read -r f || [ -n "$f" ]; do files+=( -o -name "*$f"); done < searchFiles.txt

${files[@]}이로 인해 대체 서비스로 확장 되어야 합니다 .

-false -o -name *file.a.txt -o -name *file.b.txt -o -name *file.c.txt -o -name *file.d.txt -o -name *file.e.txt

find그런 다음 다음 과 같은 명령 에서 사용할 수 있습니다 .

find . \( "${files[@]}" \) -exec grep -Ho '[^ ]*_XYZ' {} +

( 옵션을 /dev/null추가하기 위해 더미 파일을 생략했습니다 ).-H파일 수가 searchFiles.txt너무 많으면 제한으로 인해 이 방법이 실패할 수 있습니다 ARG_MAX.searchFiles.txt여러 개의 작은 파일로 분할하여 이 제한 사항을 해결할 수 있습니다 .

답변3

grep -f포함할 이름을 사용하여 텍스트 파일을 통해 파일 이름을 디렉터리로 필터링할 수 있습니다(부분 일치 허용). 그런 다음 이러한 파일은 수많은 grep검색 패턴을 거쳐 최종적으로 작은 awk.

GNU 사용 bash:

grep -Ff filenames.txt <(printf '%s\n' *) |
    xargs -d '\n' grep -oH '[^[:space:]]*_XYZ$' | awk -F: '
        {f[$1] = f[$1] ? f[$1] ";" $2 : $0}
        END {for (x in f) print f[x]}'

몇 가지 가정(문제는 아직 완전히 명확하지 않음):

  • 파일 이름은 편리하게 개행이나 콜론(출력용 grep)이 없습니다. 공백이 처리되었습니다.
  • 거기에는 일치하는 하위 디렉터리가 없습니다. 그렇지 않으면 두 번째 하위 디렉터리가 grep메시지를 표시하지만 결과를 반환합니다.
  • 두 번째는 grep줄 끝에서 패턴을 찾습니다. 단어 끝을 일치시키려는 경우 이를 수정할 수 있습니다.
  • -H하나의 파일이 있는 grep극단적인 경우 파일 이름을 출력에 인쇄합니다(파일이 두 개 이상인 경우 기본값입니다).

관련 정보