Por que redirecionar a saída de um arquivo para ele mesmo produz um arquivo em branco?

19

Por que redirecionar a saída de um arquivo para ele mesmo produz um arquivo em branco?

Declarado em Bash, por que

less foo.txt > foo.txt

e

fold foo.txt > foo.txt

produzir um vazio foo.txt? Como um anexo como less eggs.py >> eggs.pyproduz duas cópias do texto eggs.py, pode-se esperar que uma substituição produza uma cópia do texto.

Note, eu não estou dizendo que isso é um bug, é mais provável que seja um ponteiro para algo profundo sobre o Unix.

seewalker
fonte
Endereçado no canônico da U&L. Quais são os operadores de controle e redirecionamento do shell? questão.
Scott

Respostas:

20

Quando você usa >, o arquivo é aberto no modo de truncamento para que seu conteúdo seja removido antes que o comando tente lê-lo.

Quando você usa >>, o arquivo é aberto no modo de acréscimo para que os dados existentes sejam preservados. No entanto, ainda é bastante arriscado usar o mesmo arquivo que entrada e saída nesse caso. Se o arquivo for grande o suficiente para não caber no tamanho do buffer de entrada de leitura, seu tamanho poderá aumentar indefinidamente até que o sistema de arquivos esteja cheio (ou sua cota de disco seja atingida).

Se você deseja usar um arquivo como entrada e saída com um comando que não suporta modificação no local, você pode usar algumas soluções alternativas:

  • Use um arquivo intermediário e substitua o original quando terminar e somente se nenhum erro ocorrer durante a execução do utilitário (esta é a maneira mais segura e mais comum).

    fold foo.txt > fold.txt.$$ && mv fold.txt.$$ foo.txt
  • Evite o arquivo intermediário à custa de uma potencial perda de dados parcial ou completa, se ocorrer um erro ou interrupção. Neste exemplo, o conteúdo de foo.txté passado como entrada para um subshell (dentro dos parênteses) antes que o arquivo seja excluído. O inode anterior permanece ativo enquanto o subshell o mantém aberto durante a leitura dos dados. O arquivo gravado pelo utilitário interno (aqui fold) com o mesmo nome (foo.txt) aponta para um inode diferente porque a entrada do diretório antigo foi removida tecnicamente; existem dois "arquivos" diferentes com o mesmo nome durante o processo. Quando o subshell termina, o inode antigo é liberado e seus dados são perdidos. Cuidado para garantir espaço suficiente para armazenar temporariamente o arquivo antigo e o novo ao mesmo tempo, caso contrário você perderá dados.

    (rm foo.txt; fold > foo.txt) < foo.txt
jlliagre
fonte
3
spongefrom moreutils também pode ajudar. fold foo.txt | sponge foo.txt- ou fold foo.txt | sponge !$também deve fazer.
Slhck
@ sllck De fato, a esponja também pode fazer o trabalho. No entanto, não sendo especificado pelo POSIX nem mainstream no Unix como sistemas operacionais, é improvável que esteja presente.
Jlliagre 19/05
Não é como se ele não pode ser feito presente embora;)
slhck
7

O arquivo é aberto para gravação pelo shell antes que o aplicativo possa lê-lo. A abertura do arquivo para gravação o trunca.

Ignacio Vazquez-Abrams
fonte
0

No bash, o operador de redirecionamento de fluxo é ... > foo.txtesvaziado foo.txt antes de avaliar o operando esquerdo .

Pode-se usar substituição de comando e imprimir seu resultado como uma solução alternativa. Esta solução ocupa menos caracteres adicionais do que em outras respostas:

printf "%s\n" "$(less foo.txt)" > foo.txt

Cuidado: este comando não preserva nenhuma nova linha de busca em foo.txt. Dê uma olhada na seção de comentários abaixo para obter mais informações

Aqui, o subshell $(...)é avaliado antes do operador de redirecionamento de fluxo >, daí a preservação de informações.

Louis-Jacob Lebel
fonte
@KamilMaciorowski: Na verdade, existe tmp=$(cmd; printf q);  printf '%s' "${tmp%q}". Mas você perdeu outro problema com esta resposta: diz "subshell" quando significa "substituição de comando". Sim, as substituições de comando geralmente são sub-conchas, mas não vice-versa, e sub-conchas, em geral, não ajudam a esse problema.
Scott
@KamilMaciorowski Eu me sinto tão mal por perder tudo isso. Obrigado por apontar tudo isso. Pelo seu (4) ponto: as aspas anteriores seriam suficientes, ou seja, preservariam as novas linhas à direita?
Louis-Jacob Lebel
@ Scott, obrigado pela sua resposta. Alterei "subshell" para "substituição de comando". A propósito, eu me pergunto qual é a diferença exata entre os dois.
Louis-Jacob Lebel
Não, as aspas (backticks) também retiram os caracteres de nova linha.
Kamil Maciorowski
Tudo bem então, eu adicionei uma mensagem de aviso por enquanto. Vou removê-lo se encontrar uma solução.
Louis-Jacob Lebel