Por que o `cp` foi projetado para substituir silenciosamente os arquivos existentes? [fechadas]


30

Eu testei cpcom os seguintes comandos:

$ ls
first.html   second.html  third.html

$ cat first.html
first

$ cat second.html
second

$ cat third.html
third

Então eu copio first.htmlpara second.html:

$ cp first.html second.html

$ cat second.html
first

O arquivo second.htmlé substituído silenciosamente, sem erros. No entanto, se eu fizer isso em uma GUI da área de trabalho, arrastando e soltando um arquivo com o mesmo nome, o sufixo será first1.htmlautomaticamente. Isso evita a substituição acidental de um arquivo existente.

Por que não cpsegue esse padrão em vez de sobrescrever arquivos silenciosamente?


10
Imagino que apenas os designers do coreutils possam realmente responder à pergunta, mas é assim que funciona por enquanto. Geralmente, os aplicativos são criados, assumindo que o usuário realmente significa o que está fazendo e para minimizar as solicitações extras. Se você deseja alterar o comportamento, alias 'cp' para 'cp -i' ou 'cp -n'.
Kevlinux # 24/18

8
@kevlinux Os desenvolvedores do coreutils estão apenas implementando o padrão POSIX.
Kusalananda

17
Porque quando foi projetado, as pessoas queriam ser o mais concisas possível com o que fazem (portanto, não copiam) e sabiam o que faziam e, quando cometiam erros, não tentavam culpar as ferramentas. Era um tipo totalmente diferente de pessoas naquela época que criava computadores. É como perguntar por que um bisturi para um cirurgião cardíaco também pode cortar as mãos.
PlasmaHH 24/10

4
O Unix foi projetado por e para especialistas em computação, com a suposição de que o usuário sabia o que estava fazendo. O sistema operacional faria exatamente o que o usuário dissesse, se possível - sem segurar a mão do usuário e sem pedir confirmações sem fim. Se uma operação sobrescritasse algo, supunha-se que era isso que o usuário queria. Lembre-se também de que este foi o início dos anos 70 - pré-MS DOS, Windows e computadores domésticos - guiando e segurando a mão do usuário a cada passo do caminho, ainda não era comum. Além disso, com o teletipo usinado como terminais, solicitar confirmações sempre seria muito complicado.
Baard Kopperud

10
Não apelido cppara cp -iou similar, porque você vai se acostumar a ter uma rede de segurança, fazendo com que os sistemas em que ele não está disponível (a maioria deles) que muito mais arriscado. Melhor ensinar-se a rotineiramente cp -ietc., se preferir.
Reid

Respostas:


52

O comportamento de substituição padrão de cpé especificado no POSIX.

  1. Se source_file for do tipo regular file, as seguintes etapas deverão ser seguidas:

    3.a. O comportamento não é especificado se dest_file existe e foi gravado por uma etapa anterior. Caso contrário, se existir dest_file, as seguintes etapas deverão ser tomadas:

    3.ai Se a opção -i estiver em vigor, o utilitário cp deve escrever um prompt para o erro padrão e ler uma linha da entrada padrão. Se a resposta não for afirmativa, o cp não fará mais nada com o source_file e continuará com os arquivos restantes.

    3.a.ii. Um descritor de arquivo para dest_file deve ser obtido executando ações equivalentes à função open () definida no volume System Interfaces do POSIX.1-2017 chamado usando dest_file como argumento do caminho e o OR bit a bit inclusivo de O_WRONLY e O_TRUNC como o argumento oflag.

    3.a.iii. Se a tentativa de obter um descritor de arquivo falhar e a opção -f estiver em vigor, o cp tentará remover o arquivo executando ações equivalentes à função unlink () definida no volume de interfaces do sistema do POSIX.1-2017 chamado usando dest_file como o argumento do caminho. Se essa tentativa for bem-sucedida, cp continuará com a etapa 3b.

Quando a especificação POSIX foi gravada, já existia um grande número de scripts, com uma suposição interna para o comportamento de substituição padrão. Muitos desses scripts foram projetados para serem executados sem a presença direta do usuário, por exemplo, como tarefas cron ou outras tarefas em segundo plano. Mudar o comportamento os teria quebrado. A revisão e modificação de todas elas para adicionar uma opção para forçar a substituição sempre que necessário foi provavelmente considerada uma tarefa enorme, com benefícios mínimos.

