일부 목록에서 기본 이름이 제공되는 수백 개의 파일을 찾아야 합니다(라고 부르겠습니다 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
를 초과하는 명령을 구성해야 합니다 . 여기서 파일 이름 접미사의 수는 다음과 같습니다). 찾다) .n
n
인라인 스크립트는 -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를 설정했습니다.**
globstar
dotglob
nullglob
답변3
zsh 사용(따옴표 없이 이 매개변수를 확장하면 코드에서 zsh 구문을 사용하게 됩니다):
names=(foo bar baz)
exts=(txt csv py)
print -rC1 - **/(${(~j[|])names}).${(~j[|])exts})(ND)
여기서 a는 전역 연산자로 간주되므로 ${(j[|])array}
배열의 요소를 연결하는 데 사용됩니다 |
. .|
~
N
nullglob
D
dotglob
아니면 물론 이렇게 하세요:
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