GNU/Linux >> Tutoriales Linux >  >> Linux

¿Recorrer archivos con espacios en los nombres?

Esta pregunta ya tiene respuestas aquí :¿Por qué es una mala práctica recorrer la salida de find?

(8 respuestas)
Cerrado hace 3 años.

Escribí el siguiente script para diferenciar las salidas de dos directores con todos los mismos archivos como tales:

#!/bin/bash

for file in `find . -name "*.csv"`  
do
     echo "file = $file";
     diff $file /some/other/path/$file;
     read char;
done

Sé que hay otras formas de lograr esto. Sin embargo, curiosamente, este script falla cuando los archivos tienen espacios en ellos. ¿Cómo puedo lidiar con esto?

Ejemplo de resultado de encontrar:

./zQuery - abc - Do Not Prompt for Date.csv

Respuesta aceptada:

Respuesta corta (más cercana a su respuesta, pero maneja espacios)

OIFS="$IFS"
IFS=$'n'
for file in `find . -type f -name "*.csv"`  
do
     echo "file = $file"
     diff "$file" "/some/other/path/$file"
     read line
done
IFS="$OIFS"

Mejor respuesta (también maneja comodines y saltos de línea en los nombres de archivo)

find . -type f -name "*.csv" -print0 | while IFS= read -r -d '' file; do
    echo "file = $file"
    diff "$file" "/some/other/path/$file"
    read line </dev/tty
done

Mejor respuesta (basada en la respuesta de Gilles)

find . -type f -name '*.csv' -exec sh -c '
  file="$0"
  echo "$file"
  diff "$file" "/some/other/path/$file"
  read line </dev/tty
' exec-sh {} ';'

O incluso mejor, para evitar ejecutar un sh por archivo:

find . -type f -name '*.csv' -exec sh -c '
  for file do
    echo "$file"
    diff "$file" "/some/other/path/$file"
    read line </dev/tty
  done
' exec-sh {} +

Respuesta larga

Tienes tres problemas:

  1. De forma predeterminada, el shell divide la salida de un comando en espacios, tabulaciones y saltos de línea
  2. Los nombres de archivo podrían contener caracteres comodín que se expandirían
  3. ¿Qué pasa si hay un directorio cuyo nombre termina en *.csv? ?

1. Dividir solo en saltos de línea

Para averiguar qué configurar file a, el shell tiene que tomar la salida de find e interpretarlo de alguna manera, de lo contrario file sería solo la salida completa de find .

El shell lee el IFS variable, que se establece en <space><tab><newline> por defecto.

Luego mira cada carácter en la salida de find . Tan pronto como vea cualquier carácter que esté en IFS , cree que marca el final del nombre del archivo, por lo que establece file a los caracteres que vio hasta ahora y ejecuta el bucle. Luego comienza donde lo dejó para obtener el siguiente nombre de archivo y ejecuta el siguiente bucle, etc., hasta que llega al final de la salida.

Así que efectivamente está haciendo esto:

for file in "zquery" "-" "abc" ...

Para decirle que solo divida la entrada en líneas nuevas, debe hacerlo

IFS=$'n'

antes de su for ... find comando.

Eso establece IFS a una sola línea nueva, por lo que solo se divide en líneas nuevas, y no espacios y tabulaciones también.

Si está utilizando sh o dash en lugar de ksh93 , bash o zsh , debe escribir IFS=$'n' así en su lugar:

IFS='
'

Probablemente eso sea suficiente para que su secuencia de comandos funcione, pero si está interesado en manejar otros casos de esquina correctamente, siga leyendo...

2. Expandiendo $file sin comodines

Dentro del ciclo donde lo haces

diff $file /some/other/path/$file

el shell intenta expandir $file (¡otra vez!).

Podría contener espacios, pero como ya configuramos IFS arriba, eso no será un problema aquí.

Pero también podría contener caracteres comodín como * o ? , lo que conduciría a un comportamiento impredecible. (Gracias a Gilles por señalar esto).

Para indicarle al shell que no expanda los caracteres comodín, coloque la variable entre comillas dobles, por ejemplo,

diff "$file" "/some/other/path/$file"

El mismo problema también podría mordernos

for file in `find . -name "*.csv"`

Por ejemplo, si tuviera estos tres archivos

file1.csv
file2.csv
*.csv

(muy poco probable, pero aún posible)

Relacionado:Si cambio los permisos en un archivo tar, ¿se aplicará eso a los archivos que contiene?

Sería como si hubieras corrido

for file in file1.csv file2.csv *.csv

que se expandirá a

for file in file1.csv file2.csv *.csv file1.csv file2.csv

causando file1.csv y file2.csv para ser procesado dos veces.

En su lugar, tenemos que hacer

find . -name "*.csv" -print | while IFS= read -r file; do
    echo "file = $file"
    diff "$file" "/some/other/path/$file"
    read line </dev/tty
done

read lee líneas de entrada estándar, divide la línea en palabras según IFS y los almacena en los nombres de variables que especifique.

Aquí, le decimos que no divida la línea en palabras y que almacene la línea en $file .

También tenga en cuenta que read line ha cambiado a read line </dev/tty .

Esto se debe a que dentro del ciclo, la entrada estándar proviene de find a través de la canalización.

Si solo hiciéramos read , estaría consumiendo parte o todo el nombre de un archivo y algunos archivos se omitirían.

/dev/tty es la terminal desde donde el usuario está ejecutando el script. Tenga en cuenta que esto provocará un error si el script se ejecuta a través de cron, pero supongo que esto no es importante en este caso.

Entonces, ¿qué sucede si el nombre de un archivo contiene saltos de línea?

Podemos manejar eso cambiando -print a -print0 y usando read -d '' al final de una canalización:

find . -name "*.csv" -print0 | while IFS= read -r -d '' file; do
    echo "file = $file"
    diff "$file" "/some/other/path/$file"
    read char </dev/tty
done

Esto hace que find coloque un byte nulo al final de cada nombre de archivo. Los bytes nulos son los únicos caracteres que no se permiten en los nombres de archivo, por lo que esto debería manejar todos los nombres de archivo posibles, sin importar cuán raros sean.

Para obtener el nombre del archivo del otro lado, usamos IFS= read -r -d '' .

Donde usamos read arriba, usamos el delimitador de línea predeterminado de nueva línea, pero ahora, find está usando nulo como delimitador de línea. En bash , no puede pasar un carácter NUL en un argumento a un comando (incluso los integrados), pero bash entiende -d '' como significado NUL delimitado . Entonces usamos -d '' para hacer read use el mismo delimitador de línea que find . Tenga en cuenta que -d $'

Linux
  1. Copiar archivos en la terminal de Linux

  2. Mover archivos en la terminal de Linux

  3. Recorriendo el contenido de un archivo en Bash

  4. Iterar sobre una lista de archivos con espacios

  5. AWK y nombres de archivo con espacio en él.

Cómo encontrar archivos con el comando fd en Linux

Manera fácil de fusionar archivos con el comando Cat

Manipulación de archivos con el Administrador de archivos del Panel de control de Plesk

Cómo abordar los nombres de archivo con espacios en Linux

Cómo encontrar archivos con docenas de criterios con el comando Bash Find

Asegure Linux con el archivo Sudoers