for loop en carpetas con \ n caracteres en sus nombres

9

Tengo algunas carpetas con el \ncarácter de sus nombres.

por ejemplo:

$ ls
''$'\n''Test'

Eso se refiere a una carpeta con el nombre de la Prueba y una línea vacía antes de su nombre.

Entonces, cuando ejecuto algunos scripts como este, en su directorio principal:

while IFS= read -r d; do 
    rmdir $d
done < <(find * -type d)

Muestra:

rmdir: failed to remove '': No such file or directory
rmdir: failed to remove 'Test': No such file or directory

Porque se ejecuta dos veces, una \ny otra Test, porque el nombre de la carpeta tiene dos líneas.

Entonces, ¿cómo puedo resolver este problema de modo que el script sepa \nTestque solo hay una carpeta?

Tara S Volpe
fuente
3
Debe usar la -print0directiva find y la -dopción de lectura. Ver stackoverflow.com/a/40189667/7552
glenn jackman el
1
@glennjackman ¡Por favor conteste!
postre
@glennjackman Gracias por su respuesta, pero el find * -type d -print0 | while IFS= read -d '' file ; do rmdir $file ; donecomando tiene esta salida rmdir: failed to remove 'Test': No such file or directory.
Tara S Volpe
55
Usted debe citar la variable:rmdir "$file"
Glenn Jackman
1
@glennjackman Solo publique esto como respuesta. Es una solución adecuada y, además, los comentarios no son el mejor lugar para eso. Votación ya implícita :)
Sergiy Kolodyazhnyy

Respuestas:

12

Solo tiene un comando allí, por lo que es suficiente llamar findcon una llamada de -execbandera rmdir:

find -depth -type d -exec rmdir {} \;

O use la -deleteopción como en find -type d -delete, pero no funcionará con directorios no vacíos. Para eso también necesitarás -emptybandera. Tenga en cuenta también, -deleteimplica -depthque se puede omitir. Por lo tanto, otra alternativa viable que mantiene todo como un solo proceso:

find -type d -empty -delete

Si el directorio no está vacío, use rm -rf {} \;. Para aislar solo directorios con \nnombre de archivo, podemos combinar las citas ANSI-C de bash $'...'con la -nameopción:

find  -type d -name $'*\n*' -empty -delete

POSIX-ly, podríamos manejarlo de esta manera:

find -depth  -type d -name "$(printf '*\n*' )" -exec rmdir {} \;

Vale la pena mencionar que si su objetivo es la eliminación de directorios, entonces -deletees suficiente, sin embargo, si desea ejecutar un comando en el directorio, entonces -execes lo más apropiado.

Ver también

Sergiy Kolodyazhnyy
fuente
2
... usar rm -rf con cuidado . ; P ¿No findtiene una -deleteopción por cierto? Elimina los directorios vacíos y arroja errores para los que no están vacíos, como rmdir- podría ser menos costoso.
postre
3
@dessert lo hace y lo agregué, pero -deleteno eliminaré los directorios no vacíos. Por lo tanto, el uso cuidadoso derm -rf
Sergiy Kolodyazhnyy
66
Siempre ejecute en seco dichos findcomandos sin ninguna carga útil destructiva (es decir, elimine el -deleteo -exec whatever \;) para verificar si la lista de archivos afectados es realmente correcta y no contiene cosas que no deberían eliminarse ...
Byte Commander
2
Esto es askubuntu.com para que podamos asumir GNU find. Use -exec rmdir {} +para agrupar múltiples argumentos en una rmdirlínea de comando. Y BTW " pero no funcionará con directorios no vacíos " también se aplica rmdir, por lo que en realidad no es una diferencia -delete. Es una diferencia de rm -r.
Peter Cordes
2
find -type d -empty -delete( -deleteimplica -depth)
Kevin
10

Podrías usar globos de concha en lugar de find:

for d in */ ; do 
    rmdir "$d"
done

El shell glob */coincide con todas las carpetas del directorio actual. Esta construcción de bucle for se encarga de dividir las palabras correctamente automáticamente.

Tenga en cuenta que, dependiendo de sus opciones de shell, esto podría ignorar las carpetas ocultas (el nombre comienza con a.). Ese comportamiento se puede cambiar para que coincida con todos los archivos de la sesión actual con el comando shopt -s dotglob.

Además, no olvides citar siempre tus variables.

Byte Commander
fuente
el glob solo encontrará archivos / directorios en el directorio actual, no recursivamente como lo findhace
ilkkachu
1
Tienes razón @ilkkachu. Eso podría cambiarse habilitando la opción de shell globstar shopt -s globstary utilizando un **/*/glob en su lugar.
Byte Commander
6

Ambas respuestas escritas hasta ahora llaman rmdiruna vez por directorio, pero como rmdirpueden tomar múltiples argumentos, me pregunto: ¿no hay una manera más eficiente?

Uno simplemente podría hacer

rmdir */

y esa es definitivamente la forma más fácil y eficiente, pero puede arrojar un error en el caso de muchos directorios (consulte ¿Cuál es la longitud máxima de los argumentos de la línea de comandos en gnome-terminal? ). Si desea que este enfoque funcione de forma recursiva, habilite la globstaropción de shell con shopt -s globstary use en **/*/lugar de */.

Con GNU find(y si no solo queremos usar -delete), podríamos hacer

find -depth -type d -exec rmdir {} +

que construye la línea de comando "de la misma manera que xargsconstruye sus líneas de comando" ( man find). Sustituto -depthde -maxdepth 1si no se quiere que funcione de forma recursiva.

Steeldriver explica una tercera y brillante forma de la OMI en esta respuesta :

printf '%s\0' */ | xargs -0 rmdir

Esto utiliza el shell incorporado printfpara construir una lista de argumentos delimitada por cero, esta lista se canaliza a xargslas llamadas rmdirexactamente con la frecuencia necesaria. Puede hacer que funcione recursivamente con shopt -s globstary en **/*/lugar de */lo anterior.

postre
fuente
2
findes recursivo, pero el bucle del OP no lo es. Para replicar ese comportamiento, use find -maxdepth 1 -type d -exec rmdir {} +. ( -depthes redundante en ese caso). Buen truco printfpara emular find -print0, no lo había visto antes.
Peter Cordes
1
Oh! Vi el lsy el whilebucle, eché de menos que estaban redirigiendo desde una sustitución de proceso en findlugar de conectarse lsal bucle y simplemente no lo mostraban por alguna razón. Eso find *está realmente enterrado. Sí, es recursivo y fallará si hay demasiadas entradas de directorio en el directorio, incluidas las que no son directorios porque no se usa */. find .sería mucho mejor, porque findes más rápido statque la expansión global de bash.
Peter Cordes