¿Por qué se diseñó `cp` para sobrescribir silenciosamente los archivos existentes? [cerrado]


30

Probé cpcon los siguientes comandos:

$ ls
first.html   second.html  third.html

$ cat first.html
first

$ cat second.html
second

$ cat third.html
third

Luego copio first.htmla second.html:

$ cp first.html second.html

$ cat second.html
first

El archivo second.htmlse sobrescribe en silencio sin ningún error. Sin embargo, si lo hago en una GUI de escritorio arrastrando y soltando un archivo con el mismo nombre, se agregará el sufijo first1.htmlautomáticamente. Esto evita sobrescribir accidentalmente un archivo existente.

¿Por qué no cpsigue este patrón en lugar de sobrescribir archivos en silencio?


10
Me imagino que solo los diseñadores de coreutils realmente pueden responder la pregunta, pero es así como funciona por ahora. Por lo general, las aplicaciones se crean asumiendo que el usuario realmente quiere decir lo que está haciendo y para minimizar las indicaciones adicionales. Si desea cambiar el comportamiento, alias 'cp' a 'cp -i' o 'cp -n'.
kevlinux

8
@kevlinux Los desarrolladores de coreutils solo están implementando el estándar POSIX.
Kusalananda

17
Porque cuando se diseñó, las personas querían ser lo más concisas posible con lo que hacen (por lo tanto, no copiar) y sabían lo que hicieron y cuando cometieron errores no trataron de culpar a las herramientas. Era un tipo de gente totalmente diferente en aquel entonces que hacía computadoras. Es como preguntar por qué un bisturí para un cirujano cardíaco también puede cortarse las manos.
PlasmaHH

44
Unix fue diseñado por y para expertos en informática, asumiendo que el usuario sabía lo que estaba haciendo. El sistema operativo haría exactamente lo que el usuario le ordenara si fuera posible, sin tomar la mano del usuario y sin pedir confirmaciones interminables. Si una operación sobrescribía algo, se suponía que eso era lo que el usuario quería. También recuerde que esto fue a principios de la década de 1970 (antes de MS DOS, Windows y computadoras domésticas), guiar y sostener la mano del usuario en cada paso del camino, aún no era común. Además, con el teletipo mecanizado como terminales, solicitar confirmaciones siempre sería demasiado engorroso.
Baard Kopperud

10
No alias cpa cp -io similares, ya se acostumbrará a tener una red de seguridad, por lo que los sistemas en los que no está disponible (la mayoría de ellos) que mucho más arriesgado. Es mejor que te enseñes a ti mismo rutinariamente, cp -ietc., si eso es lo que prefieres.
Reid

Respuestas:


52

El comportamiento predeterminado de sobrescritura de cpse especifica en POSIX.

  1. Si source_file es de tipo archivo regular, se seguirán los siguientes pasos:

    3.a. El comportamiento no se especifica si existe dest_file y se escribió en un paso anterior. De lo contrario, si existe dest_file, se seguirán los siguientes pasos:

    3.ai Si la opción -i está vigente, la utilidad cp escribirá un mensaje al error estándar y leerá una línea desde la entrada estándar. Si la respuesta no es afirmativa, cp no hará nada más con source_file y continuará con los archivos restantes.

    3.a.ii. Se obtendrá un descriptor de archivo para dest_file realizando acciones equivalentes a la función open () definida en el volumen de Interfaces del sistema de POSIX.1-2017 llamado utilizando dest_file como argumento de ruta, y el OR inclusivo a nivel de bit de O_WRONLY y O_TRUNC como el argumento de retraso.

    3.a.iii. Si el intento de obtener un descriptor de archivo falla y la opción -f está vigente, cp intentará eliminar el archivo realizando acciones equivalentes a la función unlink () definida en el volumen de Interfaces del sistema de POSIX.1-2017 llamado usando dest_file como el argumento del camino. Si este intento tiene éxito, cp continuará con el paso 3b.

