Existem todos os tipos de razões pelas quais a leitura de um arquivo inteiro no espaço padrão pode dar errado. O problema lógico na pergunta em torno da última linha é comum. Está relacionado ao sed
ciclo de linhas - quando não há mais linhas e sed
encontra EOF em que está terminado - ele encerra o processamento. E assim, se você estiver na última linha e instruir sed
para conseguir outra, ela vai parar ali e não fazer mais.
Dito isto, se você realmente precisar ler um arquivo inteiro no espaço padrão, provavelmente vale a pena considerar outra ferramenta. O fato é que sed
é o editor de fluxo de maneira homogênea - ele é projetado para trabalhar uma linha - ou um bloco de dados lógicos - de cada vez.
Existem muitas ferramentas semelhantes que estão melhor equipadas para lidar com blocos de arquivos completos. ed
e ex
, por exemplo, podem fazer muito do que sed
podem fazer e com sintaxe semelhante - e muito mais - mas, em vez de operar apenas em um fluxo de entrada enquanto o transformam em saída sed
, eles também mantêm arquivos de backup temporários no sistema de arquivos . O trabalho deles é armazenado em buffer no disco, conforme necessário, e eles não são encerrados abruptamente no final do arquivo (e tendem a implodir com muito menos frequência sob tensão do buffer) . Além disso, eles oferecem muitas funções úteis que sed
não fazem - do tipo que simplesmente não faz sentido em um contexto de fluxo - como marcas de linha, desfazer, buffers nomeados, junção e muito mais.
sed
A força principal de sua capacidade é processar dados assim que os lê - de maneira rápida, eficiente e em fluxo. Quando você copia um arquivo, joga isso fora e tende a encontrar dificuldades de casos extremos, como o último problema de linha que você mencionou, excedentes de buffer e desempenho péssimo - à medida que os dados que ele analisa aumentam no tempo de processamento de um mecanismo de expressão regular ao enumerar correspondências aumenta exponencialmente .
A respeito desse último ponto, a propósito: embora eu entenda que o s/a/A/g
caso de exemplo é muito provavelmente apenas um exemplo ingênuo e provavelmente não é o script real para o qual você deseja reunir uma entrada, você pode achar que vale a pena se familiarizar com y///
. Se você costuma g
substituir globalmente um caractere por outro, y
pode ser muito útil para você. É uma transformação em oposição a uma substituição e é muito mais rápida, pois não implica uma regexp. Esse último ponto também pode torná-lo útil ao tentar preservar e repetir //
endereços vazios , pois não os afeta, mas pode ser afetado por eles. De qualquer forma, y/a/A/
é um meio mais simples de realizar o mesmo - e os swaps são possíveis, assim como:y/aA/Aa/
que trocaria todas as maiúsculas / minúsculas como em uma linha entre si.
Você também deve observar que o comportamento que você descreve não é realmente o que deveria acontecer de qualquer maneira.
Dos GNUs info sed
na seção BUGS RELATADOS COMUNS :
A POSIXLY_CORRECT
variável de ambiente é mencionada porque o POSIX especifica que, se sed
encontrar EOF ao tentar um, N
ele deve sair sem saída, mas a versão GNU rompe intencionalmente com o padrão nesse caso. Observe também que, mesmo que o comportamento seja justificado acima, pressupõe-se que o caso de erro seja de edição de fluxo - não colocando um arquivo inteiro na memória.
O padrão define N
o comportamento da seguinte forma:
N
Anexe a próxima linha de entrada, menos sua linha de \n
ew final , ao espaço do padrão, usando uma \n
linha de ew incorporada para separar o material anexado do material original. Observe que o número da linha atual é alterado.
Se nenhuma próxima linha de entrada estiver disponível, o N
verbo de comando deverá se ramificar no final do script e sair sem iniciar um novo ciclo ou copiar o espaço do padrão para a saída padrão.
Nessa nota, existem alguns outros GNU-ismos demonstrados na pergunta - particularmente o uso do :
rótulo, b
rancho e {
colchetes de contexto de função }
. Como regra geral, qualquer sed
comando que aceite um parâmetro arbitrário delimita em uma linha de \n
ew no script. Então os comandos ...
:arbitrary_label_name; ...
b to_arbitrary_label_name; ...
//{ do arbitrary list of commands } ...
... é provável que todos tenham um desempenho irregular, dependendo da sed
implementação que os lê. Portably eles devem ser escritos:
...;:arbitrary_label_name
...;b to_arbitrary_label_name
//{ do arbitrary list of commands
}
O mesmo vale para r
, w
, t
, a
, i
, e c
(e, possivelmente, um pouco mais que eu estou esquecendo no momento) . Em quase todos os casos, eles também podem ser escritos:
sed -e :arbitrary_label_name -e b\ to_arbitary_label_name -e \
"//{ do arbitrary list of commands" -e \}
... onde a nova -e
instrução xecution representa o \n
delimitador de linha de ew. Portanto, onde o info
texto GNU sugere que uma implementação tradicional sed
forçaria você a fazer :
/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }
... deveria ser ...
/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N
}
... é claro, isso também não é verdade. Escrever o script dessa maneira é um pouco bobo. Existem meios muito mais simples de fazer o mesmo, como:
printf %s\\n foo . . . . . . |
sed -ne 'H;/foo/h;x;//s/\n/&/3p;tnd
//!g;x;$!d;:nd' -e 'l;$a\' \
-e 'this is the last line'
... que imprime:
foo
.
.
.
foo\n.\n.\n.$
.$
this is the last line
... porque o t
comando est - como a maioria dos sed
comandos - depende do ciclo da linha para atualizar seu registro de retorno e aqui o ciclo da linha é permitido para executar a maior parte do trabalho. Essa é outra desvantagem que você faz quando inverte um arquivo - o ciclo da linha não é atualizado novamente e muitos testes se comportam de maneira anormal.
O comando acima não corre o risco de exceder a entrada, porque apenas faz alguns testes simples para verificar o que lê enquanto lê. Com o H
antigo, todas as linhas são anexadas ao espaço de espera, mas se uma linha corresponder /foo/
, substituirá o h
espaço antigo. Os buffers são os próximos e x
alterados e uma s///
substituição condicional é tentada se o conteúdo do buffer corresponder ao //
último padrão endereçado. Em outras palavras, //s/\n/&/3p
tenta substituir a terceira nova linha no espaço de espera e imprimir os resultados se o espaço de espera corresponder no momento /foo/
. Se isso for t
bem-sucedido, o script se ramifica para o rótulo n
ot d
elete - o que faz um ok l
e envolve o script.
No /foo/
entanto, se uma e a terceira nova linha não puderem ser combinadas no espaço de espera, //!g
elas substituirão o buffer se /foo/
não forem correspondidas ou, se forem correspondentes, substituirão o buffer se uma linha de \n
ew não corresponder (substituindo assim /foo/
por próprio) . Esse pequeno teste sutil evita que o buffer seja preenchido desnecessariamente por longos períodos sem /foo/
e garante que o processo permaneça rápido porque a entrada não se acumula. Em um caso de não /foo/
ou //s/\n/&/3p
falha, os buffers são novamente trocados e todas as linhas, exceto a última, são excluídas.
Essa última - a última linha $!d
- é uma demonstração simples de como um sed
script de cima para baixo pode ser feito para lidar com vários casos facilmente. Quando o seu método geral é remover os casos indesejados, começando pelos mais gerais e trabalhando pelos mais específicos, os casos extremos podem ser mais facilmente tratados, porque eles simplesmente podem passar até o final do script com seus outros dados desejados e quando tudo isso envolve apenas os dados que você deseja. Porém, ter que buscar esses casos extremos de um loop fechado pode ser muito mais difícil.
E aqui está a última coisa que tenho a dizer: se você realmente precisa extrair um arquivo inteiro, pode trabalhar um pouco menos, confiando no ciclo da linha para fazer isso por você. Normalmente você usaria N
ext e n
ext para lookahead - porque eles avançam à frente do ciclo da linha. Em vez de implementar redundantemente um loop fechado dentro de um loop - como o sed
ciclo da linha é apenas um loop de leitura simples - se seu objetivo é apenas coletar informações indiscriminadamente, provavelmente é mais fácil:
sed 'H;1h;$!d;x;...'
... que reunirá todo o arquivo ou será estourado.
uma observação lateral N
e comportamento de última linha ...
Embora eu não tenha as ferramentas disponíveis para testar, considere que N
ao ler e editar no local se comporta de maneira diferente se o arquivo editado for o arquivo de script da próxima leitura.