¿Ls siempre listará los archivos que rm eliminará?

33

Algo que creo que debo saber con certeza: si lo hago ls <something>, ¿ rm <something>eliminaré exactamente los mismos archivos que se lsmuestran? ¿Hay alguna circunstancia en la que rmpueda eliminar archivos que lsno se muestran? (Esto es en la fiesta 18.04)

Editar: gracias a todos los que respondieron. Creo que la respuesta completa es una combinación de todas las respuestas, por lo que he aceptado la respuesta más votada como "la respuesta".

Cosas inesperadas que he aprendido en el camino:

  • ls no es tan sencillo como podría pensar en el manejo de sus argumentos
  • En una instalación simple y sin complicaciones de Ubuntu, los alias .bashrc ls
  • ¡No asigne un nombre a sus archivos que comiencen con un guión, ya que pueden parecer argumentos de comando, y nombrar a uno -r lo solicita!
B. Tanner
fuente
8
Estoy algo sorprendido de que rmno tenga una --dry-runbandera ...
fkraiem 05 de
20
@Rinzwind ¿Por qué sería find -deletemejor que rm? Dices "Por eso" , pero no me queda completamente claro a qué se refiere. También tenga en cuenta que su findinvocación eliminará todos los archivos de forma recursiva en el directorio actual, donde rmsimplemente eliminará los archivos en el directorio inmediato. También -name *es un no-op. En general, estoy bastante perplejo por su consejo ...
marcelm 05 de
3
@marcelm Creo que el consejo para usar findes porque puedes ejecutarlo, ver todos los archivos y luego ejecutar el mismo comando con -delete. Como ya vio los resultados find, no debería haber ambigüedad sobre lo que se eliminará (en realidad me gustaría escuchar más detalles sobre esto en forma de respuesta)
Scribblemacher
55
@Scribblemacher "... puedes ejecutarlo, ver todos los archivos y luego ejecutar el mismo comando con -delete" - Pero, ¿cómo es eso mejor que ejecutarlo ls <filespec>, seguido de rm <filespec>(que el OP ya sabe cómo hacerlo)?
marcelm
12
@Rinzwind "Eso resuelve la respuesta de Choroba por un instante donde se crea un archivo después de ls y antes de rm". No, no lo hace. Si ejecuta find ... -printprimero para confirmar qué archivos se eliminarán, y luego find ... -delete, seguirá eliminando los archivos creados entre los dos comandos. Si usa ambos -printy -deleteno obtiene confirmación, solo un informe posterior al hecho de lo que se ha eliminado (y también podría usar rm -v).
marcelm

Respuestas:

40

Bueno, ambos lsy rmoperar sobre los argumentos que se les pasan.

Estos argumentos pueden ser un simple archivo, de modo ls file.exty rm file.extoperan en el mismo archivo y el resultado es claro (lista el archivo / borrar el archivo).

Si por el contrario el argumento es un directorio, ls directoryse enumeran los contenidos del directorio, mientras que rm directoryno va a funcionar como está (es decir, rmsin banderas no pueden eliminar directorios, mientras que si lo hace rm -r directory, de forma recursiva borra todos los archivos bajo directory y el propio directorio ).

Pero tenga en cuenta que los argumentos de la línea de comandos pueden estar sujetos a la expansión del shell , por lo que no siempre se garantiza que se pasen los mismos argumentos a ambos comandos si contienen comodines, variables, salida de otros comandos, etc.

Como ejemplo extremo, piense ls $(rand).txty rm $(rand).txt, los argumentos son "iguales" pero los resultados son bastante diferentes.

Señor shunz
fuente
3
También considere lsvs rm *donde hay archivos "ocultos" (punto), aunque incluso eso no es una comparación justa, ya que no escribí ls *. Pero no rmtiene sentido por sí solo, por lo que todo es manzanas y naranjas realmente. Si he entendido bien, ese es el quid de tu respuesta, así que buen trabajo :)
Lightness Races with Monica
55
Otro comentario en lscontra rm -r: el comando ls <directory>no mostrará los archivos ocultos en el interior del directorio, pero rm -r <directory> será eliminar incluso los archivos ocultos.
Daniel Wagner
1
@LightnessRacesinOrbit lsno los enumerará (a menos que tenga un alias ls -a), y rm *no los eliminará (a menos que haya dotglobconfigurado).
Deja de dañar a Monica el
20

