Lendo e gravando um arquivo: comando tee


10

É sabido que um comando como este:

cat filename | some_sed_command >filename

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

Pode-se 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 substituir algum conteúdo que ainda não foi lido? Vejo que também existe um comando de esponja que deve funcionar em qualquer caso: é "mais seguro" que o tee?


Qual é o seu objetivo principal? (em termos simples)
Sergiy Kolodyazhnyy 01/04

@Serg simplesmente entender como as coisas funcionam ... A resposta escrito por kos esclarece o assunto
VeryHardCoder

Respostas:


10

Pode-se resolver o problema da seguinte maneira:

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

Não .

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

Tudo depende de qual comando é processado primeiro, ao contrário do que se pode esperar, os comandos em um canal não são processados ​​da esquerda para a direita . Não há garantia sobre qual comando será escolhido primeiro; portanto, pode-se pensar nele como escolhido aleatoriamente e nunca confiar que o shell não escolha o ofensor.

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 fileseja 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

Portanto, nunca use algo como cat file | some_sed_command | tee file >/dev/null. Use spongecomo Oli sugeriu.

Como alternativa, para ambientes mais sofisticados 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

9

Para sedespecificamente, você pode usar seu -iargumento no local. Ele apenas salva de volta no arquivo que abriu, por exemplo:

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

Se você quiser fazer algo mais robusto, supondo que você esteja fazendo mais do que sed, sim, você pode armazenar a coisa toda com sponge(do moreutilspacote) que "absorverá" todo o stdin antes de gravar no arquivo. É como, teemas 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, ele tem limites; portanto, se você estiver fazendo algo colossal (e não puder editar no local com o sed), convém fazer as edições em um segundo arquivo e depois mvo arquivo de volta ao nome do arquivo original. Isso deve ser atômico (para que tudo, dependendo desses arquivos, não seja interrompido se eles precisarem de acesso constante).


0

Você pode usar o Vim no modo Ex:

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

  2. ! Comando de execução

  3. x Salvar e sair


0

Ah, mas spongenão é a única opção; você não precisa obtê moreutils-lo para que isso funcione corretamente. Qualquer mecanismo funcionará desde que atenda aos dois requisitos a seguir:

  1. Ele aceita o nome do arquivo de saída como um parâmetro.
  2. Ele só cria o arquivo de saída quando todas as entradas foram processadas.

Veja bem, o problema bem conhecido ao qual o OP está se referindo é que o shell criará todos os arquivos necessários para que os pipes funcionem antes mesmo de começar a executar os comandos no pipeline, portanto, é o shell que realmente trunca o arquivo de saída (que infelizmente também é o arquivo de entrada) antes que qualquer um dos comandos tivesse a chance de começar a executar.

O teecomando não funciona, embora atenda ao primeiro requisito, porque não atende ao segundo requisito: ele sempre criará o arquivo de saída imediatamente após a inicialização, portanto é tão ruim quanto criar um canal direto para o arquivo de saída. (Na verdade, é pior, porque seu uso introduz um atraso aleatório não determinístico antes que o arquivo de saída seja truncado; portanto, você pode pensar que ele funciona, enquanto na verdade não funciona.)

Portanto, tudo o que precisamos para resolver esse problema é algum comando que armazene em buffer todas as suas entradas antes de produzir qualquer saída e que seja capaz de aceitar o nome do arquivo de saída como parâmetro, para que não tenhamos que canalizar sua saída para o arquivo de saída. Um desses comandos é shuf. Portanto, o seguinte realizará a mesma coisa que spongefaz:

    shuf --output=file --random-source=/dev/zero 

A --random-source=/dev/zeroparte engana-se shufa fazer as coisas sem fazer nenhum embaralhamento, de modo a proteger sua entrada sem alterá-la.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.