Escapando problema com o comando find

2

Eu preciso encontrar tudo em um diretório, excluindo determinados subdiretórios e arquivos. Meu script precisa chamar isso como uma função:

function findStuff() {
  # define exclusions
  ignore_dirs=("$1" "*foo*")                        # exclude base dir
  ignore_files=("one.txt" "two.txt" "*three*.txt")
  # build patterns for find command
  dir_pattern=""
  file_pattern=""
  for i in "${ignore_dirs[@]}"; do dir_pattern=$dir_pattern" ! -path \"$i\""; done
  for i in "${ignore_files[@]}"; do file_pattern=$file_pattern" ! -name \"$i\""; done
  # find
  find "$1 $dir_pattern $file_pattern"
  # now do other stuff with the results...
}

findStuff /some/base/dir

Mas isso me dá um erro No such file or directory .

Então, eu queria ver qual era o comando e tentei echo find "$1 $dir_pattern $file_pattern" e colei isso na linha de comando e funcionou. Então eu colei isso no script e corri, e também funcionou!

Então eu acho que está falhando por causa de algum problema de escape. O que eu fiz de errado?

    
por lonix 12.09.2018 / 13:12

1 resposta

8

find usará os primeiros argumentos (até o primeiro argumento que começa com - ou ! ou ( ) que obtém como os caminhos de nível superior a serem pesquisados. Você está dando find um único argumento quando você o chama na sua função, a string $1 $dir_pattern $file_pattern (com as variáveis expandidas). Este caminho não foi encontrado.

Você também inclui aspas duplas literais nos argumentos que pretende dar a find . É feita uma cotação dupla para evitar que o shell expanda padrões glob e divida em espaços em branco (ou qualquer que seja a variável IFS ), mas se você usar, por exemplo, ! -name \"thing\" , então as aspas duplas seriam parte do padrão que find usa para comparar com os nomes dos arquivos.

Use matrizes e cite os argumentos separados para find corretamente:

myfind () {
  local ignore_paths=( "$1" "*foo*" )
  local ignore_names=( "one.txt" "two.txt" "*three*.txt" )

  local path_args=()
  for string in "${ignore_paths[@]}"; do
      path_args+=( ! -path "$string" )
  done

  local name_args=()
  for string in "${ignore_names[@]}"; do
      name_args+=( ! -name "$string" )
  done

  find "$1" "${path_args[@]}" "${name_args[@]}"
}

Sempre que adicionamos path_args e name_args acima, adicionamos três elementos à lista, ! , -path ou -name e "$string" . Ao expandir "${path_args[@]}" e "${name_args[@]}" (observe as aspas duplas), os elementos serão citados individualmente.

Implementação equivalente adequada para /bin/sh :

myfind () (
    topdir=$1

    set --

    # paths to ignore
    for string in "$topdir" "*foo*"; do
        set -- "$@" ! -path "$string"
    done

    # names to ignore
    for string in "one.txt" "two.txt" "*three*.txt"; do
        set -- "$@" ! -name "$string"
    done

    find "$topdir" "$@"
)

No shell sh , temos apenas uma única matriz disponível para nós, que é a lista de parâmetros posicionais, $@ , portanto, coletamos nossas opções find . A solução bash -specific também pode ser escrita para usar uma única matriz, obviamente, e a variação sh seria executada em bash também.

E, por último, a saída do seu echo test não é uma representação precisa do comando que teria sido executado pela sua função.

Considere isso:

cat "my file name"

que executa cat em algo chamado my file name e

echo cat "my file name"

que produz a string cat my file name . Isso se deve ao fato de que o shell remove aspas ao redor das strings antes de executar o comando. Executando o comando , cat procuraria por três arquivos, não um.

Seu comando funcionou bem quando você copiou e colou no shell, porque você incluiu as aspas duplas literais na string que foi gerada por echo (escapando delas), mas esse não era o comando real executado pelo seu função.

    
por 12.09.2018 / 13:54