Além disso, a linha de comando do Unix sempre foi projetada para permitir que um usuário experiente trabalhe com eficiência, mesmo à custa de uma curva de aprendizado difícil para um iniciante. Quando o usuário digita um comando, o computador deve esperar que o usuário realmente o queira dizer, sem nenhuma adivinhação; é responsabilidade do usuário tomar cuidado com os comandos potencialmente destrutivos.

Quando o Unix original foi desenvolvido, os sistemas tinham tão pouca memória e armazenamento em massa em comparação com os computadores modernos que substituem avisos e avisos provavelmente foram vistos como luxos desnecessários e desnecessários.

Quando o padrão POSIX estava sendo escrito, o precedente foi firmemente estabelecido, e os escritores do padrão estavam bem cientes das virtudes de não quebrar a compatibilidade com versões anteriores .

Além disso, como outros já descreveram, qualquer usuário pode adicionar / ativar esses recursos por si próprio, usando aliases de shell ou mesmo criando um cpcomando de substituição e modificando-os $PATHpara encontrar a substituição antes do comando do sistema padrão e obter a rede de segurança dessa maneira se desejado.

Mas se você fizer isso, descobrirá que está criando um risco para si mesmo. Se o cpcomando se comportar de uma maneira quando usado interativamente e de outra maneira quando chamado de um script, talvez você não se lembre de que a diferença existe. Em outro sistema, você pode acabar sendo descuidado porque está acostumado com os avisos e solicitações do seu próprio sistema.

Se o comportamento nos scripts ainda corresponder ao padrão POSIX, é provável que você se acostume com os prompts no uso interativo, em seguida, escreva um script que faça algumas cópias em massa - e descubra que você novamente substituiu algo inadvertidamente.

Se você aplicar o prompt também aos scripts, o que o comando fará quando executado em um contexto que não tenha usuário por perto, por exemplo, processos em segundo plano ou tarefas cron? O script travará, abortará ou substituirá?

Interromper ou interromper significa que uma tarefa que deveria ser executada automaticamente não será executada. A não substituição às vezes também pode causar um problema por si só: por exemplo, pode fazer com que os dados antigos sejam processados ​​duas vezes por outro sistema, em vez de serem substituídos por dados atualizados.

Uma grande parte do poder da linha de comando vem do fato de que, depois que você souber fazer algo na linha de comando, também implicitamente saberá como fazer isso acontecer automaticamente por meio de scripts . Mas isso só é verdade se os comandos que você usa interativamente também funcionarem exatamente da mesma maneira quando invocados em um contexto de script. Quaisquer diferenças significativas no comportamento entre uso interativo e uso de script criarão uma espécie de dissonância cognitiva que é irritante para um usuário avançado.


54
"Por que funciona assim?" "Porque o padrão diz isso." "Por que o padrão diz isso?" "Porque já funcionou assim."
Baptiste Candellier

16
O último parágrafo é a verdadeira razão. Diálogos de confirmação e " Você realmente quer fazer isso? " Instruções são para os fracos :-)
TripeHound

@BaptisteCandellier - Concordado. É como se a razão final estivesse lá fora, mas tentadoramente fora do alcance desta resposta.
TED

2
Esse último parágrafo é por isso que rm -rfé tão eficaz, mesmo se você realmente não quer dizer para executá-lo em seu diretório home ...
Max Vernon

2
@TED ​​Engraçado como ninguém menciona como o syscall (2) também desvenda 'pedir ' Mãe, posso? ' Por confirmação sempre que essas discussões semipiternas novamente empinam suas cabeças delicadas. :)
tchrist

20

cpvem do começo do Unix. Estava lá bem antes de o padrão Posix ser escrito. De fato: Posix acabou de formalizar o comportamento existente a cpesse respeito.

Estamos falando de Epoch (01-01-1970), quando homens eram homens de verdade, mulheres eram mulheres de verdade e criaturinhas peludas ... (eu discordo). Naqueles dias, adicionar código extra tornava um programa maior. Esse era um problema, pois o primeiro computador que executava o Unix era o PDP-7 (atualizável para 144KB de RAM!). Então, as coisas eram pequenas, eficientes, sem recursos de segurança.

Então, naquela época, você tinha que saber o que estava fazendo, porque o computador simplesmente não tinha o poder de impedir que você fizesse qualquer coisa que se arrependesse mais tarde.

