redirecionamento IO e o comando head

9

Eu estava tentando editar rapidamente um arquivo .hgignore do shell bash do Cygwin hoje, e adicionei uma linha que foi um erro. Não tenho certeza se essa foi a melhor maneira de fazer isso, mas rapidamente pensei em usar head -1 .hgignore para remover a linha ofensiva (eu tinha anteriormente apenas uma linha no arquivo). Com certeza, quando executado, fornece a primeira linha como a única saída.

Mas quando tentei redirecionar a saída e reescrever o arquivo usando head -1 .hgignore > .hgignore , o arquivo estava vazio. Por que isso acontece? Se eu tentar acrescentar, em vez disso, head -1 .hgignore >> .hgignore , ele será anexado corretamente, mas obviamente esse não é o resultado desejado. Por que um redirecionamento de truncamento não funciona neste caso?

    
por voithos 29.06.2011 / 19:55

6 respostas

11

Quando o shell obtém uma linha de comando como: command > file.out o próprio shell abre (e talvez cria) o arquivo chamado file.out . O shell configura o descritor de arquivo 0 para o descritor de arquivo que obteve do campo aberto. É assim que funciona o redirecionamento de E / S: todo processo conhece os descritores de arquivo 0, 1 e 2.

A parte difícil disso é como abrir file.out . Na maioria das vezes, você deseja que file.out seja aberto para gravação no deslocamento 0 (isto é, truncado) e isso é o que o shell fez por você. Ele truncou .hgignore, abriu-o para escrever, digitou o filedescriptor para 0 e depois executou head . Espancamento instantâneo de arquivos.

No bash shell, você faz um set noclobber para alterar esse comportamento.

    
por 29.06.2011 / 20:43
10

Acho que Bruce responde o que está acontecendo aqui com o oleoduto da concha.

Um dos meus pequenos utilitários favoritos é o comando sponge de moreutils . Ele resolve exatamente esse problema "absorvendo" toda a entrada disponível antes de abrir o arquivo de saída de destino e gravar os dados. Ele permite que você escreva pipelines exatamente como esperava:

$ head -1 .hgignore | sponge .hgignore

A solução do pobre é canalizar a saída para um arquivo temporário, depois que a pipline é feita (por exemplo, o próximo comando que você executa) é mover o arquivo temporário de volta para o local original do arquivo.

$ head -1 .hgingore > .hgignore.tmp
$ mv .hgignore{.tmp,}
    
por 29.06.2011 / 22:43
3

Em

head -n 1 file > file

file é truncado antes de head ser iniciado, mas se você o escrever:

head -n 1 file 1<> file

não é como file é aberto no modo de leitura / gravação. No entanto, quando head terminar de escrever, ele não truncará o arquivo, então a linha acima seria um no-op ( head iria apenas reescrever a primeira linha e deixar os outros intocados).

No entanto, depois que head retornar e enquanto o fd ainda estiver aberto, você poderá chamar outro comando que faça o truncate .

Por exemplo:

{ head -n 1 file; perl -e 'truncate STDOUT, tell STDOUT'; } 1<> file

O que importa aqui é que truncate acima, head apenas move o cursor para fd 1 dentro do arquivo logo após a primeira linha. Ele reescreve a primeira linha para a qual não precisamos, mas isso não é prejudicial.

Com um cabeçalho POSIX, podemos sair sem reescrever a primeira linha:

{ head -n 1 > /dev/null
  perl -e 'truncate STDIN, tell STDIN'
} <> file

Aqui, estamos usando o fato de que head move a posição do cursor em seu stdin. Enquanto head normalmente leria sua entrada por grandes blocos para melhorar o desempenho, o POSIX exigiria (sempre que possível) seek de volta logo após a primeira linha se ela tivesse ido além. Note, no entanto, que nem todas as implementações fazem isso.

Como alternativa, você pode usar o comando read do shell, neste caso:

{ read -r dummy; perl -e 'truncate STDIN, tell STDIN'; } <> file
    
por 19.02.2013 / 21:45
1

A solução do Real Man é

ed .hgignore
$d
wq

ou como um one-liner

printf '%s\n' '$d' 'wq' | ed .hgignore

Ou com o GNU sed:

sed -i '$d' .hgignore

(Não, estou brincando. Eu usaria um editor interativo. vi .hgignore GddZZ )

    
por 30.06.2011 / 00:16
1

Você pode usar o Vim no modo Ex:

ex -sc '2,d|x' .hgignore
  1. 2, selecione as linhas 2 até o final

  2. d delete

  3. x salvar e fechar

por 11.04.2016 / 04:58
0

Para a edição de arquivos no local, você também pode usar o truque de manuseio de arquivo aberto, como mostrado por Jürgen Hötzel em Redireciona a saída do sed 's / c / d /' myFile para myFile .

exec 3<.hgignore
rm .hgignore  # prevent open file from being truncated
head -1 <&3 > .hgignore

ls -l .hgignore  # note that permissions may have changed
    
por 30.06.2011 / 12:25