find의 출력을 반복하는 것이 왜 나쁜 습관입니까?


170

이 질문은

왜 쉘 루프를 사용하여 텍스트를 처리하는 것이 나쁜 습관으로 간주됩니까?

나는이 구성들을 본다

for file in `find . -type f -name ...`; do smth with ${file}; done

for dir in $(find . -type d -name ...); do smth with ${dir}; done

... 거의 어떤 사람들은 이런 종류의 물건은 피해야한다 이유를 설명하는 소식에 댓글을 달 시간이 걸릴 경우에도 매일 여기에 사용되는
등 게시물의 수 (때로는 그 의견을 간단하게 무시하고 있다는 사실을)보고 나는 질문을 할 수도 있다고 생각했다.

루프 오버 find출력이 나쁜 이유는 무엇이며 각 파일 이름 / 경로에 대해 하나 이상의 명령을 실행하는 올바른 방법은 find무엇입니까?


12
나는 이것이 "절대 ls 출력을 구문 분석하지 마라!"라고 생각합니다. -확실하게 하나의 작업을 수행 할 수 있지만 생산 품질보다 빠른 해킹입니다. 또는 더 일반적으로 절대적으로 독단적이지 마십시오.
브루스 Ediger


이것은 정식 답변으로
바뀌어야합니다

6
찾기의 요점은 찾은 내용을 반복하는 것입니다.
OrangeDog

2
하나의 보조 지점-출력을 파일로 보낸 다음 나중에 스크립트에서 처리 할 수 ​​있습니다. 이런 식으로 스크립트를 디버깅해야하는 경우 파일 목록을 검토 할 수 있습니다.
user117529

답변:


87

문제

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)의 형태하지만) **매우가 제한합니다. 또한 bash4.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 -- ./barcmd -- bar--./foorm

-execdir cmd -- {} +때때로 GNU의 일부 버전을 포함하여 여러 구현과 작동하지만 find, 그것은 동일합니다 -execdir cmd -- {} \;.

-execdir 너무 깊은 디렉토리 트리와 관련된 일부 문제를 해결하는 이점도 있습니다.

에서:

find . -exec cmd {} \;

주어진 경로의 크기는 cmd파일이있는 디렉토리의 깊이에 따라 커질 것입니다. 크기가 PATH_MAXLinux 보다 4k 보다 크면 cmd해당 경로에서 수행되는 모든 시스템 호출 은 오류와 함께 실패 ENAMETOOLONG합니다.

을 사용 -execdir하면 파일 이름 만 접두사로 붙일 ./cmd있습니다. 대부분의 파일 시스템에서 파일 이름 자체는보다 훨씬 낮은 한계 ( NAME_MAX) PATH_MAX를 가지므로 ENAMETOOLONG오류가 발생할 가능성이 적습니다.

바이트 대 문자

또한 find일반적으로 파일 이름을 처리 할 때 보안을 고려할 때 간과되는 경우가 많습니다 . 대부분의 유닉스 계열 시스템에서 파일 이름은 바이트 시퀀스 (파일 경로에서는 0이지만 모든 파일 시스템에서는 0 임) ASCII 기반의 경우, 지금은 희귀 한 EBCDIC 기반의 것을 무시합니다. 0x2f는 경로 구분 기호입니다).

해당 바이트를 텍스트로 간주할지 여부는 응용 프로그램에 따라 다릅니다. 그리고 일반적으로 그렇게하지만 일반적으로 바이트에서 문자로의 변환은 환경에 따라 사용자의 로캘을 기반으로 수행됩니다.

그 의미는 주어진 파일 이름이 로케일에 따라 다른 텍스트 표현을 가질 수 있다는 것입니다. 예를 들어, 바이트 시퀀스 63 f4 74 e9 2e 74 78 74cô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 74UTF-8 로켈에서 호출 할 때 위 의 파일을 찾을 수 없으므로 (바이트가 아닌 *0 개 이상의 문자 와 일치) 해당 문자가 아닌 문자와 일치 할 수 없습니다.