(Há um belo desenho animado de Zevar; pesquise "zevar cerveaux assiste par ordinateur" para encontrar a evolução do computador. Ou tente http://perinet.blogspirit.com/archive/2012/02/12/zevar-et- cointe.html enquanto existir)

Para aqueles realmente interessados ​​(vi algumas especulações nos comentários): O original cpno primeiro Unix tinha cerca de duas páginas de código assembler (C veio mais tarde). A parte relevante foi:

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

(Então, um duro sys creat)

E, enquanto estamos nisso: Versão 2 do Unix usada (sniplet de código)

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

o que também é difícil creatsem testes ou salvaguardas. Observe que o código C do V2 Unix cpé inferior a 55 linhas!


5
Quase correto, excpr é " peludo pequeno " (criaturas de Alpha Centauri) e não " peludo pequeno "!
TripeHound 24/10/19

1
@TED: É inteiramente possível primeiras versões de cpapenas opened o destino com O_CREAT | O_TRUNCe realizou um read/ writeciclo; claro, com os modernos cpexistem tantos botões que ele basicamente precisa tentar stato destino com antecedência e pode facilmente verificar a existência primeiro (e faz com cp -i/ cp -n), mas se as expectativas foram estabelecidas a partir de cpferramentas originais e simples , mudando esse comportamento iria quebrar scripts existentes desnecessariamente. Afinal , não é como as conchas modernas que aliasnão podem simplesmente tornar cp -io padrão para uso interativo.
ShadowRanger

@ShadowRanger - Hmmm. Você está certo que eu realmente não tenho idéia se foi fácil ou difícil de fazer. Comentário deletado.
TED

1
@ShadowRanger Sim, mas então isso é apenas empurrar a dura lição na estrada até que esteja em um sistema de produção ...
chrylis -on strike-

1
@sourcejedi: Fun! Não muda minha teoria básica (que era mais fácil abrir incondicionalmente com truncamento e createquivale a open+ O_CREAT | O_TRUNC), mas a falta de O_EXCLexplica porque não seria tão fácil lidar com arquivos existentes; tentar fazer isso seria inerentemente atrevido (você basicamente teria que open/ statverificar a existência e depois usá-lo creat, mas em grandes sistemas compartilhados, sempre é possível quando você chega creat, alguém criou o arquivo e agora de qualquer maneira). Também pode sobrescrever incondicionalmente.
ShadowRanger

19

Como esses comandos também devem ser usados ​​em scripts, possivelmente executando sem qualquer tipo de supervisão humana, e também porque há muitos casos em que você realmente deseja substituir o destino (a filosofia dos shells do Linux é que o humano sabe o que ela esta fazendo)

Ainda existem algumas salvaguardas:

  • GNU cptem um -n| --no-clobberopção
  • se você copiar vários arquivos para um único cp, reclamará que o último não é um diretório.

Isso se aplica apenas a uma implementação específica do fornecedor e a pergunta não era sobre essa implementação específica do fornecedor.
schily

10

É "fazer uma coisa ao mesmo tempo"?

Esse comentário parece uma pergunta sobre um princípio geral de design. Freqüentemente, perguntas sobre isso são muito subjetivas e não somos capazes de escrever uma resposta adequada. Esteja avisado de que podemos fechar perguntas neste caso.

Às vezes, temos uma explicação para a escolha do design original, porque os desenvolvedores escreveram sobre eles. Mas não tenho uma resposta tão boa para esta pergunta.

Por que cpé projetado dessa maneira?

O problema é que o Unix tem mais de 40 anos.

Se você estava criando um novo sistema agora, poderá fazer diferentes escolhas de design. Mas mudar o Unix quebraria os scripts existentes, como mencionado em outras respostas.

Por que foi cp projetado para substituir silenciosamente os arquivos existentes?

A resposta curta é "não sei" :-).

Entenda que cpé apenas um problema. Eu acho que nenhum dos programas de comando originais protegidos contra a substituição ou exclusão de arquivos. O shell tem um problema semelhante ao redirecionar a saída:

$ cat first.html > second.html

Este comando também substitui silenciosamente second.html.

Estou interessado em pensar em como todos esses programas poderiam ser redesenhados. Pode exigir alguma complexidade extra.

Eu acho que isso faz parte da explicação: o Unix inicial enfatizava implementações simples . Para uma explicação mais detalhada disso, consulte "pior é melhor", vinculado ao final desta resposta.