Cuando se escribió la especificación POSIX, ya existía una gran cantidad de scripts, con una suposición incorporada para el comportamiento de sobrescritura predeterminado. Muchos de esos scripts fueron diseñados para ejecutarse sin la presencia directa del usuario, por ejemplo, como trabajos cron u otras tareas en segundo plano. Cambiar el comportamiento los habría roto. Revisarlos y modificarlos todos para agregar una opción para forzar la sobrescritura donde sea necesario probablemente se consideró una tarea enorme con beneficios mínimos.

Además, la línea de comandos de Unix siempre se diseñó para permitir que un usuario experimentado trabaje eficientemente, incluso a expensas de una curva de aprendizaje difícil para un principiante. Cuando el usuario ingresa un comando, la computadora debe esperar que el usuario realmente lo diga en serio, sin ninguna duda; Es responsabilidad del usuario tener cuidado con los comandos potencialmente destructivos.

Cuando se desarrolló el Unix original, los sistemas tenían tan poca memoria y almacenamiento masivo en comparación con las computadoras modernas que sobrescriben las advertencias y los avisos que probablemente se consideraron lujos innecesarios y derrochadores.

Cuando se estaba escribiendo el estándar POSIX, el precedente estaba firmemente establecido, y los escritores del estándar eran muy conscientes de las virtudes de no romper la compatibilidad con versiones anteriores .

Además, como otros han descrito, cualquier usuario puede agregar / habilitar esas funciones para sí mismo, utilizando alias de shell o incluso creando un cpcomando de reemplazo y modificando su $PATHpara encontrar el reemplazo antes del comando del sistema estándar, y obtener la red de seguridad de esa manera si deseado.

Pero si lo hace, descubrirá que está creando un peligro para usted. Si el cpcomando se comporta de una manera cuando se usa de manera interactiva y de otra manera cuando se lo llama desde un script, es posible que no recuerde que existe la diferencia. En otro sistema, puede terminar siendo descuidado porque está acostumbrado a las advertencias y avisos en su propio sistema.

Si el comportamiento en las secuencias de comandos aún coincidirá con el estándar POSIX, es probable que se acostumbre a las indicaciones en el uso interactivo, luego escriba una secuencia de comandos que haga algunas copias masivas y luego descubra que está sobrescribiendo algo sin darse cuenta.

Si aplica la solicitud en los scripts también, ¿qué hará el comando cuando se ejecute en un contexto que no tenga ningún usuario, por ejemplo, procesos en segundo plano o trabajos cron? ¿Se bloqueará, cancelará o sobrescribirá el script?

Colgar o abortar significa que una tarea que se suponía que debía hacerse automáticamente no se realizará. No sobrescribir a veces también puede causar un problema en sí mismo: por ejemplo, puede hacer que los datos antiguos sean procesados ​​dos veces por otro sistema en lugar de ser reemplazados por datos actualizados.

Una gran parte del poder de la línea de comando proviene del hecho de que una vez que sepa cómo hacer algo en la línea de comando, implícitamente también sabrá cómo hacer que suceda automáticamente mediante secuencias de comandos . Pero eso solo es cierto si los comandos que usa de forma interactiva también funcionan exactamente igual cuando se invocan en un contexto de script. Cualquier diferencia significativa en el comportamiento entre el uso interactivo y el uso programado creará una especie de disonancia cognitiva que es molesta para un usuario avanzado.


54
"¿Por qué funciona así?" "Porque la norma lo dice". "¿Por qué lo dice el estándar?" "Porque ya funcionó así".
Baptiste Candellier

16
El último párrafo es la verdadera razón. Los cuadros de diálogo de confirmación y las preguntas " ¿Realmente quieres hacer esto? " Son para débiles :-)
TripeHound

@BaptisteCandellier - De acuerdo. Es como si la razón principal estuviera ahí fuera, pero tentadoramente fuera del alcance de esta respuesta.
TED

