Problema con espacios en los nombres de archivo

8

Quiero hacer algo repetidamente en una lista de archivos. Los archivos en las preguntas tienen espacios en sus nombres:

david@david: ls -l
total 32
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha
-rw-rw-r-- 1 david david  0 Mai  8 11:55 haha~
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (3rd copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (4th copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (5th copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (6th copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (7th copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (another copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (copy)

Ahora quiero anotar cada uno de estos archivos:

david@david: echo '
for file in $(ls)
do
stat $file
done' | bash

(Uso echo y una tubería para escribir comandos de varias líneas).

Cuando hago eso, funciona correctamente en aquellos archivos que no tienen espacios en sus nombres. Pero los otros ...

stat: cannot stat ‘(another’: No such file or directory
stat: cannot stat ‘copy)’: No such file or directory

Cambiar $(ls)a "$(ls)"o $filepara "$file"no funciona. ¿Que puedo hacer?

Editar:

echo '
for files in *
do
stat "$files"
done' | bash

¡Hace el truco! Como soy nuevo en bash, quiero mantener las cosas lo más simples posible, así que nada con tratar de escapar de espacios, o usar xargso la solución read -r, aunque resuelvan el problema.

Como algunos han preguntado: Sí, usar esto en lugar de stat *es extraño. Pero solo quería encontrar una forma general de aplicar el mismo comando en un grupo de nombres de archivo en bash, usando un bucle for. Entonces statpodría significar gzip, gpgo rm.

usuario258532
fuente
1
lo que está mal con el stat *? (;-)
Rmano
También vea mywiki.wooledge.org/ParsingLs
Rmano
Solo uso stat como ejemplo. :) Quiero recopilar nombres de archivo con ls y luego usar los resultados de ls en un bucle bash.
user258532

Respuestas:

12

La cita múltiple de la echo 'está complicando la cosa.

Solo puedes usar:

for f in *; do stat -- "$f"; done

Pero también

stat -- * 

... y si desea recopilar los archivos y luego aplicar el comando (¿por qué?) puede ir con (pero tenga cuidado con el archivo que contiene nuevas líneas ... (1))

for f in *; do echo "$f"; done | xargs stat --

... y si también quieres archivos ocultos, solo úsalo * .*como un patrón, pero luego recuerda eso .y ..estará en el conjunto .

Como comentario aparte, no debe analizar la lssalida .


(1) pero si tiene nombres de archivo con nuevas líneas, de alguna manera se lo merece ... ;-)

Rmano
fuente
"y luego aplica el comando (¿por qué?)" -> stat solo sirve como un comando arbitrario, ya que estoy probando cómo hacer bucles bash con nombres de archivos. Podría ser gpg, gzip o cualquier otra cosa.
user258532
@ user258532 sea cual sea el comando, úselo siempre for f in *; do command "$f"; done. Nunca analice ls, ciertamente nunca lo haga en un for bucle y ¿por qué usarlo echo?
terdon
echo: Porque me gusta escribir los comandos en varias líneas ... :)
user258532
2
@ user258532 ¿Eh? ¿Por qué necesitarías eco para eso? Simplemente presione enter y continúe en una nueva línea. Si al final de una línea con una cita abierta o en una de do, |, &&etc, puede continuar en la nueva línea. O eso o usa heredocs. No hay razón para usar echoy también puede causar problemas.
terdon
"Simplemente presione enter y continúe en una nueva línea". Ay. :-D Ahora mi coeficiente intelectual está oficialmente por debajo de 0 - ya sabes, soy realmente nuevo en bash y cosas así. Pero en realidad gano mi dinero con R (el paquete estadístico y el lenguaje de programación).
user258532
6

En una nota al margen: puede dividir comandos largos / complicados en varias líneas agregando un espacio seguido de una barra diagonal inversa y presionando Entercada vez que desee comenzar a escribir en una nueva línea, en lugar de bifurcar múltiples procesos usando echo [...] | bash; También debe encerrar $fileentre comillas dobles, para evitar que se statrompan en caso de nombres de archivo que contengan espacios:

for file in $(ls); \
do \
stat "$file"; \
done

El problema es que se $(ls)expande a una lista de nombres de archivos que contienen espacios, y lo mismo ocurrirá también con "$(ls)".

Incluso resolviendo este problema, este método aún se romperá en los nombres de archivo que contengan barras diagonales inversas y en los nombres de archivo que contengan nuevas líneas (como lo indica terdon).

Una solución para ambos problemas sería canalizar la salida de findun whilebucle en ejecución read -rpara que, en cada iteración, read -ralmacene una línea de findsalida en $file:

find . -maxdepth 1 -type f | while read -r file; do \
    stat "$file"; \
done
kos
fuente
1
y archivos ocultos? :)
AB
55
Eso todavía fallará en los nombres de archivo que contienen nuevas líneas. Simplemente no analices ls. Siempre.
terdon
44
@ user258532 no, no lo hace. En serio, simplemente no analicesls . Hay formas mejores y más robustas. También es posible que desee leer esto: ¿Por qué * no * analiza `ls`? para más detalles.
terdon
1
El problema aquí no es ls, es for- foritera sobre palabras (separadas por IFS) dadas después de la inpalabra clave`
glenn jackman
1
@glennjackman bueno, sí, es la combinación de fory ls. for f in *estaría bien, por ejemplo.
terdon
3

Usa lo bueno find, funciona con archivos ocultos, líneas nuevas y espacios.

find . -print0 | xargs -I {} -0 stat {}

o cualquier otro en lugar de stat

find . -print0 | xargs -I {} -0 file {}
find . -print0 | xargs -I {} -0 cat {}
AB
fuente
1

Como un chico R, ya encontré una solución en R:

filenames <- dir(); # reads file names into an array.
                    # It works also recursively
                    # with dir(recursive=TRUE)
for (i in 1:length(filenames)) {
system(     # calls a system function
 paste(     # join stat and the file name
  "stat",
  filenames[i]
 )
)
}

Lo sé, es una locura. Deseo que la salida de lssea ​​más fácil de analizar ... R puede tratar con espacios, porque dir () devuelve un valor de carácter entre comillas. Cualquier cosa entre las comillas es un nombre de archivo válido con espacios.

usuario258532
fuente
3
No te molestes (¡pero +1 por esfuerzo! :). Simplemente use for f in *, presione enter y continúe en una nueva línea: dopresione enter nuevamente stat "$f", ingrese nuevamente, doneingrese. Ese es un comando dividido muy bien en 4 líneas y no se romperá en ningún tipo de nombre de archivo.
terdon
1

Me he encontrado con otras instancias de problemas de espacios en blanco en bucles for, por lo que el siguiente comando (imo más robusto) es lo que generalmente uso. También se adapta muy bien a las tuberías.

$ ls | while read line; do stat "$line"; done;

Puede combinar esto con grepo usar finden su lugar:

$ find ./ -maxdepth 2 | grep '^\./[/a-z]+$' | while read line; do stat "$line"; done;
Tyzoid
fuente
Su primera respuesta falla en los nombres de archivo que contienen barra diagonal inversa o espacios iniciales o finales. Su segunda respuesta falla totalmente a menos que agregue la -Eopción grep, sin la cual no se reconocerá +en una expresión regular. Incluso entonces, es un cambio y una falla en esta pregunta, ya que grep elimina los nombres de archivos que contienen espacios . También elimina los nombres de archivo que contienen dígitos (números) y signos de puntuación (por ejemplo, paréntesis), como lo hacen los ejemplos en la pregunta. Y eso ni siquiera menciona nombres de archivos que contengan nueva línea o que comiencen con -(guión).
Scott
-1

Esta respuesta resolverá el problema del análisis lsy cuidará los espacios de retroceso y las nuevas líneas.

Pruebe esto, resolverá su problema usando el separador de campo interno IFS.

IFS="\n" for f in $(ls); do   stat "$f"; done

Pero también puede resolverlo fácilmente sin necesidad de analizar la salida de ls utilizando

for f in *; do   stat "$f"; done
Maythux
fuente
1
no funciona para archivos ocultos.
AB
2
OP no solicitó archivos ocultos
Maythux
1
No veo por qué necesita modificar IFSaquí: citar la variable debería ser suficiente para evitar la división de palabras, ¿verdad?
steeldriver
para analizar ls .
Maythux
¡Para qué es el voto negativo!
Maythux
-1

En cambio, puede cambiar el nombre de sus archivos reemplazando el espacio con algún otro carácter como el guión bajo, para deshacerse de este problema:

Para hacerlo, ejecute fácilmente el comando:

for file in * ; do mv "$f" "${f// /_}" ; done
Maythux
fuente