o parâmetro de aspas duplas (representando nome do arquivo) não ajuda se os espaços estiverem contidos

1

Quando iterar sobre alguns nomes de arquivos, a duplicação do parâmetro não impede que o nome do arquivo seja quebrado:

$ mkdir temp
$ cd temp/
$ touch a\ b
$ for f in $(find .) ; do echo "$f" ; done
.
./a
b
    
por Marcus Junius Brutus 05.12.2013 / 13:55

2 respostas

1

A diretiva for f in $(find .) divide a lista de elementos para iterar pelo espaço. Em um shell moderno, como zsh, sinalizadores de expansão poderiam ser usados.

for f in "${(@f)$(find .)}" ; do echo "$f" ; done

Permanecendo com bash , podemos trabalhar em torno deste como mencionado em outra pergunta modificando o separador de entrada, o caractere usado para dividir elementos, temporariamente, ao atribuir os resultados de find a uma variável. Desativar o gobbing pelo tempo evita a expansão de caracteres curinga, como * neles.

set -f  # Disable globbing.
IFS=$(echo -e '
find . -print0 | xargs -0 -n1 echo
') files=( $(find . -print0) ) # Read newline separated files into an array. for f in ${files[@]}; do echo $f; done set +f # Reenable globbing.

Mas isso apenas muda o problema, agora ele não funcionará quando a nova linha estiver em qualquer nome de arquivo. Há apenas uma coisa proibida em provavelmente todos os sistemas de arquivos, que é o caractere nulo. Mas as variáveis no bash não podem conter isso, então não podemos atribuí-lo a IFS .

Como alternativa, xargs podem ser usados para passar os caminhos de find para outro programa. Usando o comando -print0 para find e a opção -0 para xargs , eles usam o caractere nulo como um separador. Adicionar -n1 à chamada xargs manipula um nome de arquivo por vez.

for f in "${(@f)$(find .)}" ; do echo "$f" ; done
    
por 05.12.2013 / 14:02
1

Você citou duas vezes a substituição de variável no argumento para echo , mas não a substituição de comando na lista de iterações do loop for . É aí que a divisão no espaço em branco e outra maldade está acontecendo.

Não existe uma maneira totalmente confiável de analisar a saída de find , porque você não pode dizer se uma nova linha é parte de um nome de arquivo ou um separador.

Se você puder assumir que os nomes dos arquivos não contêm novas linhas, organize a saída de find restringindo os caracteres do separador de campo apenas ao caractere de nova linha (para proteger espaços e guias) e desativando globbing (para proteger \[?* ).

IFS='
'; set +f
for f in $(find .); do
  unset IFS; set +f
  echo "$f"
done
unset IFS; set +f

Existem maneiras melhores de fazer isso, no entanto. A maneira mais fácil de usar find de forma confiável é fazer com que ele execute o comando, em vez de analisar sua saída. Isso também é potencialmente mais rápido, pois os arquivos têm uma boa chance de serem processados enquanto seus metadados ainda estão no cache.

find . -exec echo {} \;

Se você precisar de um comando shell, invoque um shell explicitamente. Cuidado com aspas - passe o nome do arquivo como um argumento para o shell, não tente interpolá-lo no comando shell.

find . -exec sh -c 'echo "$0"' {} \;

Em vez de invocar uma instância de shell por arquivo, você pode agrupar invocações de shell e executar um loop sobre vários arquivos. O comando find faz o agrupamento e garante que não ultrapasse o limite de comprimento da linha de comandos. Não esqueça de passar um argumento fictício como $0 para que os arquivos sejam $1 e assim por diante.

find . -exec sh -c 'for x; do echo "$x"; done' _ {} +

Se você estiver usando ksh93, bash ou zsh, poderá usar o recurso de globalização recursiva: **/PATTERN procura um padrão curinga em um diretório e seus subdiretórios recursivamente. No ksh93, você precisa executar set -o globstar primeiro. No bash, você precisa executar shopt -s globstar primeiro e tenha cuidado, pois ** também recorre dentro de links simbólicos para diretórios.

for f in **/*; do
  echo "$f"
done

O Zsh também tem os qualificadores que incluem a maioria dos usos de find . Outras conchas não têm tal coisa.

    
por 06.12.2013 / 02:02