"find"의 -exec 옵션 이해

"find"의 -exec 옵션 이해

나는 끊임없이 구문을 찾고 있음을 발견합니다.

find . -name "FILENAME"  -exec rm {} \;

-exec주로 그 부분이 정확히 어떻게 작동하는지 이해하지 못하기 때문입니다 . 중괄호, 백슬래시 및 세미콜론은 무엇을 의미합니까? 이 구문에 대한 다른 사용 사례가 있습니까?

답변1

이 답변은 다음 부분으로 구분됩니다.

  • 기본 사용법-exec
  • -exec와 함께sh -c
  • 사용-exec ... {} +
  • 사용-execdir

기본 사용법-exec

-exec옵션은 선택적 매개변수가 있는 외부 유틸리티를 인수로 사용하여 실행합니다.

이 문자열이 {}지정된 명령의 어느 위치에나 나타나면 이 문자열의 각 인스턴스는 현재 처리 중인 경로 이름(예: ./some/path/FILENAME)으로 대체됩니다. 대부분의 쉘에서는 이 두 문자를 {}따옴표로 묶을 필요가 없습니다.

명령 이 끝나는 위치를 알기 위해서는 명령이 for로 ;끝나야 합니다(그 뒤에 더 많은 옵션이 있을 수 있기 때문입니다). 셸로부터 find보호하려면 or 로 인용해야 합니다 . 그렇지 않으면 셸에서 이를 명령의 끝으로 처리합니다 .;\;';'find

예( \처음 두 줄의 끝에 연속 전용 줄):

find . -type f -name '*.txt'      \
   -exec grep -q 'hello' {} ';'   \
   -exec cat {} ';'

그러면 현재 디렉토리 안이나 아래의 -type f패턴과 이름이 일치하는 모든 일반 파일()을 찾습니다. 그런 다음 해당 문자열이 발견된 파일에 나타나는지 *.txt테스트합니다 (출력은 없고 종료 상태만 생성). 이 문자열이 포함된 파일의 경우 파일 내용이 터미널에 출력됩니다.hellogrep -qcat

각각은 및 와 마찬가지로 -exec발견된 경로 이름에 대한 "테스트"와 유사합니다 . 명령이 종료 상태 0("성공"을 나타냄)을 반환하면 명령의 다음 부분이 고려되고, 그렇지 않으면 명령이 다음 경로 이름으로 계속됩니다. 이는 위의 예에서 string 을 포함하는 파일을 찾는 데 사용되지만 다른 모든 파일은 무시됩니다.find-type-namefindfindhello

위의 예는 가장 일반적인 두 가지 사용 사례를 보여줍니다 -exec.

  1. 검색을 더욱 제한하기 위한 테스트입니다.
  2. 발견된 경로 이름에 대해 일부 작업을 수행합니다(일반적으로 명령 끝에서 반드시 그런 것은 아님 find).

-exec와 함께sh -c

실행할 수 있는 명령은 -exec선택적 매개변수가 있는 외부 유틸리티로 제한됩니다. 쉘 내장, 함수, 조건문, 파이프, 리디렉션 등을 직접 사용하는 것은 서브쉘과 같은 것으로 -exec래핑되지 않는 한 불가능합니다 .sh -c

bash기능이 필요한 경우 bash -c대신 를 사용하세요 sh -c.

sh -c/bin/sh명령줄에 제공된 스크립트를 사용하여 실행한 다음 스크립트에 대한 선택적 명령줄 인수를 사용합니다.

sh -c다음 없이 단독으로 사용되는 간단한 예 find:

sh -c 'echo  "You gave me $1, thanks!"' sh "apples"

이는 서브쉘 스크립트에 두 개의 매개변수를 전달합니다. 이는 스크립트에 배치되어 사용 $0됩니다 .$1

  1. sh. 이는 스크립트 내에서 사용할 수 $0있으며 내부 쉘이 오류 메시지를 출력하는 경우 이 문자열을 접두어로 사용합니다.

  2. 이 매개변수는 스크립트에서 apples사용할 수 $1있으며, 더 많은 매개변수가 있는 경우 $2등 으로 사용할 수 있습니다. $3목록에서도 사용할 수 있습니다 ( 목록에 포함되지 않은 항목 "$@"제외 ).$0"$@"

이는 .와 함께 사용하면 -exec발견된 경로명에 대해 작동하는 임의로 복잡한 스크립트를 만들 수 있으므로 매우 유용합니다 find.

예: 특정 파일 이름 접미사가 있는 모든 일반 파일을 찾아 해당 파일 이름 접미사를 다른 접미사로 변경합니다. 여기서 접미사는 변수에 저장됩니다.

from=text  #  Find files that have names like something.text
to=txt     #  Change the .text suffix to .txt

find . -type f -name "*.$from" -exec sh -c 'mv "$3" "${3%.$1}.$2"' sh "$from" "$to" {} ';'

내부 스크립트에서는 $1string text, $2string txt, 그리고 우리에게 발견된 모든 경로 이름이 될 것 $3입니다 . find매개변수 확장은 경로 이름을 가져와서 ${3%.$1}접미사를 제거합니다..text

