Lendo e escrevendo um arquivo: comando tee

6

É bem conhecido que um comando como este:

cat filename | some_sed_command >filename

apaga o nome do arquivo, pois o redirecionamento de saída, executado antes do comando, faz com que o nome do arquivo seja truncado.

Poder-se-ia resolver o problema da seguinte maneira:

cat file | some_sed_command | tee file >/dev/null

mas não tenho certeza se isso funcionaria em qualquer caso: o que acontece se o arquivo (e o resultado do comando sed) for muito grande? Como o sistema operacional pode evitar sobrescrever algum conteúdo que ainda não é lido? Eu vejo que há também um comando de esponja que deve funcionar em qualquer caso: é "mais seguro" do que tee?

    
por VeryHardCoder 31.03.2016 / 10:03

3 respostas

7
  

Poder-se-ia resolver o problema da seguinte maneira:

cat file | some_sed_command | tee file >/dev/null

Não .

As chances file serão truncadas, mas não há garantia cat file | some_sed_command | tee file >/dev/null não truncará file .

Tudo depende de qual comando é processado primeiro, pois, ao contrário do que se espera, os comandos em um pipe não são processado da esquerda para a direita . Não há nenhuma garantia sobre qual comando será escolhido primeiro, então pode-se apenas pensar nisso como escolhido aleatoriamente e nunca confiar no shell não escolhendo o ofensivo.

Como as chances de o comando incorreto ser escolhido primeiro entre três comandos são menores do que as chances de o comando incorreto ser escolhido primeiro entre dois comandos, é menos provável que file seja truncado, mas ainda vai acontecer .

script.sh :

#!/bin/bash
for ((i=0; i<100; i++)); do
    cat >file <<-EOF
    foo
    bar
    EOF
    cat file |
        sed 's/bar/baz/' |
        tee file >/dev/null
    [ -s file ] &&
        echo 'Not truncated' ||
        echo 'Truncated'
done |
    sort |
    uniq -c
rm file
% bash script.sh
 93 Not truncated
  7 Truncated
% bash script.sh
 98 Not truncated
  2 Truncated
% bash script.sh
100 Not truncated

Então nunca usa algo como cat file | some_sed_command | tee file >/dev/null . Use sponge como sugerido por Oli.

Como alternativa, para ambientes mais rigorosos e / ou arquivos relativamente pequenos, pode-se usar uma string here e uma substituição de comando para ler o arquivo antes que qualquer comando seja executado:

$ cat file
foo
bar
$ for ((i=0; i<100; i++)); do <<<"$(<file)" sed 's/bar/baz/' >file; done
$ cat file
foo
baz
    
por kos 31.03.2016 / 23:13
7

Para sed especificamente, você pode usar seu argumento -i no local. Apenas salva de volta ao arquivo que abriu, por exemplo:

sed -i 's/ /-/g' filename

Se você quer fazer algo mais corpulento, supondo que você esteja fazendo mais do que sed , sim, você pode armazenar tudo com sponge (do moreutils package) que irá "absorver" todo o stdin antes de escrever no arquivo. É como tee , mas com menos funcionalidade. Para uso básico, porém, é praticamente uma substituição imediata:

cat file | some_sed_command | sponge file >/dev/null

Isso é mais seguro? Definitivamente. Provavelmente tem limites, por isso, se você está fazendo algo colossal (e não pode inserir no local com sed), você pode querer fazer suas edições em um segundo arquivo e, em seguida, mv desse arquivo de volta para o nome do arquivo original. Isso deve ser atômico (então qualquer coisa que dependa desses arquivos não será interrompida se eles precisarem de acesso constante).

    
por Oli 31.03.2016 / 11:05
0

Você pode usar o Vim no modo Ex:

ex -sc '%!some_sed_command' -cx filename
  1. % seleciona todas as linhas

  2. ! Executar comando

  3. x Salve e saia

por Steven Penny 11.04.2016 / 01:55