Por que o 'cp' foi projetado para sobrescrever arquivos existentes? [fechadas]

29

Eu testei cp com os seguintes comandos:

$ ls
first.html   second.html  third.html

$ cat first.html
first

$ cat second.html
second

$ cat third.html
third

Em seguida, copio first.html para second.html :

$ cp first.html second.html

$ cat second.html
first

O arquivo second.html é sobrescrito silenciosamente, sem erros. No entanto, se eu fizer isso em uma GUI de desktop arrastando e soltando um arquivo com o mesmo nome, ele será sufixado como first1.html automaticamente. Isso evita a substituição acidental de um arquivo existente.

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

    
por avirate 24.10.2018 / 05:53

6 respostas

51

O comportamento de sobregravação padrão de cp é especificado em POSIX.

  1. If source_file is of type regular file, the following steps shall be taken:

    3.a. The behavior is unspecified if dest_file exists and was written by a previous step. Otherwise, if dest_file exists, the following steps shall be taken:

    3.a.i. If the -i option is in effect, the cp utility shall write a prompt to the standard error and read a line from the standard input. If the response is not affirmative, cp shall do nothing more with source_file and go on to any remaining files.

    3.a.ii. A file descriptor for dest_file shall be obtained by performing actions equivalent to the open() function defined in the System Interfaces volume of POSIX.1-2017 called using dest_file as the path argument, and the bitwise-inclusive OR of O_WRONLY and O_TRUNC as the oflag argument.

    3.a.iii. If the attempt to obtain a file descriptor fails and the -f option is in effect, cp shall attempt to remove the file by performing actions equivalent to the unlink() function defined in the System Interfaces volume of POSIX.1-2017 called using dest_file as the path argument. If this attempt succeeds, cp shall continue with step 3b.

Quando a especificação POSIX foi escrita, já existia um grande número de scripts, com uma suposição incorporada para o comportamento padrão de sobrescrever. Muitos desses scripts foram projetados para serem executados sem a presença direta do usuário, por exemplo, como cron jobs ou outras tarefas em segundo plano. Mudar o comportamento teria quebrado eles. Revendo e modificando todos eles para adicionar uma opção para forçar a sobrescrever onde quer que seja necessário, provavelmente foi considerado uma tarefa enorme com benefícios mínimos.

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

Quando o Unix original foi desenvolvido, os sistemas tinham tão pouca memória e armazenamento em massa que os computadores modernos que sobrescrevem avisos e avisos eram provavelmente 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 retroativa .

Além disso, como outros descreveram, qualquer usuário pode adicionar / ativar esses recursos para si mesmo, usando aliases de shell ou até mesmo criando um comando cp de substituição e modificando seu $PATH para localizar a substituição antes do comando padrão do sistema e obtenha a rede de segurança dessa forma, se desejar.

Mas se você fizer isso, descobrirá que está criando um risco para si mesmo. Se o comando cp 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 aos avisos e prompts de seu próprio sistema.

Se o comportamento em scripts ainda corresponder ao padrão POSIX, você provavelmente se acostumará aos prompts em uso interativo, depois escreverá um script que faz alguma cópia em massa - e então descobrirá que você está novamente sobrescrito inadvertidamente.

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

Suspender ou interromper significa que uma tarefa que deveria ser executada automaticamente não será executada. A não substituição pode, às vezes, também causar um problema: por exemplo, pode fazer com que 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 uma vez que você saiba como fazer algo na linha de comando, você implicitamente também saberá como fazer isso automaticamente através 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.

    
por 24.10.2018 / 07:27
20

cp vem do início do Unix. Estava lá bem antes que o padrão de Posix fosse escrito. De fato: Posix acaba de formalizar o comportamento existente de cp a esse respeito.

Estamos falando de Epoch (1970-01-01), quando os homens eram homens de verdade, mulheres eram mulheres reais e pequenas criaturas peludas ... (divago). Naquela época, adicionar código extra tornava o programa maior. Isso foi um problema, porque o primeiro computador que executava o Unix era um PDP-7 (atualizável para 144KB de RAM!). Então as coisas eram pequenas, eficientes, sem características de segurança.

