패턴 파일이 있고 이를 파일 디렉터리와 비교하고 싶습니다.
패턴 파일 내용은 다음과 같습니다(정규 표현식일 수도 있음).
pattern-that-occurs-in-file
pattern-that-also-occurs-in-file
콘텐츠가 패턴과 일치하는 경우 표시되어야 하는 검색 파일의 예:
unrelated content
pattern-that-occurs-in-file
more unrelated content
pattern-that-also-occurs-in-file
further unrelated content
또는:
unrelated content
pattern-that-also-occurs-in-file
more unrelated content
pattern-that-occurs-in-file
further unrelated content
샘플 검색 파일은 다음과 같습니다.아니요이리와:
unrelated content
more unrelated content
pattern-that-occurs-in-file
further unrelated content
또는:
unrelated content
pattern-that-also-occurs-in-file
more unrelated content
further unrelated content
또는:
unrelated content
more unrelated content
further unrelated content
두 가지 패턴이 나타나는 파일 목록을 출력하려면 grep이 필요합니다. 일치하는 선이 보이더라도 상관 없습니다.
단일 명령으로 이 작업을 수행할 수 있습니까? 그렇다면 어떻게 해야 할까요?
답변1
정확한 명령은 아니지만 다음과 같습니다.
num_patterns=$( wc -l < patterns_file )
for file in dir/*; do
num_occurrances=$( grep -F -o -f patterns_file "$file" | sort -u | wc -l )
if (( num_patterns == num_occurrances )); then
echo "all patterns in $file"
fi
done
패턴이 정규식인 경우에는 일치 텍스트가 모든 일치 항목에 대해 고유하지 않을 수 있으므로 이 방법은 작동하지 않습니다.
답변2
./*.txt
관심 있는 모든 파일이 일치하고 다음을 포함하는 파일을 찾고 싶다고 가정해 보겠습니다.모두~의끈파일에서 ./patterns
(세 줄 이상 포함될 수 있음):
#!/bin/bash
pathnames=( ./*.txt )
while IFS= read -r pattern; do
for pathname in "${pathnames[@]}"; do
pathnames=( ${pathnames[@]:1} )
if grep -qF -e "$pattern" "$pathname"; then
pathnames+=( "$pathname" )
fi
done
done < ./patterns
printf 'Matched: %s\n' "${pathnames[@]}"
그러면 패턴이 순환됩니다. 각 패턴에 대해 배열의 모든 파일을 테스트합니다 pathnames
. 패턴이 일치하면 현재 경로 이름을 배열에 유지하고, 그렇지 않으면 폐기합니다. 마지막으로 pathnames
모든 패턴을 포함하는 경로 이름만 포함됩니다.
pathnames
어레이가 관리되는 방식 으로 인해 grep
더 많은 파일이 삭제됨에 따라 각 패턴에 대한 호출 수가 감소합니다.
이 명령은 pathnames=( ${pathnames[@]:1} )
배열에서 첫 번째(현재) 경로 이름을 제거하고 pathnames+=( "$pathname" )
끝에 다시 배치합니다.
이 명령 grep -qF -e "$pattern" "$pathname"
은진짜파일 $pathname
에 $pattern
. -q
make Quiet을 사용 grep
하고 파일의 패턴과 일치하면 즉시 종료되도록 합니다. -F
정규식 일치 대신 문자열 비교에 사용합니다 .
sh
저는 명명된 배열보다 간결한 구문을 선호하기 때문에 bash
위의 변형이 있습니다 /bin/sh
(위치 매개변수가 pathnames
배열을 대체함).
#!/bin/sh
set -- ./*.txt
while IFS= read -r pattern; do
for pathname do
shift
if grep -qF -e "$pattern" "$pathname"; then
set -- "$@" "$pathname"
fi
done
done < ./patterns
printf 'Matched: %s\n' "$@"
답변3
내가 올바르게 이해했다면 이것이 옵션이 될 수 있습니다(내 논리가 타당하다면). 여기서는 패턴이 각 파일에서 고유하다고 가정합니다.
grep -R < file_with_patterns . | cut -d':' -f1 | uniq -d
grep
두 패턴이 일치하면 두 행을 반환하거나 한 행만 반환하거나 아무것도 반환하지 않습니다. 이러한 상황을 활용하여 uniq -d
파일 이름에 대해 중복된 결과만 표시합니다.
답변4
@glenn-jackman 및 @schrodigerscatcuriosity의 답변은 정규식을 통과하지 못했습니다(OP는 정규식도 포함하도록 질문을 수정했습니다). 예를 들어 패턴은 1.
파일의 "1a" 및 "1b"와 일치하지만 패턴은 2.
아무것도 일치하지 않지만 두 알고리즘 모두 파일이 두 패턴과 일치한다고 결론을 내립니다. 둘째, 패턴은 123
"1234"와 일치하지만 12
일치하는 패턴으로 인해 grep이 추가 출력을 생성하지 않습니다. 두 알고리즘 모두 파일이 두 패턴 중 하나만 일치한다고 결론을 내립니다.
@kusalananda는 잘 작동하지만 더 효율적인 솔루션이 있을 수 있습니다.
files=`find ./*.txt`
while read pattern; do
files=`echo "$files" | xargs grep -l "$pattern"` || break
done < ./patterns
echo Matched: $files
이 솔루션은 @kusalananda의 솔루션과 유사합니다. 즉, 패턴을 반복하면서 일치하지 않는 파일을 모두 제거합니다. 그러나 이 솔루션은 xargs grep -l
중첩 루프 대신 파일을 사용합니다. 따라서 대략적으로 파일당 패턴당 하나의 grep 프로세스를 실행하는 대신 패턴당 하나의 grep 프로세스를 실행하므로 훨씬 더 빨라야 합니다.
추신: 이 솔루션은 파일 이름의 공백을 처리하지 않지만 @kusalananda는 처리합니다. 그러나 이 솔루션은 파일 이름의 공백을 처리하도록 쉽게 수정할 수 있습니다. 파일 이름에 공백이나 기타 잘못된 문자가 있으면 먼저 부끄러워서 머리를 숙이고 두 번째로 변경하십시오.
xargs
도착하다
tr \\n \\0 | xargs -0
이것이 혼란스럽고 주요 문제와 관련이 없어 보이기 때문에 이것을 주요 해결책으로 포함시키지 않았습니다.
PPS: 최대 속도를 위해 가장 희귀한 패턴을 패턴 파일에 먼저 배치하고 가장 일반적인 패턴을 마지막에 배치하여 가능한 한 많은 파일을 초기에 제거합니다.