문제
for f in $(find .)
호환되지 않는 두 가지를 결합합니다.
find
줄 바꿈 문자로 구분 된 파일 경로 목록을 인쇄합니다. $(find .)
해당 목록에서 인용 부호 를 사용하지 않고 호출 할 때 호출되는 split + glob 연산자 는 문자를 $IFS
(기본적으로 줄 바꿈뿐만 아니라 공백 및 탭 (및 NUL in zsh
) 포함) 문자로 분할하고 각 결과 단어에 대해 globbing을 수행합니다 (제외 의 zsh
경우 ksh93 또는 pdksh 같은 파생 상품) (심지어 중괄호 확장!).
당신이 그것을 만들더라도 :
IFS='
' # split on newline only
set -o noglob # disable glob (also disables brace expansion in pdksh
# but not ksh93)
for f in $(find .) # invoke split+glob
줄 바꿈 문자가 파일 경로에서와 마찬가지로 유효하기 때문에 여전히 잘못되었습니다. 출력 find -print
은 단순히 후 처리가 가능하지 않습니다 ( 여기에 표시된 것처럼 복잡한 트릭을 사용하는 것을 제외하고 ).
즉, 쉘 find
은 파일의 루프를 시작하기 전에 출력을 완전히 저장 한 다음 분할 하여 출력 (메모리에 해당 출력을 두 번째로 저장함을 의미 함)해야합니다.
참고 find . | xargs cmd
유사한 문제가 (이, 공백, 줄 바꿈, 따옴표, (그리고 일부 큰 따옴표와 백 슬래시 xarg
구현) 유효한 문자의 일부가 형성되지 바이트는 문제가 있습니다)
더 정확한 대안
for
출력 에서 루프 를 사용하는 유일한 방법 find
은 다음을 zsh
지원 IFS=$'\0'
하고 사용 하는 것입니다 .
IFS=$'\0'
for f in $(find . -print0)
(교체 -print0
에 -exec printf '%s\0' {} +
대한 find
비 - 표준 (그러나 매우 일반적 요즘)을 지원하지 않는 구현 -print0
)를.
여기에 정확하고 이식 가능한 방법은 다음을 사용하는 것입니다 -exec
.
find . -exec something with {} \;
또는 something
둘 이상의 인수를 취할 수있는 경우 :
find . -exec something with {} +
셸에서 해당 파일 목록을 처리해야하는 경우 :
find . -exec sh -c '
for file do
something < "$file"
done' find-sh {} +
(하나 이상 시작될 수 있음 sh
).
일부 시스템에서는 다음을 사용할 수 있습니다.
find . -print0 | xargs -r0 something with
표준 구문에 비해 이점이 거의없고 파이프 또는 파이프 something
라는 의미 입니다.stdin
/dev/null
병렬 처리 -P
에 GNU 옵션 을 사용하는 것이 좋을 수도 있습니다 xargs
. stdin
문제는 GNU으로 해결할 수 있습니다 xargs
와 -a
공정 대체를 지원 껍질 옵션 :
xargs -r0n 20 -P 4 -a <(find . -print0) something
예를 들어, something
각각 20 개의 파일 인수 를 갖는 최대 4 개의 동시 호출을 실행합니다 .
함께 zsh
또는 bash
의 출력을 반복 할 다른 방법 find -print0
에있다 :
while IFS= read -rd '' file <&3; do
something "$file" 3<&-
done 3< <(find . -print0)
read -d ''
개행으로 구분 된 레코드 대신 NUL로 구분 된 레코드를 읽습니다.
bash-4.4
이상은 다음을 사용 find -print0
하여 반환 된 파일을 배열에 저장할 수도 있습니다 .
readarray -td '' files < <(find . -print0)
zsh
(보존하는 장점이있다 상당 find
의 종료 상태) :
files=(${(0)"$(find . -print0)"})
을 사용하면 zsh
대부분의 find
표현식을 재귀 globbing과 glob 한정자 조합으로 변환 할 수 있습니다 . 예를 들어, 반복 find . -name '*.txt' -type f -mtime -1
은 다음과 같습니다.
for file (./**/*.txt(ND.m-1)) cmd $file
또는
for file (**/*.txt(ND.m-1)) cmd -- $file
(의 필요 조심 --
와 마찬가지로 **/*
, 파일 경로로 시작하지 않는 ./
, 그래서 시작 할 수 있습니다 -
예를 들어).
ksh93
그리고 bash
결국에 대한 지원을 추가 **/
여전히 있지만 사용하게 글로브 예선 (안 더 발전 재귀 대체 (globbing)의 형태하지만) **
매우가 제한합니다. 또한 bash
4.3 이전 버전에서는 디렉토리 트리를 내릴 때 심볼릭 링크를 따릅니다.
루핑 오버와 마찬가지로 $(find .)
이는 전체 파일 목록을 메모리 1 에 저장하는 것을 의미합니다 . 파일에 대한 작업이 파일 검색 에 영향을 미치지 않게하려는 경우 (예를 들어, 파일 자체를 찾을 수있는 파일을 더 추가 할 때)에는 바람직 할 수 있습니다.
다른 안정성 / 보안 고려 사항
경쟁 조건
이제 신뢰성에 대해 이야기하고 있다면, 시간 find
/ zsh
파일 찾기 사이의 경쟁 조건을 언급하고 파일이 기준과 사용 시간을 충족하는지 확인합니다 ( TOCTOU race ).
디렉토리 트리를 내릴 때도 심볼릭 링크를 따르지 말고 TOCTOU 레이스없이 수행해야합니다. find
( find
적어도 GNU )는 openat()
올바른 O_NOFOLLOW
플래그 (지원되는 경우)를 사용하여 디렉토리를 열고 각 디렉토리에 대해 파일 디스크립터를 열어 두어 zsh
/ bash
/ ksh
하지 마십시오. 따라서 공격자가 적시에 디렉토리를 심볼릭 링크로 교체 할 수있게되면 잘못된 디렉토리를 내릴 수 있습니다.
심지어 경우 find
에, 제대로 디렉토리를 내려하지 -exec cmd {} \;
더욱 더와 -exec cmd {} +
한 번 cmd
같은 예를 들어, 실행 cmd ./foo/bar
또는 cmd ./foo/bar ./foo/bar/baz
시간으로 cmd
사용한다 ./foo/bar
의 속성, bar
더 이상 일치 기준에 부합하지 않을 수 find
있지만, 더 악화가 ./foo
되었을 수도 있습니다 다른 곳으로 심볼릭 링크로 대체 (그리고 경주 창에 더 큰 많이 만든 -exec {} +
곳 find
호출 할 수있는 충분한 파일을 가지고 대기를 cmd
).
일부 find
구현에는 -execdir
두 번째 문제점을 완화하기 위해 ( 비표준이지만) 술어가 있습니다.
와:
find . -execdir cmd -- {} \;
find
chdir()
실행하기 전에 파일의 상위 디렉토리로 cmd
. 을 호출하는 대신 ( 일부 구현에서는)을 cmd -- ./foo/bar
호출 하므로 심볼릭 링크로 변경되는 문제를 피할 수 있습니다. 이렇게하면 더 안전한 명령을 사용하게 되지만 (다른 파일을 제거 할 수는 있지만 다른 디렉토리의 파일은 제거 할 수는 없음) 심볼릭 링크를 따르지 않도록 설계되지 않은 경우 파일을 수정할 수있는 명령은 사용할 수 없습니다.cmd -- ./bar
cmd -- bar
--
./foo
rm
-execdir cmd -- {} +
때때로 GNU의 일부 버전을 포함하여 여러 구현과 작동하지만 find
, 그것은 동일합니다 -execdir cmd -- {} \;
.
-execdir
너무 깊은 디렉토리 트리와 관련된 일부 문제를 해결하는 이점도 있습니다.
에서:
find . -exec cmd {} \;
주어진 경로의 크기는 cmd
파일이있는 디렉토리의 깊이에 따라 커질 것입니다. 크기가 PATH_MAX
Linux 보다 4k 보다 크면 cmd
해당 경로에서 수행되는 모든 시스템 호출 은 오류와 함께 실패 ENAMETOOLONG
합니다.
을 사용 -execdir
하면 파일 이름 만 접두사로 붙일 ./
수 cmd
있습니다. 대부분의 파일 시스템에서 파일 이름 자체는보다 훨씬 낮은 한계 ( NAME_MAX
) PATH_MAX
를 가지므로 ENAMETOOLONG
오류가 발생할 가능성이 적습니다.
바이트 대 문자
또한 find
일반적으로 파일 이름을 처리 할 때 보안을 고려할 때 간과되는 경우가 많습니다 . 대부분의 유닉스 계열 시스템에서 파일 이름은 바이트 시퀀스 (파일 경로에서는 0이지만 모든 파일 시스템에서는 0 임) ASCII 기반의 경우, 지금은 희귀 한 EBCDIC 기반의 것을 무시합니다. 0x2f는 경로 구분 기호입니다).
해당 바이트를 텍스트로 간주할지 여부는 응용 프로그램에 따라 다릅니다. 그리고 일반적으로 그렇게하지만 일반적으로 바이트에서 문자로의 변환은 환경에 따라 사용자의 로캘을 기반으로 수행됩니다.
그 의미는 주어진 파일 이름이 로케일에 따라 다른 텍스트 표현을 가질 수 있다는 것입니다. 예를 들어, 바이트 시퀀스 63 f4 74 e9 2e 74 78 74
는 côté.txt
문자 세트가 ISO-8859-1 인 cєtщ.txt
로케일과 문자 세트가 IS0-8859-5 인 로케일에서 해당 파일 이름을 해석하는 응용 프로그램을위한 것입니다.
보다 나쁜. 문자 집합이 UTF-8 (현재 표준) 인 로케일에서 63 f4 74 e9 2e 74 78 74는 단순히 문자에 맵핑 될 수 없습니다!
find
파일 이름을 -name
/ -path
술어에 대한 텍스트로 간주하는 응용 프로그램 중 하나입니다 (그리고 더, -iname
또는 -regex
일부 구현과 함께).
그 의미는 예를 들어 여러 find
구현 (GNU 포함 find
) 을 사용한다는 것입니다 .
find . -name '*.txt'
63 f4 74 e9 2e 74 78 74
UTF-8 로켈에서 호출 할 때 위 의 파일을 찾을 수 없으므로 (바이트가 아닌 *
0 개 이상의 문자 와 일치) 해당 문자가 아닌 문자와 일치 할 수 없습니다.
LC_ALL=C find...
C 로케일은 문자 당 1 바이트를 의미하고 (일반적으로) 모든 바이트 값이 문자에 매핑되도록 보장하기 때문에 (일부 바이트 값에 대해서는 정의되지 않은 것이더라도) 문제를 해결합니다.
이제 쉘에서 해당 파일 이름을 반복 할 때 해당 바이트 대 문자도 문제가 될 수 있습니다. 우리는 일반적으로 다음과 같은 4 가지 주요 유형의 쉘을 봅니다.
여전히 멀티 바이트를 인식하지 못하는 것들은 dash
. 그들을 위해 바이트는 문자에 매핑됩니다. 예를 들어, UTF-8에서 côté
4 자이지만 6 바이트입니다. UTF-8이 문자 세트 인 로케일에서
find . -name '????' -exec dash -c '
name=${1##*/}; echo "${#name}"' sh {} \;
find
UTF-8로 인코딩 된 4 개의 문자로 구성된 파일을 성공적으로 찾을 수 있지만 dash
길이는 4에서 24 사이입니다.
yash
: 반대. 문자 만 다룹니다 . 모든 입력은 내부적으로 문자로 변환됩니다. 가장 일관된 쉘을 만들지 만 임의의 바이트 시퀀스 (유효한 문자로 변환되지 않는)에 대처할 수 없다는 것을 의미합니다. C 로케일에서도 0x7f 이상의 바이트 값에는 대처할 수 없습니다.
find . -exec yash -c 'echo "$1"' sh {} \;
예를 들어 UTF-8 로켈의 ISO-8859-1에서는 실패 côté.txt
합니다.
멀티 바이트 지원이 좋아 bash
지거나 zsh
점진적으로 추가 된 것들. 그것들은 마치 문자 인 것처럼 문자에 매핑 될 수없는 바이트를 고려하는 것으로 넘어갑니다. 여기에는 여전히 몇 가지 버그가 있으며 특히 GBK 또는 BIG5-HKSCS와 같은 덜 일반적인 멀티 바이트 문자 세트가 있습니다 (멀티 바이트 문자 중 많은 수가 0-127 범위의 바이트를 포함하므로 상당히 불쾌합니다 (ASCII 문자와 같은)). ).
sh
FreeBSD 와 같 거나 (최소 11 개) mksh -o utf8-mode
멀티 바이트를 지원하지만 UTF-8 만 지원합니다.
노트
1 완전성을 zsh
위해 전체 목록을 메모리에 저장하지 않고 재귀 적 globbing을 사용하여 파일을 반복 하는 해킹 방법을 언급 할 수 있습니다 .
process() {
something with $REPLY
false
}
: **/*(ND.m-1+process)
+cmd
은 cmd
현재 파일 경로를 사용하여 (일반적으로 함수) 를 호출하는 glob 한정자입니다 $REPLY
. 이 함수는 파일을 선택해야하는지 여부를 결정하기 위해 true 또는 false를 반환합니다 (또한 $REPLY
여러 파일을 $reply
배열 로 수정 하거나 반환 할 수도 있음 ). 여기서 우리는 그 함수에서 처리하고 파일을 선택하지 않도록 false를 반환합니다.