2
El último párrafo es por qué rm -rfes tan efectivo, incluso si realmente no quisiste ejecutarlo en tu directorio personal ...
Max Vernon

2
@TED ​​Es curioso que nadie mencione cómo el syscall de Unlink (2) también 'falla' en pedirle confirmación a "Madre, ¿puedo?" Cada vez que estas discusiones sempiternas vuelven a levantar sus delicadas cabezas. :)
tchrist

20

cpviene del comienzo de Unix. Estaba allí mucho antes de que se escribiera el estándar Posix. De hecho: Posix acaba de formalizar el comportamiento existente cpa este respecto.

Estamos hablando de la época (1970-01-01), cuando los hombres eran hombres de verdad, las mujeres eran mujeres de verdad y pequeñas criaturas peludas ... (estoy divagando). En esos días, agregar código adicional hacía que un programa fuera más grande. Ese fue un problema entonces, porque la primera computadora que ejecutó Unix fue una PDP-7 (¡actualizable a 144 KB de RAM!). Así que las cosas eran pequeñas, eficientes, sin características de seguridad.

Entonces, en esos días, tenía que saber lo que estaba haciendo, porque la computadora simplemente no tenía el poder para evitar que hiciera algo de lo que luego se arrepintió.

(Hay una bonita caricatura de Zevar; busque "zevar cerveaux assiste par ordinateur" para encontrar la evolución de la computadora. O pruebe http://perinet.blogspirit.com/archive/2012/02/12/zevar-et- cointe.html durante el tiempo que exista)

Para aquellos realmente interesados ​​(vi algunas especulaciones en los comentarios): El original cpen el primer Unix era sobre dos páginas de código ensamblador (C vino más tarde). La parte relevante fue:

sys open; name1: 0; 0   " Open the input file
spa
  jmp error         " File open error
lac o17         " Why load 15 (017) into AC?
sys creat; name2: 0     " Create the output file
spa
  jmp error         " File create error

(Entonces, un duro sys creat)

Y, mientras estamos en eso: se utilizó la versión 2 de Unix (fragmento de código)

mode = buf[2] & 037;
if((fnew = creat(argv[2],mode)) < 0){
    stat(argv[2], buf);

que también es difícil creatsin pruebas ni salvaguardas. ¡Tenga en cuenta que el código C para V2 Unix cpes de menos de 55 líneas!


55
¡Casi correcto, excepto que es " pequeño peludo " (criaturas de Alpha Centauri) no " pequeño peludo "!
TripeHound

1
@TED: es completamente posible las primeras versiones de cpsolo editar openel destino con O_CREAT | O_TRUNCy realizar un read/ writeloop; Claro, con las modernas cpperillas hay tantos botones que básicamente tiene que intentar llegar al statdestino de antemano, y podría verificar fácilmente la existencia primero (y lo hace con cp -i/ cp -n), pero si las expectativas se establecieran a partir de cpherramientas originales y básicas , cambiar ese comportamiento rompería los scripts existentes innecesariamente. No es como si los shells modernos aliasno pudieran simplemente ser cp -iel predeterminado para el uso interactivo después de todo.
ShadowRanger

@ShadowRanger - Hmmm. Tienes toda la razón en que realmente no tengo idea si fue fácil o difícil de hacer. Comentario borrado.
TED

1
@ShadowRanger Sí, pero eso solo empuja la dura lección por el camino hasta que esté en un sistema de producción ...
chrylis -on strike-

1
@sourcejedi: ¡Diversión! No cambia mi teoría básica (que era más fácil abrir incondicionalmente con truncamiento, y creatresulta ser equivalente a open+ O_CREAT | O_TRUNC), pero la falta de O_EXCLexplica por qué no hubiera sido tan fácil manejar los archivos existentes; tratar de hacerlo sería intrínsecamente picante (básicamente tendría que open/ statverificar la existencia, luego usar creat, pero en sistemas compartidos grandes, siempre es posible cuando llega el momento creat, alguien más hizo el archivo y ahora ha volado) de todos modos). También podría sobrescribir incondicionalmente.
ShadowRanger

19

Debido a que estos comandos también están destinados a ser utilizados en scripts, posiblemente ejecutados sin ningún tipo de supervisión humana, y también porque hay muchos casos en los que realmente desea sobrescribir el objetivo (la filosofía de los shells de Linux es que el humano sabe qué ella está haciendo)

Todavía hay algunas salvaguardas:

  • GNU cptiene un -n| --no-clobberopción
  • Si copia varios archivos en uno solo, cpse quejará de que el último no es un directorio.

Esto solo se aplica a la implementación específica de un proveedor y la pregunta no era sobre la implementación específica de ese proveedor.
schily

10

¿Es "hacer una cosa a la vez"?

Este comentario suena como una pregunta sobre un principio de diseño general. A menudo, las preguntas sobre estos son muy subjetivas, y no podemos escribir una respuesta adecuada. Tenga en cuenta que podemos cerrar preguntas en este caso.

A veces tenemos una explicación para la elección del diseño original, porque los desarrolladores han escrito sobre ellas. Pero no tengo una buena respuesta para esta pregunta.

¿Por qué cpestá diseñado de esta manera?

El problema es que Unix tiene más de 40 años.

Si estuviera creando un nuevo sistema ahora, podría hacer diferentes elecciones de diseño. Pero cambiar Unix rompería los scripts existentes, como se menciona en otras respuestas.

¿Por qué se cp diseñó para sobrescribir silenciosamente los archivos existentes?

La respuesta corta es "No sé" :-).