Você pode alterar > second.htmlpara parar com um erro, se second.htmljá existir. No entanto, como dissemos, por vezes, o usuário não deseja substituir um arquivo existente. Por exemplo, ela pode estar construindo um comando complexo, tentando várias vezes até que faça o que deseja.

O usuário pode executar rm second.htmlprimeiro se precisar. Este pode ser um bom compromisso! Possui algumas desvantagens possíveis.

  1. O usuário deve digitar o nome do arquivo duas vezes.
  2. As pessoas também têm muitos problemas para usar rm. Então, eu gostaria de tornar rmmais seguro também. Mas como? Se rmmostrarmos cada nome de arquivo e solicitarmos que o usuário confirme, agora ela precisará escrever três linhas de comandos em vez de uma. Além disso, se ela precisar fazer isso com muita frequência, ela adquirirá o hábito e digitar "y" para confirmar sem pensar. Por isso, pode ser muito chato e ainda pode ser perigoso.

Em um sistema moderno, recomendo instalar o trashcomando e usá-lo em vez de rmonde for possível. A introdução do armazenamento do Lixo foi uma ótima idéia, por exemplo, para um PC gráfico de usuário único .

Eu acho que também é importante entender as limitações do hardware Unix original - RAM e espaço em disco limitados, saída exibida em impressoras lentas , bem como o sistema e o software de desenvolvimento.

Observe que o Unix original não tinha preenchimento de tabulação , para preencher rapidamente um nome de arquivo para um rmcomando. (Além disso, o shell Bourne original não possui histórico de comandos, por exemplo, quando você usa a tecla de seta para cima bash).

Com a saída da impressora, você usaria o editor baseado em linha ed,. Isso é mais difícil de aprender do que um editor de texto visual. Você precisa imprimir algumas linhas atuais, decidir como deseja alterá-las e digitar um comando de edição.

Usar > second.htmlé um pouco como usar um comando em um editor de linha. O efeito que isso depende depende do estado atual. (Se second.htmljá existir, seu conteúdo será descartado). Se o usuário não tiver certeza do estado atual, é esperado que ele seja executado lsou ls second.htmlprimeiro.

"Implementação simples" como princípio de design

Existe uma interpretação popular do design do Unix, que começa:

O design deve ser simples, tanto na implementação quanto na interface. É mais importante que a implementação seja simples que a interface. Simplicidade é a consideração mais importante em um design.

...

Gabriel argumentou que "Pior é melhor" produziu software mais bem-sucedido do que a abordagem do MIT: Enquanto o programa inicial for basicamente bom, levará muito menos tempo e esforço para implementar inicialmente e será mais fácil se adaptar a novas situações. Portar software para novas máquinas, por exemplo, fica muito mais fácil dessa maneira. Assim, seu uso se espalhará rapidamente, muito antes de um programa [melhor] ter a chance de ser desenvolvido e implantado (vantagem do primeiro a mover).

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


Por que substituir o destino com cpum "problema"? Tê-lo interativamente pedindo permissão ou falhar pode ser um "problema" tão grande quanto esse.
Kusalananda

uau, obrigada. complementar a diretriz: 1) Escreva programas que façam uma coisa e façam bem. 2) Confie no programador.
Álgebra

2
A perda de dados do @Kusalananda é um problema. Pessoalmente, estou interessado em reduzir o risco de perder dados. Existem várias abordagens para isso. Dizer que é um problema não significa que as alternativas também não têm problemas.
sourcejedi

1
@riderdragon Os programas escritos na linguagem C geralmente podem falhar de maneiras surpreendentes, porque C confia no programador. Mas os programadores não são tão confiáveis. Temos que escrever ferramentas muito avançadas, como o valgrind , necessárias para tentar encontrar os erros que os programadores cometem. Eu acho que é importante ter linguagens de programação como Rust ou Python ou C # que tentam impor "segurança de memória" sem confiar no programador. (A linguagem C foi criada por um dos autores do UNIX, para escrever o UNIX em um idioma portátil).
sourcejedi

1
Melhor ainda é cat first.html second.html > first.htmldar resultado em first.htmlser substituído pelo conteúdo de second.htmlapenas. O conteúdo original é perdido para sempre.
doneal24

9

O design do "cp" remonta ao design original do Unix. De fato, havia uma filosofia coerente por trás do design do Unix, que foi um pouco menos do que, de brincadeira, foi referido como Pior-é-Melhor * .