Então, naqueles dias, você tinha que saber o que estava fazendo, porque o computador simplesmente não tinha o poder de impedir que você fizesse algo do qual se arrependeu mais tarde.

(Há um bom desenho animado de Zevar; procure por "zevar cerveaux assiste par ordinateur" para encontrar a evolução do computador. Ou tente link enquanto existir)

Para aqueles realmente interessados (eu vi algumas especulações nos comentários): O original cp no primeiro Unix era de cerca de duas páginas de código assembler (C veio depois). 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 disco sys creat )

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

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

que também é um creat difícil sem testes ou salvaguardas. Note que o código C para o V2 Unix de cp é menor que 55 linhas!

    
por 24.10.2018 / 11:19
18

Como esses comandos também devem ser usados em scripts, possivelmente em execução sem qualquer tipo de supervisão humana, e também porque há muitos casos em que você realmente deseja sobrescrever o alvo (a filosofia dos shells do Linux é que o o humano sabe o que está fazendo)

Existem ainda algumas salvaguardas:

  • O GNU cp tem uma opção -n | --no-clobber
  • se você copiar vários arquivos para um único, cp reclamará que o último não é um diretório.
por 24.10.2018 / 09:20
10

Is it "do one thing at one time"?

Esse comentário soa como uma pergunta sobre um princípio geral de design. Freqüentemente, as perguntas sobre elas são muito subjetivas e não podemos escrever uma resposta adequada. Esteja avisado que podemos fechar questões neste caso.

Por vezes, temos uma explicação para a escolha do design original, porque o (s) programador (es) escreveu (m) sobre eles. Mas eu não tenho uma resposta tão boa para essa pergunta.

Por que cp é projetado dessa maneira?

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

Se você estivesse criando um novo sistema agora, você poderia fazer escolhas de design diferentes. Mas mudar o Unix poderia quebrar os scripts existentes, como mencionado em outras respostas.

Por que era cp projetado para sobrescrever 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 sobrescrever ou excluir arquivos. O shell tem um problema semelhante ao redirecionar a saída:

$ cat first.html > second.html

Este comando também sobrescreve silenciosamente second.html .

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

Acho que isso é parte da explicação: o Unix inicial enfatizava implementações simples . Para uma explicação mais detalhada disso, veja "pior é melhor", vinculado no fim desta resposta.

Você pode alterar > second.html para que pare com um erro, se second.html já existir. No entanto, como mencionamos, às vezes o usuário quer substituir um arquivo existente. Por exemplo, ela pode estar construindo um comando complexo, tentando várias vezes até fazer o que ela quer.

O usuário pode executar rm second.html primeiro se precisar. Isso pode ser um bom compromisso! Tem algumas possíveis desvantagens próprias.

  1. O usuário deve digitar o nome do arquivo duas vezes.
  2. As pessoas também enfrentam muitos problemas ao usar rm . Então eu gostaria de tornar o rm mais seguro também. Mas como? Se fizermos rm mostrar cada nome de arquivo e pedir ao usuário para confirmar, ela agora tem que escrever três linhas de comandos ao invés de uma. Além disso, se ela tiver que fazer isso com muita frequência, ela terá um hábito e digitará "y" para confirmar sem pensar. Por isso, pode ser muito chato e ainda ser perigoso.

Em um sistema moderno, recomendo que instale o comando trash e use-o em vez de rm , sempre que possível. A introdução do armazenamento de lixo foi uma ótima ideia, por exemplo para um PC gráfico de usuário único .

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