Comprende que ese cpes solo un problema. Creo que ninguno de los programas de comando originales está protegido contra la sobrescritura o eliminación de archivos. El shell tiene un problema similar al redirigir la salida:

$ cat first.html > second.html

Este comando también sobrescribe en silencio second.html.

Me interesa pensar cómo se podrían rediseñar todos estos programas. Puede requerir cierta complejidad adicional.

Creo que esto es parte de la explicación: los primeros Unix enfatizaron implementaciones simples . Para una explicación más detallada de esto, vea "peor es mejor", vinculado al final de esta respuesta.

Puede cambiar > second.htmlpara que se detenga con un error, si second.htmlya existe. Sin embargo, como hemos mencionado, a veces el usuario no desea reemplazar un archivo existente. Por ejemplo, puede estar construyendo un comando complejo, intentando varias veces hasta que haga lo que quiere.

El usuario podría correr rm second.htmlprimero si lo necesita. ¡Esto podría ser un buen compromiso! Tiene algunas posibles desventajas propias.

  1. El usuario debe escribir el nombre de archivo dos veces.
  2. Las personas también tienen muchos problemas para usar rm. Así que me gustaría hacer rmmás seguro también. ¿Pero cómo? Si hacemos rmmostrar cada nombre de archivo y le pedimos al usuario que confirme, ahora tiene que escribir tres líneas de comandos en lugar de una. Además, si tiene que hacer esto con demasiada frecuencia, se acostumbrará y escribirá "y" para confirmar sin pensar. Por lo tanto, podría ser muy molesto y aún podría ser peligroso.

En un sistema moderno, recomiendo instalar el trashcomando y usarlo en lugar de rmdonde sea posible. La introducción del almacenamiento de basura fue una gran idea, por ejemplo, para una PC gráfica de usuario único .

Creo que también es importante comprender las limitaciones del hardware original de Unix: RAM y espacio en disco limitados, salida mostrada en impresoras lentas , así como el sistema y el software de desarrollo.

Observe que el Unix original no tenía finalización de tabulación , para completar rápidamente un nombre de archivo para un rmcomando. (Además, el shell Bourne original no tiene historial de comandos, por ejemplo, como cuando usa la tecla de flecha hacia arriba bash).

