¿Hay alguna desventaja de usar rm $ (ls) para eliminar archivos?

13

Me preguntaba si usar rm $(ls)para eliminar archivos (o rm -r $(ls)para eliminar directorios también) era seguro. Porque en todos los sitios web, las personas ofrecen otras formas de hacerlo, aunque este comando parece mucho más fácil que otros comandos.

posixKing
fuente
3
Respuesta básica, no. No puedo manejar caracteres especiales. Puedo escribir una respuesta en un momento para explicar esto con más detalle
Sergiy Kolodyazhnyy
55
touch 'foo -r .. bar'; rm $(ls); ¿A dónde fue mi directorio principal? Además, ¿qué tipo de alternativas estás viendo que son aún más complicadas que esta? rm *es mucho más fácil de escribir y pensar, y más seguro (pero no perfectamente seguro; vea la respuesta de Dennis).
Peter Cordes
1
Además de las excelentes respuestas, tenga en cuenta que lspuede variar entre implementaciones y, por lo tanto, no es estándar. Dependiendo de lo que necesite, considere alternativas como findy stat. Debe usarlo lssolo para consumo humano, nunca para usarlo con otros comandos o en scripts.
Paddy Landau

Respuestas:

7

¿Qué se pretende hacer con esto?

  • ls enumera archivos en el directorio actual
  • $(ls)sustituye la salida de lslugares que como argumento pararm
  • Esencialmente rm $(ls)está destinado a eliminar todos los archivos en el directorio actual

Qué está mal con esta imagen ?

lsno puede manejar correctamente caracteres especiales en el nombre del archivo. Los usuarios de Unix generalmente recomiendan usar diferentes enfoques . También lo he demostrado en una pregunta relacionada sobre el conteo de nombres de archivos . Por ejemplo:

$ touch file$'\n'name                                                                                                    
$ ls                                                                                                                     
file?name
$ rm $(ls)
rm: cannot remove 'file': No such file or directory
rm: cannot remove 'name': No such file or directory
$ 

Además, como se menciona correctamente en la respuesta de Denis, un nombre de archivo con guiones iniciales podría interpretarse como un argumento para rmdespués de la sustitución, lo que anula el propósito de eliminar el nombre de archivo.

Que funciona

Desea eliminar archivos en el directorio actual. Entonces usa glob rm *:

$ ls                                                                                                                     
file?name
$ rm $(ls)
rm: cannot remove 'file': No such file or directory
rm: cannot remove 'name': No such file or directory
$ rm *
$ ls
$ 

Puedes usar el findcomando. Esta herramienta se recomienda con frecuencia para algo más que el directorio actual: puede atravesar recursivamente todo el árbol de directorios y operar archivos a través de-exec . . .{} \;

$ touch "file name"                                
$ find . -maxdepth 1 -mindepth 1                                                                                         
./file name
$ find . -maxdepth 1 -mindepth 1 -exec rm {} \;                                                                          
$ ls
$ 

Python no tiene problemas con los caracteres especiales en los nombres de archivo, por lo que podríamos emplear eso también (tenga en cuenta que este es solo para archivos, deberá usar os.rmdir()y os.path.isdir()si desea operar en directorios):

python -c 'import os; [ os.remove(i) for i in os.listdir(".") if os.path.isfile(i) ]'

De hecho, el comando anterior podría convertirse en función o alias ~/.bashrcpor brevedad. Por ejemplo,

rm_stuff()
{
    # Clears all files in the current working directory
    python -c 'import os; [ os.remove(i) for i in os.listdir(".") if os.path.isfile(i) ]'

}

Perl versión de eso sería

