찾기: 대용량 파일 이름 목록을 효율적으로 검색하는 방법

찾기: 대용량 파일 이름 목록을 효율적으로 검색하는 방법

일부 목록에서 기본 이름이 제공되는 수백 개의 파일을 찾아야 합니다(라고 부르겠습니다 baseNames). 그런 다음 기본 이름 + 주어진 세 가지 확장자를 검색해야 합니다.

예:입력 목록에서 추출된 기본 이름 중 하나가 이고, FOO제공된 확장자가 .txt, .csv, 이라고 가정합니다 .py. 그럼 FOO.txt,, 를 찾아야 합니다 FOO.csv.FOO.py

내 bash 스크립트의 현재 방법은 다음과 같습니다.

for bn in ${baseNames}; do
  find ${searchDir} '(' -name "$bn.txt" -o -name "$bn.csv" -o -name "$bn.py" ')'
done

이는 작동하지만 비효율적입니다. 각 기본 이름에 대해 find전체 작업을 다시 실행해야 하는데 searchDir, 여기에는 꽤 많은 파일이 포함되어 있으므로 시간이 걸립니다.

find옵션이나 파이프를 통해 검색해야 하는 파일 목록을 제공하는 방법이 있습니까?

분명히 알고 있지만 -name ... -or수백 개의 파일이 있는 경우 이 접근 방식은 분명히 실용적이지 않습니다. 단순화를 위해 확장명을 무시할 수도 있습니다. .find

답변1

배열을 사용하세요. 예를 들어

#!/bin/bash

baseNames=(FOO BAR BAZ)

findNames=('(')
for bn in "${baseNames[@]}"; do
  for ext in txt csv py; do
    findNames+=("$bn.$ext" '-o' '-name')
  done
done
# replace the final '-o' and '-name' in the array with a close parenthesis
unset 'findNames[-1]'
findNames[-1]=')'
# If using a version of bash before v4.3, use:
#unset 'findNames[${#findNames[@]}-1]'
#findNames[${#findNames[@]}-1]=')'


declare -p findNames

출력은 다음과 같습니다 declare -p(개행과 공백을 추가하여 더 쉽게 읽을 수 있도록 했습니다).

declare -a findNames=(
  [0]="("
    [1]="-name" [2]="FOO.txt" [3]="-o" [4]="-name" [5]="FOO.csv"
    [6]="-o" [7]="-name" [8]="FOO.py" [9]="-o" [10]="-name" [11]="BAR.txt"
    [12]="-o" [13]="-name" [14]="BAR.csv" [15]="-o" [16]="-name" [17]="BAR.py"
    [18]="-o" [19]="-name" [20]="BAZ.txt" [21]="-o" [22]="-name" [23]="BAZ.csv"
    [24]="-o" [25]="-name" [26]="BAZ.py"
  [27]=")"
)

에서 배열을 사용하려면 find다음을 수행해야 합니다.

searchDir="./"
find "$searchDir" "${findNames[@]}"

그러면 다음 find 명령이 실행됩니다(가독성을 위해 줄 바꿈이 추가됨).

find ./ ( -name FOO.txt -o -name FOO.csv -o -name FOO.py \
  -o -name BAR.txt -o -name BAR.csv -o -name BAR.py \
  -o -name BAZ.txt -o -name BAZ.csv -o -name BAZ.py )

그리고 여기서 이스케이프할 필요가 없습니다. 왜냐하면 쉘은 이를 하위 쉘을 시작하라는 지시가 아닌 리터럴 인수(배열은 bash 확장됨)로 처리하기 때문입니다 (. )쉘에 입력하는 경우 이스케이프하거나 인용해야 합니다.

답변2

다음 sh스크립트는 한 줄에 하나의 이름이 포함된 파일에서 기본 이름을 읽고 names(이름에 공백 등이 포함된 경우 따옴표로 묶어야 함) 해당 이름(한 번에 50개)이 포함된 인라인 스크립트 배치를 호출합니다 sh -c. 데이터가 단일 호출에 비해 너무 긴 목록으로 확장되는 경우를 대비하여 입력을 일괄 처리로 분할합니다 (총 결합 길이가 입력 데이터의 길이 find를 초과하는 명령을 구성해야 합니다 . 여기서 파일 이름 접미사의 수는 다음과 같습니다). 찾다) .nn