Con la salida de impresora, se usaría editor basado en línea, ed. Esto es más difícil de aprender que un editor de texto visual. Debe imprimir algunas líneas actuales, decidir cómo desea cambiarlas y escribir un comando de edición.

Usar > second.htmles un poco como usar un comando en un editor de línea. El efecto que tiene depende del estado actual. (Si second.htmlya existe, su contenido será descartado). Si el usuario no está seguro del estado actual, se espera que se ejecute lso que ls second.htmlprimero.

"Implementación simple" como principio de diseño

Hay una interpretación popular del diseño de Unix, que comienza:

El diseño debe ser simple, tanto en la implementación como en la interfaz. Es más importante que la implementación sea simple que la interfaz. La simplicidad es la consideración más importante en un diseño.

...

Gabriel argumentó que "Peor es mejor" produjo un software más exitoso que el enfoque MIT: siempre que el programa inicial sea básicamente bueno, llevará mucho menos tiempo y esfuerzo implementarlo inicialmente y será más fácil adaptarse a nuevas situaciones. Portar software a nuevas máquinas, por ejemplo, se vuelve mucho más fácil de esta manera. Por lo tanto, su uso se extenderá rápidamente, mucho antes de que un [mejor] programa tenga la oportunidad de desarrollarse y desplegarse (ventaja de primer jugador).

https://en.wikipedia.org/wiki/Worse_is_better


¿Por qué se sobrescribe el objetivo con cpun "problema"? Hacer que interactivamente solicite permiso o que falle puede ser un "problema" tan grande como ese.
Kusalananda

Wow gracias. Complemente la guía: 1) Escriba programas que hagan una cosa y que lo hagan bien. 2) Confía en el programador.
Álgebra

2
La pérdida de datos de @Kusalananda es un problema. Personalmente, estoy interesado en reducir el riesgo de perder datos. Hay varios enfoques para esto. Decir que es un problema no significa que las alternativas tampoco tengan problemas.
sourcejedi

1
@riderdragon Los programas escritos en lenguaje C a menudo pueden fallar de maneras muy sorprendentes, porque C confía en el programador. Pero los programadores simplemente no son tan confiables. Tenemos que escribir herramientas muy avanzadas, como valgrind , que son necesarias para tratar de encontrar los errores que cometen los programadores. Creo que es importante tener lenguajes de programación como Rust o Python o C # que intenten forzar la "seguridad de la memoria" sin confiar en el programador. (El lenguaje C fue creado por uno de los autores de UNIX, para escribir UNIX en un lenguaje portátil).
sourcejedi

1
Aún mejor cat first.html second.html > first.html, dará como resultado que first.htmlse sobrescriba solo con el contenido second.html. El contenido original se pierde para siempre.
doneal24

9

El diseño de "cp" se remonta al diseño original de Unix. De hecho, había una filosofía coherente detrás del diseño de Unix, que ha sido un poco menos de lo que se ha referido en tono de broma como Peor-es-Mejor * .

La idea básica es que mantener el código simple es en realidad una consideración de diseño más importante que tener una interfaz perfecta o "hacer lo correcto".

  • Simplicidad: el diseño debe ser simple, tanto en la implementación como en la interfaz. Es más importante que la implementación sea simple que la interfaz . La simplicidad es la consideración más importante en un diseño.

  • Corrección: el diseño debe ser correcto en todos los aspectos observables. Es un poco mejor ser simple que correcto.

  • Consistencia: el diseño no debe ser demasiado inconsistente. La consistencia se puede sacrificar por simplicidad en algunos casos, pero es mejor descartar aquellas partes del diseño que se ocupan de circunstancias menos comunes que introducir complejidad de implementación o inconsistencia.

  • Integridad: el diseño debe cubrir tantas situaciones importantes como sea práctico. Todos los casos razonablemente esperados deben estar cubiertos. La integridad se puede sacrificar en favor de cualquier otra cualidad. De hecho, la integridad debe sacrificarse siempre que se ponga en peligro la simplicidad de implementación. La consistencia se puede sacrificar para lograr la integridad si se conserva la simplicidad; especialmente inútil es la consistencia de la interfaz.