perl -e 'use Cwd;my $d=cwd();opendir(DIR,$d); while ( my $f = readdir(DIR)){ unlink $f;}; closedir(DIR)'
Sergiy Kolodyazhnyy
fuente
1
Su "$(ls)"ejemplo solo funciona si solo hay un archivo en el directorio. También podría usar la finalización de tabulación para expandir el nombre de archivo, ya que es la única finalización.
Peter Cordes
@PeterCordes de hecho, por una extraña razón, solo funciona con un archivo. Ese es un argumento más en contra de usar lsentonces :) Lo
editaré
No lo llamaría una razón extraña: o cita $(ls)para deshabilitar la división de palabras, o deja que ocurra la división de palabras (con resultados desastrosos). La única forma limpia de pasar una lista de múltiples cadenas sin tratar los datos como código es con variables de matriz, o con \0un separador, pero el shell en sí mismo no puede hacer eso. Todavía IFS=$'\n'es el menos peligroso, pero no puede igualar find -print0 | xargs -0. O grep -l --null. O evitas todo el problema con cosas como find -exec rm {} +. (Tenga en cuenta que +para pasar varios argumentos a cada invocación de rm; MUCHO más eficiente).
Peter Cordes
@PeterCordes Sí, totalmente de acuerdo allí. Pero IFS=$'\n'también fallará en este caso, ya que tengo una nueva línea en el nombre del archivo, por lo que la división de palabras lo tratará como dos nombres de archivo en lugar de uno. La extraña razón, sin embargo, es el hecho de que con el valor predeterminado IFSque es espacio, tabulación, nueva línea, original rm "$(ls)"también debería fallar, debería tratar como dije el nombre de archivo como dos separados, pero no fue así. Me suelen utilizar findcon -execo find . . .-print0 | while IFS= read -d'' FILENAME ; do . . . doneestructura para hacer frente a los nombres de archivo. O uno podría usar python, he agregado un ejemplo de eso.
Sergiy Kolodyazhnyy
"$(ls)"siempre se expande a un argumento porque las comillas protegen la expansión de $(ls)la división de palabras. Justo como lo protegen "$foo".
Peter Cordes
26

No, no es seguro, y la alternativa de uso común rm *no es mucho más segura.

Hay muchos problemas con rm $(ls). Como otros ya han cubierto en sus respuestas, la salida de lsse dividirá en los caracteres presentes en el separador de campo interno .

En el mejor de los casos, simplemente no funciona. En el peor de los casos, pretendía eliminar solo archivos (pero no directorios), o eliminar selectivamente algunos archivos con -i, pero hay un archivo con el nombre c -rfen el directorio actual. Veamos qué pasa.

$ mkdir a
$ touch b
$ touch 'c -rf'
$ rm -i $(ls)
$ ls
c -rf

Se rm -i $(ls)suponía que el comando debía eliminar solo los archivos y preguntar antes de eliminar cada uno, pero el comando que finalmente se ejecutó decía

rm -i a b c -rf

entonces hizo algo completamente diferente.

Tenga en cuenta que rm *solo es marginalmente mejor. Con la estructura de directorios como antes, se comportará como se pretende aquí, pero si tiene un archivo llamado -rf, todavía no tiene suerte.

$ mkdir a
$ touch b
$ touch ./-rf
$ rm -i *
$ ls
-rf

Hay varias alternativas mejores. Los más fáciles implican solo rm y globbing.

  • El comando

    rm -- *
    

    funcionará exactamente como se pretendía, donde --indica que todo después de esto no debe interpretarse como una opción.

    Esto ha sido parte de las pautas de sintaxis de la utilidad POSIX durante más de dos décadas. Está muy extendido, pero no debes esperar que esté presente en todas partes.

  • El comando

    rm ./*
    

    hace que el globo se expanda de manera diferente y, por lo tanto, no requiere soporte de la utilidad llamada.

    Para mi ejemplo de arriba, puede ver el comando que finalmente se ejecutará anteponiendo echo .

    $ echo rm ./*
    rm ./a ./b ./-rf
    

    El encabezado ./evita que rm trate accidentalmente cualquiera de los nombres de archivo como opciones.

Dennis
fuente
1
Muy buen punto, los nombres de archivo con - después de la expansión se convierten en indicadores de rm. +1
Sergiy Kolodyazhnyy
1
Aún más desagradable: touch 'foo -rf .. bar. No creo que un atacante pueda llegar más alto que el directorio padre, a menos que podamos producir un separador de ruta en lsla salida de.
Peter Cordes
@Peter atacante tendría que tener permisos de escritura para eliminar el directorio de patentes en primer lugar, ¿no?
Sergiy Kolodyazhnyy
1
@PeterCordes No estoy seguro de si todas las versiones de rm tienen este sistema a prueba de fallas, pero en Ubuntu, openSUSE y Fedora, dice rm: refusing to remove '.' or '..' directory: skipping '..'o algo similar al intentar eliminar el directorio principal.
Dennis
@Serg: el atacante solo te envía un .zip con ese nombre de archivo y te permite dispararte en el pie extrayéndolo y luego tratando de eliminar el contenido. O creando ese nombre de archivo en / var / tmp o algo así.
Peter Cordes