Linux: Como usar um arquivo como entrada e saída ao mesmo tempo?

46

Acabei de executar o seguinte no bash:

uniq .bash_history > .bash_history

e meu arquivo de histórico acabou completamente vazio.

Acho que preciso de uma maneira de ler o arquivo inteiro antes de escrever nele. Como isso é feito?

PS: Eu obviamente pensei em usar um arquivo temporário, mas estou procurando uma solução mais elegante.

    
por MilliaLover 24.04.2010 / 20:32

11 respostas

42

Eu recomendo usar sponge de moreutils . A partir do manpage:

DESCRIPTION
  sponge  reads  standard  input  and writes it out to the specified file. Unlike
  a shell redirect, sponge soaks up all its input before opening the output file.
  This allows for constructing pipelines that read from and write to the same 
  file.

Para aplicar isso ao seu problema, tente:

uniq .bash_history | sponge .bash_history
    
por 25.04.2010 / 00:46
61

Eu só queria contribuir com outra resposta que é simples e não usa esponja (porque muitas vezes não é incluída em ambientes leves).

echo "$(uniq .bash_history)" > .bash_history

deve ter o resultado desejado. O subshell é executado antes de .bash_history ser aberto para gravação. Como explicado na resposta de Phil P, no momento em que .bash_history é lido no comando original, ele já foi truncado pelo '>' operador.

    
por 20.10.2013 / 10:46
11

O problema é que seu shell está configurando o pipeline de comando antes de executar os comandos. Não é uma questão de "entrada e saída", é que o conteúdo do arquivo já se foi antes que o uniq seja executado. É algo como:

  1. O shell abre o arquivo de saída > para gravação, truncando-o
  2. O shell configura para que o descritor de arquivo 1 (para stdout) seja usado para essa saída
  3. O shell executa uniq, talvez algo como execlp ("uniq", "uniq", ".bash_history", NULL)
  4. o uniq é executado, abre .bash_history e não encontra nada lá

Existem várias soluções, incluindo a edição no local e o uso temporário de arquivos que os outros mencionam, mas a chave é entender o problema, o que realmente está errado e por quê.

    
por 25.04.2010 / 01:36
6

use esponja de moreutils

uniq .bash_history | sponge .bash_history
    
por 25.04.2010 / 00:46
6

Outro truque para fazer isso, sem usar sponge , é o seguinte comando:

{ rm .bash_history && uniq > .bash_history; } < .bash_history

Este é um dos truques descritos no excelente artigo “In-place” edição de arquivos em backreference.org.

Basicamente, ele abre o arquivo para leitura e "remove". Na verdade, ele não é removido: há um descritor de arquivo aberto apontando para ele e, enquanto ele permanecer aberto, o arquivo ainda estará ativo. Em seguida, cria um novo arquivo com o mesmo nome e grava as linhas exclusivas para ele.

Desvantagem desta solução: Se uniq falhar por algum motivo, seu histórico será eliminado.

    
por 26.11.2013 / 16:49
3

Este script sed remove duplicados adjacentes. Com a opção -i , ele faz a modificação no local. É do arquivo sed info :

sed -i 'h;:b;$b;N;/^\(.*\)\n$/ {g;bb};$b;P;D' .bash_history
    
por 24.04.2010 / 21:39
3

Como um petisco interessante, o sed também usa um arquivo temporário (isso faz isso por você):

$ strace sed -i 's/foo/bar/g' foo    
open("foo", O_RDONLY|O_LARGEFILE)       = 3
...
open("./sedPmPv9z", O_RDWR|O_CREAT|O_EXCL|O_LARGEFILE, 0600) = 4
...
read(3, "foo\n"..., 4096)               = 4
write(4, "bar\n"..., 4)                 = 4
read(3, ""..., 4096)                    = 0
close(3)                                = 0
close(4)                                = 0
rename("./sedPmPv9z", "foo")            = 0
close(1)                                = 0
close(2)                                = 0

Descrição:
O tempfile ./sedPmPv9z torna-se fd 4 e os arquivos foo tornam-se fd 3. As operações de leitura estão em fd 3 e as gravações em fd 4 (o arquivo temporário). O arquivo foo é então sobrescrito com o arquivo temporário na chamada de renomeação.

    
por 25.04.2010 / 03:12
0

Um arquivo temporário é praticamente isso, a menos que o comando em questão aconteça para suportar a edição em vigor ( uniq não - alguns sed s do ( sed -i )).

    
por 24.04.2010 / 20:45
0

Você pode usar o Vim no modo Ex:

ex -sc '%!uniq' -cx .bash_history
  1. % seleciona todas as linhas

  2. ! comando de execução

  3. x salvar e fechar

por 10.04.2016 / 18:41
0

Outra solução:

uniq file 1<> líneas.txt
    
por 27.07.2016 / 12:38
-1

Você pode usar tee também, usando a saída uniq como entrada:

uniq .bash_history | tee .bash_history
    
por 12.04.2016 / 00:30