또는 dirname/를 사용하세요 basename:

find . -type f -name "*.$from" -exec sh -c '
    mv "$3" "$(dirname "$3")/$(basename "$3" ".$1").$2"' sh "$from" "$to" {} ';'

또는 내부 스크립트에 변수를 추가합니다.

find . -type f -name "*.$from" -exec sh -c '
    from=$1; to=$2; pathname=$3
    mv "$pathname" "$(dirname "$pathname")/$(basename "$pathname" ".$from").$to"' sh "$from" "$to" {} ';'

마지막 변형에서 서브셸의 변수는 외부 스크립트의 동일한 이름을 가진 변수 from와 다릅니다.to

-exec위의 내용은 에서 복잡한 스크립트를 호출하는 올바른 방법 입니다 find. find예를 들어 루프에 사용됩니다.

for pathname in $( find ... ); do

오류가 발생하기 쉽고 우아하지 않습니다(개인 의견). 파일 이름을 공백으로 분할하고 파일 이름 와일드카드를 호출하며 find루프의 첫 번째 반복을 실행하기 전에 쉘이 전체 결과를 확장하도록 강제합니다.

또한보십시오:


사용-exec ... {} +

;끝에 있는 것은 로 대체될 수 있습니다 +. 이렇게 하면 find찾은 각 경로 이름에 대해 한 번이 아니라 가능한 한 많은 인수(찾은 경로 이름)를 사용하여 주어진 명령이 실행됩니다. 제대로 작동 하려면 문자열이 {} 먼저 나타나야 합니다.+.

find . -type f -name '*.txt' \
   -exec grep -q 'hello' {} ';' \
   -exec cat {} +

여기서는 find생성된 경로명을 모아 cat최대한 많은 경로를 한번에 실행한다.

find . -type f -name "*.txt" \
   -exec grep -q "hello" {} ';' \
   -exec mv -t /tmp/files_with_hello/ {} +

다시 말하지만, 이 작업은 mv가능한 한 적은 횟수로 실행됩니다. 마지막 예 mv에서는 coreutils(이 옵션을 지원함 -t)에 GNU가 필요합니다.

를 사용하는 것은 -exec sh -c ... {} +임의로 복잡한 스크립트를 통해 경로 이름 집합을 반복하는 효율적인 방법이기도 합니다.

기본 사항은 를 사용할 때와 동일 -exec sh -c ... {} ';'하지만 이제 스크립트에는 더 긴 매개변수 목록이 필요합니다. "$@"스크립트 내부를 반복 하여 반복할 수 있습니다 .

이전 섹션에서 파일 이름 접미사를 변경하는 예:

from=text  #  Find files that have names like something.text
to=txt     #  Change the .text suffix to .txt

find . -type f -name "*.$from" -exec sh -c '
    from=$1; to=$2
    shift 2  # remove the first two arguments from the list
             # because in this case these are *not* pathnames
             # given to us by find
    for pathname do  # or:  for pathname in "$@"; do
        mv "$pathname" "${pathname%.$from}.$to"
    done' sh "$from" "$to" {} +

사용-execdir

또한 -execdir(대부분의 변형으로 구현되지만 find표준 옵션은 아님)

이는 -exec발견된 경로 이름의 디렉터리를 현재 작업 디렉터리로 사용하여 실행되는 특정 셸 명령 과 유사하게 작동하며 {}경로 없이 발견된 경로 이름의 기본 이름을 포함합니다(그러나 GNU는 find여전히 기본 이름 앞에 접두사 를 붙입니다 ./. 반면 BSD는 find그렇지 sfind않습니다. ).

예:

find . -type f -name '*.txt' \
    -execdir mv -- {} 'done-texts/{}.done' \;

이렇게 하면 발견된 각 *.txt파일이 기존 done-texts하위 디렉터리 로 이동됩니다.파일이 발견된 디렉토리와 동일한 디렉토리. .done접미사를 추가하여 파일 이름도 변경됩니다 . , 기본 이름 앞에 . 을 붙이지 않는 구현에서는 --옵션의 끝을 표시해야 합니다. 쉘이 포함된 경우 전체가 포함되지 않은 인수를 묶어야 합니다. 또한 모든 구현이 그곳으로 확장되는 것은 아닙니다(그렇지는 않습니다).find./{}(t)cshfind{}sfind

파일의 새 이름을 구성 -exec하려면 찾은 파일의 기본 이름을 가져와야 하기 때문에 이는 약간 까다로워집니다 . 또한 디렉터리를 올바르게 찾으 {}려면 디렉터리 이름이 필요합니다 .{}done-texts

그것으로 -execdir어떤 일이 더 쉬워집니다.

-exec반대의 해당 작업은 -execdir서브셸을 사용해야 합니다.

find . -type f -name '*.txt' -exec sh -c '
    for name do
        mv "$name" "$( dirname "$name" )/done-texts/$( basename "$name" ).done"
    done' sh {} +

또는,

find . -type f -name '*.txt' -exec sh -c '
    for name do
        mv "$name" "${name%/*}/done-texts/${name##*/}.done"
    done' sh {} +

관련 정보