Bash variáveis ​​com espaço (diretórios)

6

Estou tentando fazer eco de todos os arquivos /foo, preservando os espaços nos diretórios listados. Atualmente, um diretório intitulado "bar1 bar2"ecoará como /path/to/foo/bar1/então /bar2em file.txttodas as novas linhas. Eu preciso ecoar /path/to/foo/bar1 bar2/file.txtou /path/to/foo/bar1\ bar2/file.txt.

for file in $(find /path/to/foo/ -type f); do
  if [[ ... ]]; then
    echo "${file}"
  fi
done

Também tentei:

for file in $(find /path/to/foo/ -type f | sed 's/ /\ /g'); do
  if [[ ... ]]; then
    echo "${file}"
    echo "$file" # variations i've tried
    echo $file   # variations i've tried
    echo ${file}
    otherCommand "${file}"
  fi
done
super-frustrado
fonte
2
Aqui está uma regra para o Linux Life: Se você tiver problemas para fazê-lo no Bash, pare de fazê-lo no Bash e use outra coisa ... O Bash é uma linguagem terrível. Use Perl ou algo assim.
Ben Ben
Veja também: superuser.com/a/117600/334516
muru

Respostas:

15

O problema não é com $file, é com o seu uso de $(find…).

As variáveis ​​regulares e as $(…)substituições de processo estão sujeitas exatamente ao mesmo tipo de divisão de palavras. Se você colocar $(find…)aspas duplas, obterá uma única palavra com toda a saída. Caso contrário, ele será dividido em qualquer espaço em branco - não apenas em novas linhas ou em algum outro limite mágico que você provavelmente esperava.

Uma parte importante do exposto acima é que as escapes de barra invertida não são processadas ao expandir a variável. É apenas dividido em espaços e é isso.

(Em outras palavras, citar $filenão ajudou porque nunca teve o valor correto em primeiro lugar!)

Se você deseja processar todos os arquivos recursivamente, suas opções são:

  1. Leia a saída de findoutra maneira:

    find  -print | while read -r file; do
        echo "Got $file"
    done

    (Observação: os nomes dos arquivos também podem incluir novas linhas tecnicamente , portanto, você pode se proteger contra isso, por mais raro que seja:

    find  -print0 | while read -r -d "" file; do
        echo "Got $file"
    done
  2. Para pequenas quantidades de arquivos, use os curingas estendidos do bash:

    shopt -s globstar
    for file in /path/to/foo/**; do
        echo "Got $file"
    done

Com diretórios grandes, a vantagem find | while readé que ele é transmitido - seu script processará os resultados à medida que forem chegando. Enquanto isso, os $(find)curingas e os curingas precisam coletar tudo na memória primeiro e só então retornar os resultados (possivelmente maciços).

Por outro lado, o uso de um pipe afeta todo o while loop (não apenas o readcomando); portanto, se você deseja executar algo que deseje ler a entrada do teclado, é necessário fornecer manualmente o stdin original, por exemplo vim "$file" < /dev/tty.

gravidade
fonte
1
Uau; seu tempo de resposta foi incrível. Muito obrigado.
super-frustrado
@ super-frustrado Então você deve marcá-lo "aceito"
xenoid
Desculpas. Não uso pilhas com frequência. Marcado.
super-frustrado
3
É melhor usar ... | IFS= read ...; sem isso, readremoverá os espaços em branco iniciais e finais (o que é raro, mas perfeitamente legal em nomes de arquivos). Além disso, observe que o pipe faz com que o loop seja executado no subshell s, portanto, quaisquer variáveis ​​definidas nele não estarão disponíveis fora do loop. Consulte BashFAQ # 20: "Como posso encontrar e lidar com segurança com nomes de arquivos contendo novas linhas, espaços ou ambos?" para mais dicas e truques.
Gordon Davisson
1
Os espaços à frente devem ser bastante óbvios se você fizer lsou find -print.
G-Man