Observe que o Unix original não tinha a conclusão da guia , para preencher rapidamente um nome de arquivo para um comando rm . (Além disso, o shell Bourne original não tem histórico de comandos, por exemplo, quando você usa a tecla de seta para cima em 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 linhas. O efeito que isso tem depende do estado atual. (Se second.html já existir, seu conteúdo será descartado). Se o usuário não tiver certeza sobre o estado atual, espera-se que execute ls ou ls second.html primeiro.

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

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

The design must be simple, both in implementation and interface. It is more important for the implementation to be simple than the interface. Simplicity is the most important consideration in a design.

...

Gabriel argued that "Worse is better" produced more successful software than the MIT approach: As long as the initial program is basically good, it will take much less time and effort to implement initially and it will be easier to adapt to new situations. Porting software to new machines, for example, becomes far easier this way. Thus its use will spread rapidly, long before a [better] program has a chance to be developed and deployed (first-mover advantage).

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

    
por 24.10.2018 / 13:08
9

O design de "cp" remonta ao design original do Unix. De fato, havia uma filosofia coerente por trás do design Unix, que tem sido um pouco menos que meio de brincadeira foi chamado de Worse-is-Better * .

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

  • Simplicity -- the design must be simple, both in implementation and interface. It is more important for the implementation to be simple than the interface. Simplicity is the most important consideration in a design.

  • Correctness -- the design must be correct in all observable aspects. It is slightly better to be simple than correct.

  • Consistency -- the design must not be overly inconsistent. Consistency can be sacrificed for simplicity in some cases, but it is better to drop those parts of the design that deal with less common circumstances than to introduce either implementational complexity or inconsistency.

  • Completeness -- the design must cover as many important situations as is practical. All reasonably expected cases should be covered. Completeness can be sacrificed in favor of any other quality. In fact, completeness must be sacrificed whenever implementation simplicity is jeopardized. Consistency can be sacrificed to achieve completeness if simplicity is retained; especially worthless is consistency of interface.

( ênfase minha )

Lembrando que isso era 1970, o caso de uso de "Eu quero copiar este arquivo somente se ele ainda não existe" teria sido um caso de uso bastante raro para alguém que estivesse executando uma cópia. Se é isso que você queria, você seria capaz de verificar antes da cópia, e isso pode até mesmo ser roteirizado.

Sobre o motivo de um sistema operacional com essa abordagem de design ter sido aquele que venceu todos os outros sistemas operacionais sendo construídos na época, o autor do ensaio também tinha uma teoria para isso.

A further benefit of the worse-is-better philosophy is that the programmer is conditioned to sacrifice some safety, convenience, and hassle to get good performance and modest resource use. Programs written using the New Jersey approach will work well both in small machines and large ones, and the code will be portable because it is written on top of a virus.

It is important to remember that the initial virus has to be basically good. If so, the viral spread is assured as long as it is portable. Once the virus has spread, there will be pressure to improve it, possibly by increasing its functionality closer to 90%, but users have already been conditioned to accept worse than the right thing. Therefore, the worse-is-better software first will gain acceptance, second will condition its users to expect less, and third will be improved to a point that is almost the right thing.

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

    
por 24.10.2018 / 21:50
0

A principal razão é que uma GUI é por definição interativa, enquanto um binário como /bin/cp é apenas um programa que pode ser chamado de todos os tipos de lugares, por exemplo da sua GUI; ). Eu aposto que até hoje a grande maioria das chamadas para /bin/cp não será de um terminal real com um usuário digitando um comando shell, mas sim de um servidor HTTP ou um sistema de correio ou um NAS. Uma proteção integrada contra erros do usuário faz todo o sentido em um ambiente interativo; menos em um binário simples. Por exemplo, sua GUI provavelmente chamará /bin/cp em segundo plano para realizar as operações reais e teria que lidar com as questões de segurança no padrão, mesmo que ele apenas tenha perguntado ao usuário!

Note que foi desde o primeiro dia perto de trivial para escrever um wrapper seguro em torno de /bin/cp , se assim o desejar. A filosofia * nix é fornecer blocos de construção simples para os usuários: desses, /bin/cp é um.

    
por 25.10.2018 / 13:55

Tags