Nombres de archivo con espacios que se rompen para el bucle, comando encontrar

34

Tengo un script que busca todos los archivos en múltiples subcarpetas y archivos para tar. Mi guion es

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

El comando find me da el siguiente resultado

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

Pero la variable ARCHIVO solo almacena la primera parte de la ruta ./F1/F1-2013-03-19 y luego la siguiente parte 160413.csv .

Traté de usar readcon un bucle while,

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

pero me sale el siguiente error

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

¿Alguien puede sugerir una forma alternativa?

Actualizar

Como se sugiere en las respuestas a continuación, actualicé los 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

La salida que obtengo es

./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
fuente
44
Parece que debe establecer IFS=$'\n'antes del bucle `for para que se analice por cada línea
kiri

Respuestas:

36

Usar forcon findes el enfoque incorrecto aquí, vea por ejemplo este artículo acerca de la lata de gusanos que está abriendo.

El enfoque recomendado es usar find, whiley readcomo se describe aquí . A continuación se muestra un ejemplo que debería funcionar para usted:

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

De esta manera, delimita los nombres de archivo con caracteres nulos ( \0), esto significa que la variación en el espacio y otros caracteres especiales no causarán problemas.

Para actualizar un archivo con los archivos que findlocaliza, puede pasar su salida directamente a tar:

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

Tenga en cuenta que no tiene que diferenciar si el archivo existe o no, tarlo manejará con sensatez. También tenga en cuenta el uso de -printfaquí para evitar incluir el ./bit en el archivo.

Thor
fuente
Gracias, casi funciona. Lo único es archivarlo ./como alquitrán. ./.tar tar: ./archive.tar: file is the archive; not dumped
Ubuntuser
@Ubuntuser Puede agregar una simple verificación para verif [[ "$FILE" == "./" ]]; then continue
kiri
@Ubuntuser: puede evitar el ./bit con -printfver respuesta actualizada. Sin embargo, no debería hacer ninguna diferencia si está incluido o no, ya que solo hace referencia al directorio actual. También incluí una find/tarcombinación alternativa que es posible que desee utilizar.
Thor
Para aquellos de ustedes que deseen obtener sortlos resultados antes de repetirlos, necesitarán sort -zel separador nulo.
Adambeano
13

Intenta citar el forciclo así:

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

Sin comillas, bash no maneja bien los espacios y las nuevas líneas ( \n) ...

También intente configurar

IFS=$'\n'
kiri
fuente
1
+1 por $ IFS. Eso describe el carácter separador.
Ray
1
Esta es la solución que funcionó para mí. Estaba usando commpara comparar listados de archivos ordenados y los nombres de los archivos tenían espacios en ellos, a pesar de las variables de cita no funcionaba. Luego vi cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html y la solución de configurar $ IFS con IFS = $ (echo -en "\ n \ b") funcionó para mí.
pbhj
Además de las comillas dobles, elegante, simple, hermoso, ¡gracias!
Big Rich
8

Esto funciona y es más simple:

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

Gracias a Rupa ( https://github.com/rupa/z ) por esta respuesta.

ShawnMilo
fuente
4

Además de las citas adecuadas, puede indicar findque use un separador NULL, y luego leer y procesar los resultados en un whilebucle

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

Esto debería manejar cualquier nombre de archivo que sea compatible con POSIX - ver 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.
conductor de acero
fuente
Esta es la única solución que funcionó para mí. Gracias.
codefreak
1
find . <find arguments> -print0 | xargs -0 grep <pattern>
user2802945
fuente
1

Hice algo como esto para encontrar archivos que pueden contener espacios.

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

Trabajado como un encanto :)

Scott B
fuente
0

Creo que es mejor que finduses la opción 's -exec.

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

A continuación, Buscar ejecuta el comando mediante una llamada al sistema, de modo que se conservan los espacios y las nuevas líneas (en lugar de una tubería, lo que requeriría el uso de caracteres especiales). Tenga en cuenta que "tar -c" funciona si el archivo ya existe o no, y que (al menos con bash) ni {} ni + necesitan ser citados.

Drake Clarris
fuente
-1

Como sugirió minerz029, debe citar la expansión del findcomando. También necesita citar todas las sustituciones de $FILEen su ciclo.

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

Tenga en cuenta que la $()sintaxis debe preferirse al uso de backticks; mira esta pregunta de U&L . También eliminé la [[palabra clave y la reemplacé por el [comando porque es POSIX.

Joseph R.
fuente
Acerca de [[y [, parece que [[es más nuevo y admite más funciones, como globing y regex. sin embargo, [[solo está enbashsh
kiri
@ minerz029 Sí. Eso es lo que estoy diciendo. No sé a qué te refieres con [[apoyo global. De acuerdo con la wiki de Greg , no se producen burbujas en el interior [[.
Joseph R.
Prueba [ "ab" == a? ] && echo "true"entonces[[ "ab" == a? ]] && echo "true"
kiri
@ minerz029 Eso no es engorroso. Estas son expresiones regulares (interpretadas libremente). Esto no es un problema global porque a*significa "seguido de cualquier número de caracteres" en lugar de "todos los archivos cuyos nombres comienzan ay tienen cualquier número de caracteres después". Trate [ ab = a* ] && echo true vs [[ ab == a* ]] && echo true.
Joseph R.
Ah bueno, [[todavía hace expresiones regulares mientras [que no. Debe haberse confundido
kiri