Executando a função definida pelo usuário em uma chamada find -exec

21

Estou no Solaris 10 e testei o seguinte com ksh (88), bash (3.00) e zsh (4.2.1).

O seguinte código não produz nenhum resultado:

function foo {
    echo "Hello World"
}

find somedir -exec foo \;

A localização corresponde a vários arquivos (como mostrado substituindo -exec ... por -print ), e a função funciona perfeitamente quando chamada fora da chamada find .

Veja o que a página man find diz sobre -exec :

 -exec command       True if the executed command  returns  a
                     zero  value  as  exit status. The end of
                     command must be punctuated by an escaped
                     semicolon (;).  A command argument {} is
                     replaced by the current pathname. If the
                     last  argument  to  -exec  is {} and you
                     specify + rather than the semicolon (;),
                     the command is invoked fewer times, with
                     {} replaced by groups of  pathnames.  If
                     any  invocation of the command returns a
                     non-zero  value  as  exit  status,  find
                     returns a non-zero exit status.

Eu provavelmente poderia fugir fazendo algo assim:

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

Mas tenho medo de lidar com questões de separação de campos.

É possível chamar uma função de shell (definida no mesmo script, não vamos nos preocupar com problemas de escopo) de uma chamada find ... -exec ... ?

Eu tentei com /usr/bin/find e /bin/find e obtive o mesmo resultado.

    
por rahmu 12.10.2012 / 15:30

4 respostas

18

Um function é local para um shell, então você precisaria de find -exec para gerar um shell e ter essa função definida nesse shell antes de poder usá-lo. Algo como:

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

bash permite exportar funções através do ambiente com export -f , assim você pode fazer (no bash):

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

ksh88 tem typeset -fx para exportar a função (não pelo ambiente), mas isso só pode ser usado pelo she-bang menos scripts executados por ksh , portanto não com ksh -c .

Outra opção é fazer:

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

Ou seja, use typeset -f para despejar a definição da função foo dentro do script embutido. Note que se foo usar outras funções, você também precisará descarregá-las.

    
por 12.10.2012 / 16:08
5

Isso nem sempre é aplicável, mas quando é, é uma solução simples. Defina a opção globstar ( set -o globstar em ksh93, shopt -s globstar em bash ≥4; ela está ativada por padrão em zsh). Em seguida, use **/ para corresponder ao diretório atual e seus subdiretórios recursivamente.

Por exemplo, em vez de find . -name '*.txt' -exec somecommand {} \; , você pode executar

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

Em vez de find . -type d -exec somecommand {} \; , você pode executar

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

Em vez de find . -newer somefile -exec somecommand {} \; , você pode executar

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

Quando **/ não funciona para você (porque seu shell não o possui, ou porque você precisa de uma opção find que não tenha um análogo de shell), define a função no argumento find -exec .

    
por 15.10.2012 / 01:10
4

Use \ 0 como um delimitador e leia os nomes dos arquivos no processo atual a partir de um comando gerado, da seguinte forma:

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

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

O que está acontecendo aqui:

  • read -d '' lê até o próximo byte de 0, assim você não precisa se preocupar com caracteres estranhos em nomes de arquivos.
  • da mesma forma, -print0 usa \ 0 para finalizar cada nome de arquivo gerado em vez de \ n.
  • cmd2 < <(cmd1) é o mesmo que cmd1 | cmd2 , exceto que cmd2 é executado no shell principal e não em um subshell.
  • a chamada para foo é redirecionada de /dev/null para garantir que ela não seja acidentalmente lida do canal.
  • $filename é citado para que o shell não tente dividir um nome de arquivo que contenha espaço em branco.

Agora, read -d e <(...) estão em zsh, bash e ksh 93u, mas não tenho certeza sobre as versões anteriores do ksh.

    
por 15.10.2012 / 11:42
4

Se você quiser que um processo filho, gerado a partir do seu script, use uma função de shell predefinida, será necessário exportá-lo com export -f <function>

NOTA: export -f é bash específico

já que apenas um shell pode executar as funções shell :

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

EDIT: essencialmente, seu script deve se parecer com isso:

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