A idéia básica é que manter o código simples é realmente uma consideração de design mais importante do que ter uma interface perfeita ou "fazer a coisa certa".

  • Simplicidade - o design deve ser simples, tanto na implementação quanto na interface. É mais importante que a implementação seja simples que a interface . Simplicidade é a consideração mais importante em um design.

  • Correção - o projeto deve estar correto em todos os aspectos observáveis. É um pouco melhor ser simples do que correto.

  • Consistência - o design não deve ser excessivamente inconsistente. A consistência pode ser sacrificada pela simplicidade em alguns casos, mas é melhor descartar as partes do design que lidam com circunstâncias menos comuns do que introduzir complexidade ou inconsistência na implementação.

  • Completude - o design deve abranger o maior número possível de situações importantes. Todos os casos razoavelmente esperados devem ser cobertos. A integridade pode ser sacrificada em favor de qualquer outra qualidade. De fato, a integridade deve ser sacrificada sempre que a simplicidade da implementação é comprometida. A consistência pode ser sacrificada para alcançar a integridade se a simplicidade for mantida; especialmente inútil é a consistência da interface.

( ênfase minha )

Lembrando que isso foi em 1970, o caso de uso de "Quero copiar este arquivo apenas se ele ainda não existir" teria sido um caso de uso bastante raro para alguém que está executando uma cópia. Se era isso que você queria, seria capaz de verificar antes da cópia, e isso pode até ser roteirizado.

Quanto ao motivo pelo qual um SO com essa abordagem de design foi o vencedor em todos os outros SOs que estavam sendo construídos na época, o autor do ensaio também tinha uma teoria para isso.

Um benefício adicional da filosofia do pior é o melhor é que o programador está condicionado a sacrificar alguma segurança, conveniência e aborrecimento para obter bom desempenho e uso modesto de recursos. Os programas escritos usando a abordagem de Nova Jersey funcionarão bem em máquinas pequenas e grandes, e o código será portátil porque está gravado em cima de um vírus.

É importante lembrar que o vírus inicial deve ser basicamente bom. Nesse caso, a propagação viral é garantida desde que seja portátil. Depois que o vírus se espalhar, haverá pressão para melhorá-lo, possivelmente aumentando sua funcionalidade para perto de 90%, mas os usuários já foram condicionados a aceitar coisas piores que as certas. Portanto, o pior é o primeiro a ganhar aceitação, o segundo condicionará seus usuários a esperar menos e o terceiro será aprimorado a um ponto que é quase a coisa certa.

* - ou o que o autor, mas mais ninguém, chamou de "A abordagem de Nova Jersey" .


1
Esta é a resposta certa.
tchrist

+1, mas acho que ajudaria a ter um exemplo concreto. Quando você instala uma nova versão de um programa que editou e recompilou (e talvez testou :-), você deseja sobrescrever deliberadamente a versão antiga do programa. (E você provavelmente quer um comportamento semelhante do seu compilador. Tão cedo UNIX só tem creat()vs open(). open()Não pôde criar um arquivo se ele não existisse. Leva apenas 0/1/2 para leitura / gravação / ambos. Ele não ainda tomar O_CREAT, e não há O_EXCL).
sourcejedi

@sourcejedi - Desculpe, mas como desenvolvedor de software, honestamente, não consigo pensar em outro cenário além daquele em que eu estaria fazendo uma cópia. :-)
TED

@TED ​​desculpe, quero dizer que estou sugerindo este exemplo, como um dos casos não raros em que você definitivamente deseja uma substituição, versus a comparação na pergunta em que talvez você não queira.
sourcejedi

0

A principal razão é que uma GUI é, por definição, interativa, enquanto uma binária /bin/cpé apenas um programa que pode ser chamado de todos os tipos de lugares, por exemplo, na sua GUI ;-). Aposto que ainda hoje a grande maioria das chamadas /bin/cpnão será de um terminal real com um usuário digitando um comando shell, mas de um servidor HTTP ou de um sistema de email ou de um NAS. Uma proteção interna contra erros do usuário faz todo sentido em um ambiente interativo; menos ainda em um binário simples. Por exemplo, sua GUI provavelmente chamará /bin/cpo plano de fundo para executar as operações reais e terá que lidar com as questões de segurança na saída padrão, mesmo que apenas peça ao usuário!

Observe que, desde o primeiro dia, era quase trivial escrever um invólucro seguro, /bin/cpse desejado. A filosofia * nix é fornecer blocos de construção simples para os usuários: um deles /bin/cpé um deles.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.