Si está pensando en algo como ls foo*.txtvs. rm foo*.txt, entonces sí, mostrarán y eliminarán los mismos archivos. El shell expande el globo y lo pasa al comando en cuestión, y los comandos funcionan en los archivos enumerados. Uno los enumera, uno los elimina.

La diferencia obvia es que si alguno de esos archivos fuera un directorio, lsenumeraría su contenido, pero rmno lo eliminaría. Eso generalmente no es un problema, ya rmque eliminaría menos de lo que se mostró ls.

El gran problema aquí proviene de ejecutar ls *o rm *en un directorio que contiene nombres de archivos que comienzan con un guión . Ellos se expandirían a las líneas de comandos de los dos programas como si las escribió por su cuenta, y lsllevarían -ra significar "orden inverso", mientras que rmllevaría -ra significar una eliminación recursiva. La diferencia es importante si tiene subdirectorios de al menos dos niveles de profundidad. ( ls *mostrará el contenido de los directorios de primer nivel, pero rm -r *también todo pasará el primer subnivel).

Para evitar eso, escriba globos permisivos con un signo ./que indique el directorio actual, y / o ponga un --para indicar el final del procesamiento de opciones antes del globo (es decir, rm ./*o rm -- *).

Con un glob como *.txt, en realidad no es un problema ya que el punto es un carácter de opción no válido y causará un error (hasta que alguien expanda las utilidades para inventar un significado para él), pero de todos modos es más seguro ponerlo ./allí.


Por supuesto, también podría obtener resultados diferentes para los dos comandos si cambia las opciones de bloqueo del shell, o crea / mueve / elimina archivos entre los comandos, pero dudo que haya querido decir alguno de esos casos. (Tratar con archivos nuevos / movidos sería extremadamente complicado de hacer de forma segura).

ilkkachu
fuente
1
Gracias. Esto parece resaltar un problema con toda la sintaxis de la línea de comando: si nombra archivos que comienzan con un guión, navega en aguas peligrosas. Quién sabe qué comandos puede usar en el futuro, mucho después de que se haya olvidado de los archivos.
B.Tanner
1
@ B. Tanner, sí. En cierto sentido, el problema es que los argumentos de la línea de comandos son simplemente cadenas simples. Si el sistema se diseñó hoy, puede haber más estructura en él para que el programa ejecutado pueda saber si se supone que un argumento es una opción o no. También hay otros problemas que provienen de la estructura muy laxa de los nombres de archivo. Hay un ensayo muy completo sobre eso por dwheeler , pero tengo que advertir que leerlo va a doler. (Ya sea por los detalles, o por lo horrible de lo que puede salir mal.)
ilkkachu
3
No puedo resistirme, me lo has vendido, me voy a leerlo ahora, con recuerdos del momento en que logré obtener un carácter de retorno de carro al final de muchos nombres de archivos (tratando de ver si podía construya un archivo por lotes de Windows que también puede ejecutarse como un script bash ... ¡No lo vi venir!)
B.Tanner
17

Dejando a un lado el comportamiento de shell, centrémonos solo en qué rmy lspodemos lidiar con ellos mismos. Al menos un caso donde lsse mostrará lo rmque no se puede eliminar involucra permisos de directorio, y el otro - directorios especiales .y ...

Permisos de carpeta

rmes una operación en un directorio, porque al eliminar un archivo, está cambiando el contenido del directorio (o, en otras palabras, una lista de entradas de directorio, ya que el directorio no es más que una lista de nombres de archivo e inodos ). Esto significa que necesita permisos de escritura en un directorio. Incluso si es el propietario del archivo , sin permisos de directorio no puede eliminar archivos. Lo contrario también es cierto : rmpuede eliminar archivos que pueden ser propiedad de otros, si usted es el propietario del directorio.

Por lo tanto, es muy posible que tenga permisos de lectura y ejecución en un directorio, lo que le permitirá atravesar el directorio y ver los contenidos muy bien, por ejemplo ls /bin/echo, pero no puede hacerlo a rm /bin/echomenos que sea el propietario /bin o eleve sus privilegios sudo.

Y verá casos como este en todas partes. Aquí hay uno de estos casos: https://superuser.com/a/331124/418028


Directorios especiales '.' y '..'

Otro caso especial es .y ..directorios. Si lo hace ls .o ls .., felizmente le mostrará el contenido, pero rmno está permitido:

$ rm -rf .
rm: refusing to remove '.' or '..' directory: skipping '.'
Sergiy Kolodyazhnyy
fuente
15

Si escribe ls *y luego rm *, es posible que elimine más archivos de los lsmostrados; podrían haberse creado en el pequeño intervalo de tiempo entre el final lsy el inicio de rm.

choroba
fuente
1
Ese es un caso probable para /tmp, donde muchas aplicaciones pueden crear archivos temporales, por lo que siempre es una posibilidad en ambos comandos con *. Sin embargo, algunas aplicaciones también hacen que los archivos sean anónimos unlink(), manteniéndolos abiertos, por lo que puede mostrarse ls *pero rm *no capturarse.
Sergiy Kolodyazhnyy
1
@OrangeDog Te estás perdiendo el punto. Estamos hablando de la condición de la carrera
Sergiy Kolodyazhnyy
1
@OrangeDog Tendré que verificar esto ya que estoy hablando por teléfono en este momento, pero el punto sigue en pie incluso con *la diferencia entre lo que lsse mostrará y lo que rmfunciona, porque la lista de contenidos del directorio ha cambiado en el medio.
Sergiy Kolodyazhnyy
1
Incluso si solo haces un comando, hay una condición de carrera entre expandir los argumentos y luego eliminarlos.
Deja de dañar a Monica el
1
@OrangeDog Estoy de acuerdo con eso. Como la expansión de comodines funciona en los nombres de archivo existentes, sí, hay una condición de carrera entre el shell que expande el comodín y un comando que lo procesa, por lo que ls *podría mostrar el nombre de archivo que ya no está.
Sergiy Kolodyazhnyy
9

ls *y rm *no son responsables de expandir el glob - eso lo hace el shell antes de pasarlo al comando.

Esto significa que puede usar cualquier comando con la lista de archivos expandida, por lo que usaría algo que haga lo menos posible.

Entonces, una mejor manera de hacer esto (o al menos, otra forma) es saltarse al intermediario.

echo *le mostrará exactamente lo que se pasaría a su rmcomando.

Sombra
fuente
2
Gracias, me gusta la idea de usar echo en lugar de ls en este escenario.
B.Tanner
3
O printf "%s\n" *para obtener una visión inequívoca de los nombres de archivo con espacios. (O %qen lugar de tratar con los saltos de línea y caracteres de control también, a expensas de los más feos de salida.)
ilkkachu
4

Qué tal si:

$ mkdir what
$ cd what
$ mkdir -p huh/uhm ./-r
$ ls *
uhm
$ rm *
$ ls
-r
$ ls -R
.:
-r

./-r:

Básicamente, los comodines que se expanden a cosas que comienzan con -(o cosas ingresadas manualmente que comienzan con -pero que se parece un poco más a una trampa) pueden interpretarse de manera diferente por lsy rm.


fuente
4

No son casos extremos donde lo que lsmuestra que no es lo rmelimina. Uno bastante extremo, pero afortunadamente benigno, es si el argumento que pasa es un enlace simbólico a un directorio: lsle mostrará todos los archivos en el directorio de enlaces simbólicos, mientras rmque eliminará el enlace simbólico, dejando intacto el directorio original y su contenido:

% ln -s $HOME some_link
% ls some_link    # Will display directory contents  
bin    lib    Desktop ...
% rm some_link
% ls $HOME
bin    lib    Desktop ...
alexis
fuente
1
Huh ln -s $HOME some_link; ls some_linksalidas some_link@para mí, pero me he lsalias ls -F. Aparentemente, -Fcambia el comportamiento para mostrar el enlace en lugar de desreferenciarlo. No esperaba eso.
marcelm
¡En efecto! También, ls -lpor ejemplo, apunta al enlace, no al destino ... tiene que haber formas de inspeccionar el enlace en sí.
alexis
1
Agregar opciones a la pregunta abre demasiadas posibilidades: mi respuesta es sobre el comportamiento no modificado de lsy rm.
alexis
Si usted dice ls some_link/,  ls -H some_linko ls -L some_link, listará la ligada al directorio, incluso si se agrega -Fo -l. Por el contrario (más o menos), -ddice mirar un directorio en lugar de su contenido; comparar ls -l /tmpy ls -ld /tmp.
G-Man dice 'reinstalar a Monica' el
Claro, puede agregar banderas que cambien el comportamiento de ls. Básicamente, se está mostrando por qué enumerar comportamientos con diferentes banderas no es digno de la incomodidad para esta pregunta ...
Alexis
4

Si lo hace solamente lsen lugar de ls -a, sí rmse pueden eliminar los archivos ocultos que no han visto con lsy sin -a.

Ejemplo:

De acuerdo a :

dir_test
├── .test
└── test2

ls dir_test : solo mostrará test2

ls -A dir_test : mostrará test2 + .test

rm -r dir_test : eliminará todo (.test + test2)

Espero que eso te ayude.

DevHugo
fuente
¿Puede dar un ejemplo? Porque normalmente, rm *no eliminará los archivos de puntos. Si lo hace, ls *también los mostrará.
marcelm
No, ls *no muestres archivos ocultos.
DevHugo
Pero sí, está un poco confundido, agregué algunos ejemplos.
DevHugo
1
ls -aenumerará ., .., .testy test2. Es posible que desee cambiar su ejemplo para usar ls -A, que enumera todo excepto . y ..(es decir, solo .testy test2).
G-Man dice 'reinstalar a Monica' el
3

Ya hay muchas buenas respuestas, pero quiero agregar una visión más profunda.

Hágase la pregunta: ¿A cuántos parámetros se pasan lssi escribe?

ls *

...? Tenga en cuenta que el lscomando no obtiene el *parámetro como si hay archivos a los que *se pueda expandir. En cambio, el shell primero realiza el bloqueo antes de invocar el comando, por lo que el lscomando realmente obtiene tantos parámetros como archivos que coinciden con el bloqueo. Para suprimir el globbing, cite el parámetro.

Esto es cierto para cualquier comando: echo *vs echo '*'.

Hay un script, llámalo countparams.shpara probar el efecto. Le indica cuántos parámetros pasó y los enumera.

#!/bin/bash
echo "This script was given $# parameters."
arr=( "$@" )
for ((i=0;i<$#;i++)); do
        echo "Parameter $((i+1)): ${arr[$i]}"
done

Hazlo ejecutable y ejecútalo ./countparams.sh *. ¡Aprende de su salida!

rexkogitans
fuente
1

El globo se expandirá de la misma manera las dos veces, si el contenido del directorio es el mismo en esos dos momentos diferentes.


Si realmente desea verificar qué se eliminará, úselo rm -i *.txt. Le indicará por separado para cada archivo antes de (intentar) eliminarlo.

Se garantiza que esto es seguro contra las condiciones de la carrera:
        ls *.txt/ se crea un nuevo archivo / rm *.txt
porque el mismo programa que realiza la eliminación le solicita cada archivo.


Esto es demasiado engorroso para un uso normal, y si alias rma rm -i, usted se encontrará usando \rmo rm -fcon bastante frecuencia. Pero vale la pena mencionar al menos que hay una solución para la condición de la carrera. (Incluso es portátil para sistemas que no son GNU: POSIX rm(1)especifica la -iopción ).

Otra opción sería una matriz bash:, to_remove=(*.txt)luego pídale al usuario que confirme (tal vez después de hacerlo ls -ld -- "${to_remove[@]}"), luego rm -- "${to_remove[@]}". Por lo tanto, la expansión global solo se realiza una vez, y la lista se pasa literalmente a rm.

Otra opción prácticamente utilizable es GNU rm -I( página de manual ), que indica si se eliminan más de 4 elementos. (Pero no muestra la lista, solo el total). Lo uso alias rm='rm -I'en mi escritorio.

Es una buena protección contra el regreso de los dedos gordos con un patrón medio tipado que coincide demasiado. Pero usar lsprimero es generalmente bueno en un directorio de su propiedad, o en un sistema de usuario único, y cuando no hay procesos en segundo plano que puedan crear asincrónicamente nuevos archivos allí. Para protegerse contra los dedos gordos, no escriba rm -rf /foo/bar/bazde izquierda a derecha. rm -rf /es especial, pero rm -rf /usrno lo es! Omita la -rfparte, o comience con ls, y solo agregue la rm -rfparte después de escribir la ruta.

Peter Cordes
fuente