LC_ALL=C find... C 로케일은 문자 당 1 바이트를 의미하고 (일반적으로) 모든 바이트 값이 문자에 매핑되도록 보장하기 때문에 (일부 바이트 값에 대해서는 정의되지 않은 것이더라도) 문제를 해결합니다.

이제 쉘에서 해당 파일 이름을 반복 할 때 해당 바이트 대 문자도 문제가 될 수 있습니다. 우리는 일반적으로 다음과 같은 4 가지 주요 유형의 쉘을 봅니다.

  1. 여전히 멀티 바이트를 인식하지 못하는 것들은 dash. 그들을 위해 바이트는 문자에 매핑됩니다. 예를 들어, UTF-8에서 côté4 자이지만 6 바이트입니다. UTF-8이 문자 세트 인 로케일에서

    find . -name '????' -exec dash -c '
      name=${1##*/}; echo "${#name}"' sh {} \;
    

    findUTF-8로 인코딩 된 4 개의 문자로 구성된 파일을 성공적으로 찾을 수 있지만 dash길이는 4에서 24 사이입니다.

  2. yash: 반대. 문자 만 다룹니다 . 모든 입력은 내부적으로 문자로 변환됩니다. 가장 일관된 쉘을 만들지 만 임의의 바이트 시퀀스 (유효한 문자로 변환되지 않는)에 대처할 수 없다는 것을 의미합니다. C 로케일에서도 0x7f 이상의 바이트 값에는 대처할 수 없습니다.

    find . -exec yash -c 'echo "$1"' sh {} \;
    

    예를 들어 UTF-8 로켈의 ISO-8859-1에서는 실패 côté.txt합니다.

  3. 멀티 바이트 지원이 좋아 bash지거나 zsh점진적으로 추가 된 것들. 그것들은 마치 문자 인 것처럼 문자에 매핑 될 수없는 바이트를 고려하는 것으로 넘어갑니다. 여기에는 여전히 몇 가지 버그가 있으며 특히 GBK 또는 BIG5-HKSCS와 같은 덜 일반적인 멀티 바이트 문자 세트가 있습니다 (멀티 바이트 문자 중 많은 수가 0-127 범위의 바이트를 포함하므로 상당히 불쾌합니다 (ASCII 문자와 같은)). ).

  4. shFreeBSD 와 같 거나 (최소 11 개) mksh -o utf8-mode멀티 바이트를 지원하지만 UTF-8 만 지원합니다.

노트

1 완전성을 zsh위해 전체 목록을 메모리에 저장하지 않고 재귀 적 globbing을 사용하여 파일을 반복 하는 해킹 방법을 언급 할 수 있습니다 .

