Nomes de arquivos com espaços quebrando para loop, comando find

34

Eu tenho um script que pesquisa todos os arquivos em várias subpastas e arquivos para tar. Meu script é

for FILE in `find . -type f  -name '*.*'`
  do
if [[ ! -f archive.tar ]]; then

  tar -cpf archive.tar $FILE
else 
  tar -upf archive.tar $FILE 
fi
done

O comando find me fornece a seguinte saída

find . -type f  -iname '*.*'
./F1/F1-2013-03-19 160413.csv
./F1/F1-2013-03-19 164411.csv
./F1-FAILED/F2/F1-2013-03-19 154412.csv
./F1-FAILED/F3/F1-2011-10-02 212910.csv
./F1-ARCHIVE/F1-2012-06-30 004408.csv
./F1-ARCHIVE/F1-2012-05-08 190408.csv

Mas a variável FILE armazena apenas a primeira parte do caminho ./F1/F1-2013-03-19 e, em seguida, a próxima parte 160413.csv .

Eu tentei usar readcom um loop while,

while read `find . -type f  -iname '*.*'`;   do ls $REPLY; done

mas eu recebo o seguinte erro

bash: read: `./F1/F1-2013-03-19': not a valid identifier

Alguém pode sugerir uma maneira alternativa?

Atualizar

Conforme sugerido nas respostas abaixo, atualizei os scripts

#!/bin/bash

INPUT_DIR=/usr/local/F1
cd $INPUT_DIR
for FILE in "$(find  . -type f -iname '*.*')"
do
archive=archive.tar

        if [ -f $archive ]; then
        tar uvf $archive "$FILE"
        else
        tar -cvf $archive "$FILE"
        fi
done

A saída que eu recebo é

./test.sh
tar: ./F1/F1-2013-03-19 160413.csv\n./F1/F1-2013-03-19 164411.csv\n./F1/F1-2013-03-19 153413.csv\n./F1/F1-2013-03-19 154412.csv\n./F1/F1-2012-09-10 113409.csv\n./F1/F1-2013-03-19 152411.csv\n./.tar\n./F1-FAILED/F3/F1-2013-03-19 154412.csv\n./F1-FAILED/F3/F1-2013-03-19 170411.csv\n./F1-FAILED/F3/F1-2012-09-10 113409.csv\n./F1-FAILED/F2/F1-2011-10-03 113911.csv\n./F1-FAILED/F2/F1-2011-10-02 165908.csv\n./F1-FAILED/F2/F1-2011-10-02 212910.csv\n./F1-ARCHIVE/F1-2012-06-30 004408.csv\n./F1-ARCHIVE/F1-2011-08-17 133905.csv\n./F1-ARCHIVE/F1-2012-10-21 154410.csv\n./F1-ARCHIVE/F1-2012-05-08 190408.csv: Cannot stat: No such file or directory
tar: Exiting with failure status due to previous errors
Ubuntuser
fonte
4
Parece que você deve definir IFS=$'\n'antes do `loop for para torná-la analisar por cada linha
kiri

Respostas:

36

Usar forcom finda abordagem errada aqui, veja, por exemplo, este artigo sobre a lata de worms que você está abrindo.

A abordagem recomendada é usar find, whilee readcomo descrito aqui . Abaixo está um exemplo que deve funcionar para você:

find . -type f -name '*.*' -print0 | 
while IFS= read -r -d '' file; do
    printf '%s\n' "$file"
done

Dessa forma, você delimita os nomes de arquivos com \0caracteres nulos ( ), isso significa que variações no espaço e outros caracteres especiais não causarão problemas.

Para atualizar um arquivo morto com os arquivos findlocalizados, você pode transmitir sua saída diretamente para tar:

find . -type f -name '*.*' -printf '%p\0' | 
tar --null -uf archive.tar -T -

Observe que você não precisa diferenciar entre se o arquivo existe ou não, taro manipulará com sensatez. Observe também o uso -printfaqui para evitar a inclusão do ./bit no arquivo morto.

Thor
fonte
Obrigado, quase funciona. A única coisa é arquivar o ./as tar. ./.tar tar: ./archive.tar: file is the archive; not dumped
precisa saber é o seguinte
@Ubuntuser Você pode adicionar uma verificação simples para verif [[ "$FILE" == "./" ]]; then continue
kiri
@ Ubuntuser: Você pode evitar um ./pouco com a -printfresposta atualizada. No entanto, não deve fazer diferença se estiver incluído ou não, pois apenas faz referência ao diretório atual. Também incluí uma find/tarcombinação alternativa que você pode querer usar.
Thor
Para aqueles que desejam sortos resultados antes de iterá-los, será necessário sort -zo separador nulo.
Adambean
13

