encontrar em um conjunto de dirs que podem não existir

3

Eu preciso invocar um conjunto de "pontos iniciais" que é gerado, mas alguns dos caminhos podem não ser válidos:

paths() {
    #
    # mock version of the generator
    #
    echo /bin
    echo kjsdfhk
    echo /etc
}

find $(paths) -type f name foo.sh

Meu problema é que não sei se o caminho será válido e, se não for, quero ignorá-lo silenciosamente. Mais fácil para mim é agora

paths \
  | while read path;
    do
        test -e "$path" || continue
        find "$path" -type f -name foo.sh
    done

mas isso é caro: ele invoca encontrar para cada caminho válido, e como o código inteiro pode ser chamado de loop interno, eu gostaria de encontrar uma maneira mais eficaz.

Uma solução fácil e muito unix-y seria ter encontrar os "pontos iniciais" de STDIN:

paths \
  | while read path;
    do
        test -e "$path" || continue
    done \
  | find - -type f -name foo.sh

exceto que ... encontrar não suporta isso! :)

Alguma idéia?

Note que os caminhos são fornecidos pelo usuário, então os espaços (e provavelmente outras coisas engraçadas) precisam ser considerados. Também estou apontando para POSIX /bin/sh , mas isso poderia ser sacrificado. (Ah, e afundar STDERR inteiro para /dev/null também não é uma opção ...)

Update: Desculpe, esqueci de mencionar que estou restrito a bash --- provavelmente confundi mais com a nota POSIX. Na verdade, o código está procurando por trechos de código dentro de scripts Bash e agora está principalmente em sh com poucos bashisms que eu possa me livrar na versão futura. Então, se eu pudesse evitar colocar mais bashismos, isso seria legal, mas eu certamente não posso me dar ao luxo de "zhisms", por mais elegantes que sejam - como a resposta de @ stephane (vá votar agora!).

    
por Alois Mahdal 29.05.2015 / 18:24

4 respostas

3

Em zsh , se você tiver os caminhos em uma matriz, como em:

files=($(paths))

(que dividiria a saída de paths no espaço, na guia nova linha ou nul) ou:

files=(${(f)"$(paths)"})

para dividir em linhas, você pode fazer:

find $^files(N) -type f -name foo.sh

Ou se você quiser restringir a diretórios:

find $^files(/N) -type f -name foo.sh

Agora, se nenhum desses arquivos existir, você poderá acabar executando:

find -type f -name foo.sh

Que com algumas implementações de find como o GNU significa procurar no diretório atual. Para evitar isso, você poderia fazer:

dirs=($^files(/N))
(($#dirs)) && find $dirs -type f -name foo.sh

Ou:

setopt cshnullglob
find $^files(/) -type f -name foo.sh

Agora, com zsh , não há necessidade real de find , você poderia simplesmente fazer:

files=($^files/**/foo.sh(.N))

Isso também teria o benefício de funcionar mesmo se esses arquivos forem iguais a ! ou -name , o que find iria sufocar.

Há uma diferença no caso em que esses arquivos são links simbólicos para os diretórios ( find não procuraria por arquivos neles, enquanto zsh seria (o que pode realmente ser o que você deseja)).

    
por 29.05.2015 / 19:24
1

Como você está usando o bash, armazene a lista de caminhos em um array . Iterar sobre o array para construir uma matriz de caminhos existentes. Você precisa de um caso especial se a matriz resultante estiver vazia, já que find iria errar ou atravessar o diretório atual.

Para ser totalmente robusto, assegure-se de que nenhum dos argumentos do caminho comece com um - , que find interpretaria como uma opção ou primária.

paths=(/some/where around/here -print 'one  with
odd spaces')
existing_paths=()
for x in "${paths[@]}"; do
  if [ -e "$x" ]; then
    if [[ "$x" = -* ]]; then x="./$x";; fi
    existing_paths+=("$x")
  fi
done
if [[ ${#existing_paths[@]} -ne 0 ]]; then
  find "${existing_paths[@]}" -type f -name foo.sh
fi
    
por 30.05.2015 / 00:24
0

Você pode testar a existência do caminho usando python, ecoar os caminhos encontrados com o NUL separado e alimentá-los em xargs para passar para find , a menos que o comprimento da saída do python exceda o comprimento máximo do argumento deve invocar encontrar apenas uma vez:

python -c 'import os, sys;  sys.stdout.write("
python -c 'import os, sys;  sys.stdout.write("%pre%".join([x for x in sys.argv[1:] if os.path.exists(x)]) + "%pre%")' a\ b xyz abc| xargs -0 --no-run-if-empty find
".join([x for x in sys.argv[1:] if os.path.exists(x)]) + "%pre%")' a\ b xyz abc| xargs -0 --no-run-if-empty find

A parte em python (o único argumento citado para -c):

  • importa os módulos necessários os e sys usados no comando
  • analisa os argumentos a b , xyz e abc usando for x in sys.argv[1:]
  • só coloca na lista se o caminho existir: [x ... if os.path.exists(x)]
  • junta-se à lista com o NUL e anexa um NUL e escreve para o stdout: %código%

Se você tiver um diretório vazio e fizer sys.stdout.write("touch a\ b xyz".join[...] + "abc") , verá que os dois primeiros argumentos foram encontrados, mas %code% não é e o find nunca recebe o último caminho.

    
por 29.05.2015 / 19:18
0

se isso funcionar para você

find $(paths) -type f name foo.sh

você pode alterar a função de caminhos para fazer eco apenas de caminhos válidos, como

if [ -d "$path" ]; then echo $path fi

ou se path também pode ser um arquivo

if [ -d "$path" ] || [ -e "$path" ]; then echo $path fi

desta forma, o find será executado apenas uma vez. Se você não pode modificar a função de caminhos, você sempre pode criar outra que irá filtrar todos os caminhos válidos antes de passá-los para encontrar o comando, portanto, nenhum caminho inválido será passado.

    
por 29.05.2015 / 19:09

Tags