O redirecionamento com `>>` é equivalente a `>` quando o arquivo de destino ainda não existe?

80

Considere um shell como Bash ou sh. A diferença básica entre >e se >>manifesta em um caso em que o arquivo de destino existe:

  • > trunca o arquivo para tamanho zero e depois grava;
  • >> não truncar, ele grava (anexa) no final do arquivo.

Se o arquivo não existir, ele será criado com tamanho zero; então escrito para. Isso é verdade para os dois operadores. Pode parecer que os operadores são equivalentes quando o arquivo de destino ainda não existe.

Eles são mesmo?

Kamil Maciorowski
fonte

Respostas:

107

tl; dr

No. >>é essencialmente "sempre procure final do arquivo" enquanto >mantém um ponteiro para o último local escrito.


Resposta completa

(Nota: todos os meus testes feitos no Debian GNU / Linux 9).

Outra diferença

Não, eles não são equivalentes. Há outra diferença. Pode se manifestar independentemente de o arquivo de destino existir antes ou não.

Para observá-lo, execute um processo que gere dados e redirecione para um arquivo com >ou >>(por exemplo pv -L 10k /dev/urandom > blob). Deixe rodar e altere o tamanho do arquivo (por exemplo, com truncate). Você verá que >mantém seu deslocamento (crescente) enquanto >>sempre se anexa ao final.

  • Se você truncar o arquivo para um tamanho menor (ele pode ter tamanho zero)
    • >não se importa, ele escreverá no deslocamento desejado como se nada tivesse acontecido; logo após o truncamento do deslocamento estar além do final do arquivo, isso fará com que o arquivo recupere seu tamanho antigo e cresça ainda mais, os dados ausentes serão preenchidos com zeros (de maneira esparsa, se possível);
    • >> será anexado ao novo final, o arquivo aumentará de seu tamanho truncado.
  • Se você aumentar o arquivo
    • >não se importará, ele escreverá no deslocamento desejado como se nada tivesse acontecido; logo após alterar o tamanho, o deslocamento está em algum lugar dentro do arquivo, isso fará com que o arquivo pare de crescer por um tempo, até que o deslocamento atinja o novo final, então o arquivo aumentará normalmente;
    • >> será anexado ao novo final, o arquivo aumentará a partir do tamanho ampliado.

Outro exemplo é acrescentar (com um separado >>) algo extra quando o processo de geração de dados estiver em execução e gravando no arquivo. Isso é semelhante a ampliar o arquivo.

  • O processo de geração >gravará no deslocamento desejado e substituirá os dados extras eventualmente.
  • O processo de geração >>irá pular os novos dados e anexá-los (a condição de corrida pode ocorrer, os dois fluxos podem ser intercalados, ainda assim nenhum dado deve ser substituído).

Exemplo

Isso importa na prática? Existe esta pergunta :

Estou executando um processo que produz muita saída no stdout. Enviando tudo para um arquivo [...] Posso usar algum tipo de programa de rotação de logs?

Esta resposta diz que a solução é logrotatecom a copytruncateopção que age assim:

Trunque o arquivo de log original depois de criar uma cópia, em vez de mover o arquivo de log antigo e, opcionalmente, criar um novo.

De acordo com o que escrevi acima, o redirecionamento com >tornará o log truncado grande em pouco tempo. A escassez salvará o dia; nenhum espaço em disco significativo deve ser desperdiçado. No entanto, cada log consecutivo terá cada vez mais zeros à esquerda que são completamente desnecessários.

Porém, se logrotatecriar cópias sem preservar a escassez, esses zeros à esquerda precisarão de mais e mais espaço em disco toda vez que uma cópia for feita. Não investiguei o comportamento da ferramenta, ela pode ser inteligente o suficiente com escassez ou compactação em tempo real (se a compactação estiver ativada). Ainda assim, os zeros podem apenas causar problemas ou, na melhor das hipóteses, serem neutros; nada de bom neles.

Nesse caso, usar em >>vez de >é significativamente melhor, mesmo que o arquivo de destino esteja prestes a ser criado ainda.


atuação

Como podemos ver, os dois operadores agem de maneira diferente, não apenas quando começam, mas também mais tarde. Isso pode causar alguma (sutil?) Diferença de desempenho. Por enquanto, não tenho resultados significativos para apoiá-lo ou refutá-lo, mas acho que você não deve assumir automaticamente que o desempenho deles é o mesmo em geral.

Kamil Maciorowski
fonte
9
Assim, >>é essencialmente "sempre procure o final do arquivo" enquanto >mantém um ponteiro para o último local escrito. Parece que pode haver alguma diferença sutil de desempenho na maneira como eles funcionam ...
Mokubai
10
No nível da chamada do sistema, >>usa o O_APPENDsinalizador paraopen() . E, na verdade, >usa O_TRUNC, enquanto >>não. A combinação de O_TRUNC | O_APPENDtambém seria possível, a linguagem shell simplesmente não fornece esse recurso.
23918 ilkkachu
3
@jjmontes, a fonte padrão seria POSIX: pubs.opengroup.org/onlinepubs/9699919799.2018edition/utilities/… mas é claro que o manual do Bash também tem descrições sobre os operadores de redirecionamento, incluindo os não-padrão que suporta: gnu.org/ software / bash / manual / html_node / Redirections.html
ilkkachu
2
@ilkkachu eu achei que isso seja de interesse, como explica detalhes sobre O_APPEND que eu estava querendo saber sobre após o seu comentário :): stackoverflow.com/questions/1154446/...
jjmontes
1
@Mokubai, Qualquer sistema operacional saudável teria o tamanho do arquivo em mãos quando aberto, e verificar uma bandeira e mover o deslocamento para o final deve desaparecer em todas as outras contas. Tentar emular O_APPENDcom um lseek()antes de cada um write()seria diferente, porém, haveria uma sobrecarga adicional de chamada do sistema. (E, claro que não iria funcionar, pois outro processo poderia write()no meio.)
ilkkachu