Tente citar o forloop assim:

for FILE in "`find . -type f  -name '*.*'`"   # note the quotation marks

Sem aspas, o bash não lida bem com espaços e novas linhas ( \n) ...

Tente também configurar

IFS=$'\n'
kiri
fonte
1
+1 por US $ IFS. Isso descreve o caractere separador.
Raio
1
Esta é a solução que funcionou para mim. Eu estava usando commpara comparar listagens de arquivos classificados e os nomes de arquivos tinham espaços, apesar de citar variáveis ​​que não estavam funcionando. Então vi cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html e a solução de definir $ IFS com IFS = $ (echo -en "\ n \ b") funcionou para mim.
Pbhj 01/07/2015
Além das aspas, elegante, simples, bonito - obrigado!
Big Rich
8

Isso funciona e é mais simples:

find . -name '<pattern>' | while read LINE; do echo "$LINE" ; done

Agradecemos a Rupa ( https://github.com/rupa/z ) por esta resposta.

ShawnMilo
fonte
4

Além da citação adequada, você pode dizer findpara usar um separador NULL e, em seguida, ler e processar os resultados em um whileloop

while read -rd $'\0' file; do
    something with "$file"
done < <(find  . -type f -name '*.*' -print0)

Isso deve lidar com todos os nomes de arquivos compatíveis com POSIX - consulte man find

   -print0
          True; print the full file name on the standard output, followed by a null character (instead of the newline character that  -print  uses).   This  allows  file
          names that contain newlines or other types of white space to be correctly interpreted by programs that process the find output.  This option corresponds to the
          -0 option of xargs.
chave de aço
fonte
Esta é a única solução que funcionou para mim. Obrigado.
Codefreak #
1
find . <find arguments> -print0 | xargs -0 grep <pattern>
user2802945
fonte
1

Fiz algo assim para encontrar arquivos que podem conter espaços.

IFS=$'\n'
for FILE in `/usr/bin/find $DST/shared -name *.nsf | grep -v bookmark.nsf | grep -v names.nsf`; do
    file $FILE | tee -a $LOG
done

Trabalhou como um encanto :)

Scott B
fonte
0

Eu acho que você pode estar melhor usando finda opção -exec.

find . -type f -name '*.*' -exec tar -cpf archive.tar {} +

O Find executa o comando usando uma chamada do sistema, para que os espaços e as novas linhas sejam preservados (em vez de um canal, o que exigiria a citação de caracteres especiais). Observe que "tar -c" funciona se o arquivo já existe ou não, e que (pelo menos com o bash) nem {} nem + precisam ser citados.

Drake Clarris
fonte
-1

Como o minerz029 sugeriu, é necessário citar a expansão do findcomando. Você também precisa citar todas as substituições de $FILEno seu loop.

for FILE in "$(find . -type f  -name '*.*')"
do
    if [ ! -f archive.tar ]; then
        tar -cpf archive.tar "$FILE"
    else 
        tar -upf archive.tar "$FILE" 
    fi
done

Observe que a $()sintaxe deve ser preferida ao uso de backticks; veja esta pergunta de U&L . Também removi a [[palavra - chave e a substitui pelo [comando porque é POSIX.

Joseph R.
fonte
Sobre [[e [, parece que [[é mais recente e suporta mais recursos, como globbing e correspondência de expressões regulares. [[é apenas em bash, no entanto, nãosh
kiri
@ minerz029 Sim. É isso que eu estou dizendo. Não sei o que você quer dizer com [[apoios globbing. De acordo com o wiki de Greg , nenhum globbing ocorre dentro [[.
Joseph R.
Tente [ "ab" == a? ] && echo "true"então[[ "ab" == a? ]] && echo "true"
kiri
@ minerz029 Isso não é exagero. Estas são expressões regulares (livremente interpretadas). Isso não é um glob porque a*significa "a seguido por qualquer número de caracteres" em vez de "todos os arquivos cujos nomes começam com ae têm qualquer número de caracteres posteriormente". Tente [ ab = a* ] && echo true vs. [[ ab == a* ]] && echo true.
10133 Joseph R.
Ah, bem, [[ainda faz expressões regulares, enquanto [não. Deve ter ficado confuso
kiri