find -exec 호출에서 사용자 정의 함수 실행

find -exec 호출에서 사용자 정의 함수 실행

저는 Solaris 10을 사용하고 있으며 ksh(88), bash(3.00) 및 zsh(4.2.1)를 사용하여 다음을 테스트했습니다.

다음 코드는 결과를 생성하지 않습니다.

function foo {
    echo "Hello World"
}

find somedir -exec foo \;

find는 여러 파일과 일치하며(대체로 표시됨 -exec ...) -print함수는 find외부에서 호출될 때 완벽하게 작동합니다.

man find이 페이지의 내용은 다음과 같습니다 -exec.

-exec 명령 실행된 명령이 다음을 반환하면 참입니다.
                     0 값은 종료 상태로 사용됩니다. 끝
                     명령은 이스케이프 문자로 구분되어야 합니다.
                     세미콜론(;). 명령 매개변수 {}는
                     현재 경로 이름으로 바꾸십시오. 만약에
                     -exec의 마지막 인수는 {}이며
                     세미콜론(;) 대신 +를 지정하세요.
                     명령이 덜 자주 호출됩니다.
                     {}는 경로 이름 그룹으로 대체됩니다. 만약에
                     이 명령을 호출하면 반환됩니다.
                     종료 상태로 0이 아닌 값을 찾으십시오.
                     0이 아닌 종료 상태를 반환합니다.

아마도 다음과 같이 할 수 있을 것입니다.

for f in $(find somedir); do
    foo
done

하지만 필드 구분 기호 문제를 다루는 것이 두렵습니다.

호출에서 쉘 함수를 호출하는 것이 가능합니까(동일한 스크립트에 정의되어 있으므로 범위 문제에 대해 걱정할 필요가 없습니다) find ... -exec ...?

나는 둘 다 시도했고 /usr/bin/find같은 /bin/find결과를 얻었습니다.

답변1

A function는 셸 고유의 함수이므로 find -exec사용하기 전에 셸을 생성하고 해당 셸에서 함수를 정의해야 합니다. 그것은 다음과 같습니다:

find ... -exec ksh -c '
  function foo {
    echo blan: "$@"
  }
  foo "$@"' ksh {} +

bash환경을 통해 기능을 내보낼 수 있으므로 export -f(bash에서) 다음을 수행할 수 있습니다.

foo() { ...; }
export -f foo
find ... -exec bash -c 'foo "$@"' bash {} +

ksh88해당 함수는 (환경을 통하지 않고) 내 보내야 typeset -fx하지만 실행 중인 she-bang less 스크립트에서만 사용할 수 있으므로 ksh사용할 수 없습니다 ksh -c.

또 다른 옵션은 다음을 수행하는 것입니다.

find ... -exec ksh -c "
  $(typeset -f foo)"'
  foo "$@"' ksh {} +

즉, 인라인 스크립트에서 함수 typeset -f정의를 덤프하는 데 사용됩니다. 다른 기능을 사용하는 경우 해당 기능도 덤프해야 합니다.foofoo

ps -f또는 명령줄이 아닌 환경 변수를 통해 함수 정의를 전달할 수 있습니다(예: 출력에 표시됨).

FUNCDEFS=$(typeset -f foo) find ... -exec ksh -c '
  eval "$FUNCDEFS" &&
    unset -v FUNCDEFS &&
    foo "$@"' ksh {} +

( unset -v FUNCDEFS이 기능으로 실행되는 명령의 환경을 오염시키지 않기 위해 foo(있는 경우))

답변2

\0을 구분 기호로 사용하고 생성된 명령의 파일 이름을 다음과 같이 현재 프로세스로 읽어옵니다.

foo() {
  printf "Hello {%s}\n" "$1"
}

while IFS= read -d '' -r filename; do
  foo "${filename}" </dev/null
done < <(find . -maxdepth 2 -type f -print0)

여기서 무슨 일이 일어나고 있나요?

  • read -d ''다음 \0바이트까지 읽으므로 파일 이름의 이상한 문자에 대해 걱정할 필요가 없습니다.
  • 마찬가지로 -print0생성된 각 파일 이름을 종료하려면 \n 대신 \0을 사용합니다.
  • 옵션 -r(원시)은 파일 이름에서 백슬래시가 이스케이프되는 것을 방지합니다.
  • cmd2 < <(cmd1)cmd2 와 동일 cmd1 | cmd2하지만 cmd2는 하위 쉘 대신 기본 쉘에서 실행됩니다.
  • foo에 대한 호출은 /dev/null실수로 파이프에서 읽히지 않도록 리디렉션됩니다.
  • $filename인용되어 있으므로 쉘은 공백이 포함된 파일 이름을 분할하려고 시도하지 않습니다.
  • IFS=Bash가 파일 이름에서 후행 공백을 제거하지 못하도록 방지합니다(감사합니다다니엘 스메드가드 부스이 수정을 위해)

이제 zsh, bash 및 ksh 93u read -d에서는 <(...)이전 ksh 버전이 확실하지 않습니다.

답변3

사전 정의된 셸 기능을 사용하기 위해 스크립트에서 생성된 하위 프로세스를 원하는 경우 이를 내보내야 합니다.export -f <function>

참고: export -fbash에만 해당

단 하나뿐이니까껍데기실행할 수 있습니다껍데기기능:

find / -exec /bin/bash -c 'function "$1"' bash {} \;

편집: 기본적으로 스크립트는 다음과 같아야 합니다.

#!/bin/bash
function foo() { do something; }
export -f foo
find somedir -exec /bin/bash -c 'foo "$0"' {} \;

답변4

항상 적용되는 것은 아니지만 적용되는 경우 간단한 해결책입니다. 이 globstar옵션을 설정합니다( set -o globstarksh93에서는 shopt -s globstarbash ≥4, zsh에서는 기본적으로 활성화됨). 그런 다음 재귀를 사용하여 **/현재 디렉터리와 해당 하위 디렉터리를 일치시킵니다.

예를 들어 find . -name '*.txt' -exec somecommand {} \;다음 대신 실행할 수 있습니다.

for x in **/*.txt; do somecommand "$x"; done

대신 find . -type d -exec somecommand {} \;다음을 실행할 수 있습니다.

for d in **/*/; do somecommand "$d"; done

대신 find . -newer somefile -exec somecommand {} \;다음을 실행할 수 있습니다.

for x in **/*; do
  [[ $x -nt somefile ]] || continue
  somecommand "$x"
done

이것이 작동하지 않는 경우 **/(쉘에 해당 기능이 없거나 find쉘 아날로그가 없는 옵션이 필요하기 때문에),find -exec매개변수에서 함수 정의.

관련 정보