인라인 스크립트는 -name주어진 기본 이름을 기반으로 테스트의 "OR 목록"을 작성합니다 find. 각 기본 이름 은 세 가지 파일 이름 접미사 및 의 변형이 포함된 목록에 입력됩니다 .txt..csv.py

이 목록은 위치 매개변수 목록에 저장됩니다 "$@".

목록이 완성되면 find함수를 호출하여 디렉토리 안이나 아래에서 이러한 이름과 일치하는 일반 파일을 찾습니다 $topdir.

topdir=$HOME

<names xargs -L 50 sh -c '
        topdir=$1; shift

        for name do
                for suffix in txt csv py; do
                        set -- "$@" -o -name "$name.$suffix"
                done
                shift  # shift off current base name
        done
        shift  # shift off the initial "-o"

        find "$topdir" -type f \( "$@" \) -print
' sh "$topdir"

50보다 작은 숫자로 실행하고 sh -x -c대신 사용하여 sh -c인라인 스크립트가 실제로 실행하는 명령을 확인하세요.


명명된 배열과 셸을 사용하려면 다음 을 수행하세요 bash.

topdir=$HOME

<names xargs -L 50 bash -c '
        topdir=$1; shift
        unset tests

        for name do
                for suffix in txt csv py; do
                        tests+=( -o -name "$name.$suffix" )
                done
        done

        find "$topdir" -type f \( "${tests[@]:1}" \) -print
' bash "$topdir"

여기서는 tests위치 인수 목록 대신 배열이 사용됩니다. "${tests[@]:1}"첫 번째 요소(예: )를 제외하고 배열 요소 목록으로 확장하는 것이 이상해 보입니다 -o.

그러나 다음을 사용하는 경우 bash글로빙 도구(원래 shell 에서 상속됨)를 사용할 수도 있습니다 ksh.

shopt -s extglob globstar dotglob nullglob

topdir=$HOME

printf -v pattern '%s/**/@(%s).@(txt|csv|py)' "$topdir" "$(paste -s -d '|' - <names)"

eval "pathnames=( $pattern )"

# The following loop is only for illustration.
# If you really just wanted to list the names, use
#     printf '%s\n' "${pathnames[@]}"

for pathname in "${pathnames[@]}"; do
        printf '%s\n' "$pathname"
done

이는 파일의 내용을 기반으로 확장된 와일드카드 패턴을 구축합니다 names. 이 패턴은 결국 다음과 같이 보일 수 있습니다.

/home/myself/**/@(name1|name2|name3).@(txt|csv|py)

...당신이 관심을 가질 만한 이름과 일치합니다. (디렉터리 등에서 일반 파일을 필터링하기 위해) 루프에서 직접 모든 파일 형식 테스트를 수행해야 합니다.

스크립트 상단에 설정된 셸 옵션을 사용하면 확장 모드 @(...|...)( ) 사용, 하위 디렉터리( )에 대한 하향 일치 extglob사용 , 숨겨진 하위 디렉터리( )에 있거나 있는 이름을 숨길 수 있습니다 . 또한 일치하는 항목이 전혀 없으면 패턴이 사라지도록 ta를 설정했습니다.**globstardotglobnullglob

답변3

zsh 사용(따옴표 없이 이 매개변수를 확장하면 코드에서 zsh 구문을 사용하게 됩니다):

names=(foo bar baz)
exts=(txt csv py)
print -rC1 - **/(${(~j[|])names}).${(~j[|])exts})(ND)

여기서 a는 전역 연산자로 간주되므로 ${(j[|])array}배열의 요소를 연결하는 데 사용됩니다 |. .​​​|~NnullglobDdotglob

아니면 물론 이렇게 하세요:

print -rC1 - **/(foo|bar|baz).(cvs|py|txt)(ND)

일부 파일에서 한 줄에 하나의 이름과 확장자를 찾으면 다음을 사용하십시오.

names=( ${(f)"$(< names.txt)"} )
 exts=( ${(f)"$(< exts.txt)"}  )

다음과 같이 할 수도 있습니다.

print -rC1 - **/$^names.$^exts(ND)

그러나 이는 이름 + 확장의 각 조합에 대해 재귀적 글로브를 확장하기 때문에 효율성이 떨어집니다.

검색 용 find:

cmd=(find . '(') or=()
for name ($^names.$^exts) cmd+=($or -name ${(b)name}) or=(-o)
cmd+=(')')
$cmd

관련 정보