process() {
  something with $REPLY
  false
}
: **/*(ND.m-1+process)

+cmdcmd현재 파일 경로를 사용하여 (일반적으로 함수) 를 호출하는 glob 한정자입니다 $REPLY. 이 함수는 파일을 선택해야하는지 여부를 결정하기 위해 true 또는 false를 반환합니다 (또한 $REPLY여러 파일을 $reply배열 로 수정 하거나 반환 할 수도 있음 ). 여기서 우리는 그 함수에서 처리하고 파일을 선택하지 않도록 false를 반환합니다.


zsh와 bash를 사용할 있다면 find안전하게 동작 하도록 왜곡하는 대신 globbing 및 shell 구문을 사용하는 것이 좋습니다 . 글 로빙은 기본적으로 안전하지만 find는 기본적으로 안전하지 않습니다.
Kevin

@Kevin, 편집 참조.
Stéphane Chazelas

182

루프 오버 find출력 이 왜 나쁜 습관입니까?

간단한 대답은 다음과 같습니다.

파일 이름은 모든 문자를 포함 수 있기 때문 입니다.

따라서 파일 이름을 구분하는 데 안정적으로 사용할 수있는 인쇄 가능한 문자가 없습니다.


뉴 라인이되어 종종 이 있기 때문에, 파일 이름을 구분하기 위해 (잘못) 사용 이상한 파일 이름에서 개행 문자를 포함 할 수 있습니다.

그러나 임의의 가정을 중심으로 소프트웨어를 구축하는 경우에는 예외적 인 경우를 처리하지 못하고 최악의 경우 시스템을 제어 할 수있는 악의적 인 악용에 노출 될 수 있습니다. 따라서 견고성과 안전의 문제입니다.

두 가지 방식으로 소프트웨어를 작성할 수 있고 그 중 하나가 엣지 케이스 (비정상 입력)를 올바르게 처리하지만 다른 하나는 읽기가 더 쉬운 경우, 트레이드 오프가 있다고 주장 할 수 있습니다. (그렇지 않습니다. 올바른 코드를 선호합니다.)

그러나, 정확하고 강력한 코드 버전 읽기 쉬운 경우, 에지 사례에서 실패한 코드 작성에 대한 변명의 여지가 없습니다. 이 경우 find발견 된 각 파일에 대해 명령을 실행해야합니다.


좀 더 구체적으로 설명하자면 : UNIX 또는 Linux 시스템에서 파일 이름에는 /경로 구성 요소 구분자로 사용되는 문자를 제외한 모든 문자가 포함될 수 있으며 null 바이트를 포함 할 수 없습니다.

따라서 널 바이트는 파일 이름을 구분 하는 유일한 올바른 방법입니다.


GNU 이후 find포함 -print0가 인쇄 파일명을 구분하기 위해 널 (null) 바이트를 사용 차를 GNU가 find 있다 안전하게 GNU 함께 사용 xargs-0플래그 (및 -r출력을 처리하는 플래그) find:

find ... -print0 | xargs -r0 ...

그러나이 양식을 사용해야 할 이유 는 없습니다 .

  1. GNU findutils에 대한 의존성을 추가합니다.
  2. find되어 설계 찾은 파일에서 명령을 실행할 수 있도록.

또한, GNU는 xargs필요 -0-rFreeBSD는 반면, xargs단지 필요 -0(더없는 -r옵션), 일부는 xargs지원하지 않습니다 -0전혀. 따라서 POSIX 기능 find(다음 섹션 참조)을 고수 하고 건너 뛰는 것이 가장 좋습니다 xargs.

find발견 한 파일에 대해 명령을 실행할 수있는 지점 2 와 관련하여 Mike Loukides는 다음과 같이 말했습니다.

find의 사업은 파일을 찾는 것이 아니라 표현을 평가하는 것입니다. 예, find확실히 파일을 찾습니다. 그러나 그것은 실제로 부작용 일뿐입니다.

-유닉스 전동 공구


POSIX 지정 용도 find

find결과 에 대해 하나 이상의 명령을 실행하는 올바른 방법은 무엇입니까 ?

발견 된 각 파일에 대해 단일 명령을 실행하려면 다음을 사용하십시오.

find dirname ... -exec somecommand {} \;

발견 된 각 파일에 대해 여러 명령을 순서대로 실행하려면 첫 번째 명령이 성공한 경우에만 두 번째 명령을 실행해야합니다.

find dirname ... -exec somecommand {} \; -exec someothercommand {} \;

한 번에 여러 파일에서 단일 명령을 실행하려면

find dirname ... -exec somecommand {} +

find 와 함께 sh

당신이 사용해야하는 경우 과 같은 출력을 재 지정하거나 파일 이름이나 비슷한 해제 확장을 제거 등의 명령에 기능을, 당신은 사용할 수 있습니다 sh -c구조를. 이것에 대해 몇 가지를 알아야합니다.

  • 코드에 {}직접 포함 하지 마십시오sh . 이를 통해 악의적으로 제작 된 파일 이름에서 임의의 코드를 실행할 수 있습니다. 또한 실제로 POSIX에 의해 지정되지 않았으므로 전혀 작동하지 않습니다. (다음 요점 참조)

  • {}여러 번 사용하거나 더 긴 인수의 일부로 사용 하지 마십시오 . 이것은 휴대용이 아닙니다. 예를 들어, 이렇게하지 마십시오 :

    find ... -exec cp {} somedir/{}.bak \;

    POSIX 사양find 을 인용하려면 :

    경우 UTILITY_NAME 또는 인수 문자열이 두 글자 "{}"하지만 단지 두 개의 문자가 포함 된 "{}", 그것을 구현 한 것인지의 여부입니다 발견이 두 문자를 대체하거나 변경하지 않고 문자열을 사용합니다.

    ... 두 문자 "{}"을 (를) 포함하는 인수가 둘 이상 있으면 동작이 지정되지 않습니다.

  • -c옵션으로 전달 된 쉘 명령 문자열 다음의 인수 는로 시작$0 하여 쉘의 위치 매개 변수로 설정됩니다 . 로 시작하지 않습니다 $1.

    이러한 이유로 생성 된 쉘 내에서 오류보고에 사용될 "더미" $0값 을 포함하는 것이 좋습니다 find-sh. 또한 "$@"여러 파일을 셸에 전달할 때 와 같은 구문을 사용할 수 있지만 값을 생략하면 $0전달 된 첫 번째 파일이로 설정 $0되어 포함되지 않습니다 "$@".


파일 당 단일 쉘 명령을 실행하려면 다음을 사용하십시오.

find dirname ... -exec sh -c 'somecommandwith "$1"' find-sh {} \;

그러나 일반적으로 발견 된 모든 단일 파일에 대해 쉘을 생성하지 않도록 쉘 루프에서 파일을 처리하는 성능이 향상됩니다.

find dirname ... -exec sh -c 'for f do somecommandwith "$f"; done' find-sh {} +

( for f do이는 for f in "$@"; do각 위치 매개 변수와 동일 하고 처리합니다. 즉, find이름의 특수 문자에 관계없이로 찾은 각 파일을 사용 합니다.)


올바른 find사용법 의 추가 예 :

(참고 :이 목록을 자유롭게 확장하십시오.)


5
각 파일에 대해 현재 쉘에서find 명령을 실행 해야하는 경우 (예 : 변수를 설정하려는 경우)의 구문 분석 출력 에 대한 대안을 모르는 경우가 있습니다. 이 경우 내가 아는 최고의 관용구입니다. 참고 : 이식성이 없습니다. bash 또는 zsh를 사용하십시오. 또한 및 루프 내부에 stdin을 읽으려고 시도하는 경우가 있습니다. while IFS= read -r -u3 -d '' file; do ... done 3< <(find ... -print0)<( )-u33<
Gordon Davisson

1
@GordonDavisson, 아마 -하지만 당신은 그 변수를 설정하기 위해 필요한 것은 무엇 을 위해 ? 나는 그것을 처리해야 무엇이든 그 주장 의 내부find ... -exec 전화를. 또는 유스 케이스를 처리 할 경우 쉘 글로브를 사용하십시오.
와일드 카드

1
파일을 처리 한 후 ( "2 변환, 3 건너 뛰기, 다음 파일에 오류가 발생했습니다 : ...") 요약을 인쇄하려고하는데, 그 수 / 목록은 셸 변수에 누적되어야합니다. 또한 파일 이름 배열을 만들어서 반복하는 것보다 복잡한 작업을 수행 할 수있는 상황이 있습니다 (이 경우에는 filelist=(); while ... do filelist+=("$file"); done ...).
Gordon Davisson

3
당신의 대답은 맞습니다. 그러나 나는 교리를 좋아하지 않습니다. 비록 더 잘 알고 있지만, (특히 대화 형) 사용 사례가 많으며 find출력을 통해 루핑을 입력하는 것이 안전하고 더 쉽습니다 ls. 나는 매일 문제없이하고 있습니다. 모든 종류의 도구의 -print0, --null, -z 또는 -0 옵션에 대해 알고 있습니다. 그러나 실제로 필요한 경우가 아니라면 대화식 쉘 프롬프트에서 사용할 시간을 낭비하지 않을 것입니다. 이것은 귀하의 답변에도 언급 될 수 있습니다.
rudimeier

16
@rudimeier, 도그마 대 모범 사례에 대한 논쟁은 이미 죽었다 . 관심이 없다. 대화식으로 사용하고 작동하면 훌륭하지만 좋습니다.하지만 그렇게하도록 권장하지는 않습니다. 강력한 코드가 무엇인지 배우고 프로덕션 스크립트를 작성할 때 대화 형 작업에 익숙한 작업을 수행하지 않고 스크립트를 작성 하는 스크립트 작성자의 비율 은 극히 적습니다. 처리 방법은 항상 모범 사례홍보하는 것입니다. 사람들 은 일을하는 올바른 방법있다는 것을 배워야합니다 .
와일드 카드

10

이 답변은 매우 큰 결과 집합에 대한 것이며 주로 느린 네트워크를 통해 파일 목록을 가져올 때의 성능과 관련이 있습니다. 소량의 파일 (예 : 로컬 디스크에서 몇 100 또는 1000 정도)의 경우 대부분이 문제가됩니다.

병렬 처리 및 메모리 사용량

분리 문제와 관련하여 주어진 다른 답변 외에도 다른 문제가 있습니다.

for file in `find . -type f -name ...`; do smth with ${file}; done

줄 바꿈으로 분할되기 전에 백틱 내부의 부분을 먼저 완전히 평가해야합니다. 즉, 대량의 파일을 얻는 경우 다양한 구성 요소에있는 크기 제한에 질식 할 수 있습니다. 제한이 없으면 메모리가 부족할 수 있습니다. 어쨌든 전체 목록이 출력 될 때까지 기다렸다가 첫 번째 find구문을 for실행하기 전에 구문 분석 해야합니다 smth.

선호되는 유닉스 방식은 본질적으로 병렬로 실행되며 일반적으로 임의로 큰 버퍼가 필요하지 않은 파이프를 사용하는 것입니다. 즉, find를 병렬로 실행 하는 것을 훨씬 선호 smth하며 현재 파일 이름을 RAM에 유지하면서 파일 이름을 RAM에 보관하십시오 smth.

이를위한 적어도 부분적으로 OKish 솔루션은 앞서 언급 한 것 find -exec smth입니다. 모든 파일 이름을 메모리에 유지할 필요가 없으며 병렬로 훌륭하게 실행됩니다. 불행히도 smth파일 당 하나의 프로세스를 시작 합니다. smth하나의 파일에서만 작동 할 수 있다면 그렇게해야합니다.

가능하다면, 최적의 솔루션이 될 것 find -print0 | smth으로, smth자사의 STDIN에 파일 이름을 처리 할 수있는. 그런 다음 smth파일 수에 관계없이 하나의 프로세스 만 있으며 두 프로세스간에 적은 양의 바이트 (내재적 파이프 버퍼링이 진행중인 경우) 만 버퍼링해야합니다. 물론 이것은 smth표준 Unix / POSIX 명령 이라면 다소 비현실적 이지만 직접 작성하는 경우 접근 방법이 될 수 있습니다.

이것이 가능하지 않다면 find -print0 | xargs -0 smth아마도 더 나은 해결책 중 하나 일 것입니다. 주석에서 언급 한 @ dave_thompson_085 는 시스템 한계에 도달 할 때 (기본적으로 128KB 범위 또는 시스템에 부과되는 한계 에 따라) xargs여러 실행으로 인수를 나눕니다. 파일은 한 번의 호출로 주어 지므로 프로세스 수와 초기 지연 간의 균형을 찾습니다 .smthexecsmthsmth

편집 : "최고"의 개념을 제거-더 나은 무언가가자를 지 여부를 말하기 어렵습니다. ;)


find ... -exec smth {} +솔루션입니다.
와일드 카드

find -print0 | xargs smth전혀 작동하지 않지만 find -print0 | xargs -0 smth(주 -0) 또는 find | xargs smth파일 이름에 공백 따옴표가 없거나 백 슬래시가 smth있으면 가능한 한 많은 파일 이름으로 실행 되고 하나의 인수 목록에 맞습니다 . maxargs를 초과하면 smth주어진 모든 인수를 처리하는 데 필요한 횟수만큼 실행 됩니다 (제한 없음). 로 더 작은 '청크'(따라서 다소 초기 병렬 처리)를 설정할 수 있습니다 -L/--max-lines -n/--max-args -s/--max-chars.
dave_thompson_085 1


4

한 가지 이유는 공백이 작업에서 스패너를 던지고 'foo bar'파일이 'foo'및 'bar'로 평가되게하기 때문입니다.

$ ls -l
-rw-rw-r-- 1 ec2-user ec2-user 0 Nov  7 18:24 foo bar
$ for file in `find . -type f` ; do echo filename $file ; done
filename ./foo
filename bar
$

-exec를 대신 사용하면 정상적으로 작동합니다.

$ find . -type f -exec echo filename {} \;
filename ./foo bar
$ find . -type f -exec stat {} \;
  File: ‘./foo bar’
  Size: 0               Blocks: 0          IO Block: 4096   regular empty file
Device: ca01h/51713d    Inode: 9109        Links: 1
Access: (0664/-rw-rw-r--)  Uid: (  500/ec2-user)   Gid: (  500/ec2-user)
Access: 2016-11-07 18:24:42.027554752 +0000
Modify: 2016-11-07 18:24:42.027554752 +0000
Change: 2016-11-07 18:24:42.027554752 +0000
 Birth: -
$

특히 find모든 파일에서 명령을 실행하는 옵션 이 있기 때문에 쉽게 가장 좋은 옵션입니다.
Centimane

1
또한 고려 -exec ... {} \;-exec ... {} +
thrig

1
당신이 사용하는 경우 for file in "$(find . -type f)" echo "${file}"다음 그래도, 심지어 공백으로 내가 더 많은 문제의 원인 추측 다른 특수 문자를 작동
mazs

9
@mazs-아니오, 인용은 당신이 생각하는 것을하지 않습니다. 여러 파일이있는 디렉토리에서 for file in "$(find . -type f)";do printf '%s %s\n' name: "${file}";done각 파일 이름을 앞에 나오는 별도의 줄에 인쇄해야합니다 (사용자에 따라) name:. 그렇지 않습니다.
don_crissti

2

모든 명령의 출력은 단일 문자열이지만 루프를 반복하려면 루프에 문자열 배열이 필요합니다. "작동"하는 이유는 쉘이 문자열을 배신하여 공백으로 나눕니다.

둘째,의 특정 기능이 필요하지 않은 경우 find, 쉘이 이미 재귀 glob 패턴을 자체적으로 확장 할 수 있으며 결정적으로 적절한 배열로 확장 될 수 있다는 점에 유의하십시오.

배쉬 예제 :

shopt -s nullglob globstar
for i in **
do
    echo «"$i"»
done

물고기도 마찬가지입니다 :

for i in **
    echo «$i»
end

의 기능이 필요한 경우 관용구 find와 같이 NUL에서만 분할해야합니다 find -print0 | xargs -r0.

물고기는 NUL 구분 출력을 반복 할 수 있습니다. 그래서이 사람은 실제로 하지 나쁜 :

find -print0 | while read -z i
    echo «$i»
end

마지막으로, 많은 쉘 (물론 피쉬가 아닌)에서 명령 출력을 반복하면 루프 본문이 하위 쉘이됩니다 (루프가 끝난 후에 볼 수있는 방식으로 변수를 설정할 수 없음을 의미 함). 당신이 원하는 것을 절대로.


@don_crissti 정확하게. 일반적으로 작동 하지 않습니다 . 나는 그것이 "일하는"(따옴표와 함께) 말함으로써 냉소적이 되려고 노력했다.
user2394284

재귀 globbing은 zsh90 년대 초반에 시작되었습니다 (필요 **/*하지만). fishbash의 동등한 기능의 이전 구현과 마찬가지로 디렉토리 트리를 내릴 때 symlink를 따릅니다. 구현 간의 차이점에 대해서는 ls *, ls ** 및 ls ***의 결과를 참조하십시오 .
Stéphane Chazelas

1

find의 출력을 반복하는 것은 나쁜 습관이 아닙니다. 나쁜 습관 (이 상황 및 모든 상황에서)은 입력이 특정 형식 인지 알기 (테스트 및 확인) 대신 특정 형식 이라고 가정 합니다 .

tldr / cbf : find | parallel stuff

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.