( énfasis mío )

Recordando que esto era 1970, el caso de uso de "Quiero copiar este archivo solo si aún no existe" habría sido un caso de uso bastante raro para alguien que realiza una copia. Si eso es lo que querías, serías capaz de verificar antes de la copia, y eso incluso puede ser programado.

En cuanto a por qué un sistema operativo con ese enfoque de diseño resultó ser el que ganó a todos los demás sistemas operativos que se construyeron en ese momento, el autor del ensayo también tenía una teoría para eso.

Un beneficio adicional de la filosofía de peor es mejor es que el programador está condicionado a sacrificar algo de seguridad, conveniencia y molestia para obtener un buen rendimiento y un uso modesto de los recursos. Los programas escritos con el enfoque de Nueva Jersey funcionarán bien tanto en máquinas pequeñas como en máquinas grandes, y el código será portátil porque está escrito sobre un virus.

Es importante recordar que el virus inicial tiene que ser básicamente bueno. Si es así, la propagación viral está asegurada siempre que sea portátil. Una vez que el virus se haya propagado, habrá presión para mejorarlo, posiblemente aumentando su funcionalidad más cerca del 90%, pero los usuarios ya han sido condicionados para aceptar algo peor que lo correcto. Por lo tanto, el software peor es mejor primero ganará aceptación, segundo condicionará a sus usuarios a esperar menos, y tercero mejorará a un punto que es casi lo correcto.

* - o lo que el autor, pero nadie más, llamó "El enfoque de Nueva Jersey" .


1
Esta es la respuesta correcta.
tchrist

+1, pero creo que sería útil tener un ejemplo concreto. Cuando instala una nueva versión de un programa que ha editado y compilado de nuevo (y tal vez probado :-), deliberadamente desea sobrescribir la versión anterior del programa. (Y es probable que desee un comportamiento similar de su compilador. Tan temprano sólo para UNIX tiene creat()vs open(). open()No ha podido crear un archivo si no existiera. Sólo se necesita 0/1/2 para lectura / escritura / ambas cosas. No adoptan, sin embargo O_CREAT, y no hay O_EXCL)
sourcejedi

@sourcejedi - Lo siento, pero como desarrollador de software, honestamente no puedo pensar en otro escenario que aquel en el que estaría haciendo una copia. :-)
TED

@TED ​​lo siento, quiero decir que estoy sugiriendo este ejemplo, como uno de los casos no raros en los que definitivamente quieres sobrescribir, en comparación con la comparación en la pregunta donde tal vez no lo hiciste.
sourcejedi

0

La razón principal es que una GUI es, por definición, interactiva, mientras que un binario como /bin/cpes solo un programa al que se puede llamar desde todo tipo de lugares, por ejemplo desde su GUI ;-). Apuesto a que incluso hoy la gran mayoría de las llamadas /bin/cpno serán desde un terminal real con un usuario escribiendo un comando de shell, sino desde un servidor HTTP o un sistema de correo o un NAS. Una protección incorporada contra errores del usuario tiene mucho sentido en un entorno interactivo; menos en un binario simple. Por ejemplo, lo más probable es que su GUI llame /bin/cpen segundo plano para realizar las operaciones reales y ¡tenga que ocuparse de las preguntas de seguridad en la salida estándar a pesar de que solo le preguntó al usuario!

Tenga en cuenta que fue desde el primer día casi trivial escribir un envoltorio seguro /bin/cpsi así lo desea. La filosofía * nix es proporcionar bloques de construcción simples para los usuarios: de estos, /bin/cpes uno.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.