A leitura de um arquivo inteiro no espaço padrão é útil para substituir novas linhas, etc. e há muitos casos aconselhando o seguinte:
sed ':a;N;$!ba; [commands...]'
No entanto, ele falhará se a entrada contiver apenas uma linha.
Como exemplo, com entrada de duas linhas, todas as linhas estão sujeitas ao comando de substituição:
$ echo $'abc\ncat' | sed ':a;N;$!ba; s/a/xxx/g'
xxxbc
cxxxt
Mas, com entrada de linha única, nenhuma substituição é realizada:
$ echo 'abc' | sed ':a;N;$!ba; s/a/xxx/g'
abc
Como se escreve um sed
comando para ler todas as entradas de uma só vez e não ter esse problema?
sed -z
opção do GNU . Se o seu arquivo não tiver nulo, ele será lido até o final do arquivo! Encontrado a partir desta: stackoverflow.com/a/30049447/582917Respostas:
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 esed
encontra EOF em que está terminado - ele encerra o processamento. E assim, se você estiver na última linha e instruirsed
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
eex
, por exemplo, podem fazer muito do quesed
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ídased
, 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 quesed
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 comy///
. Se você costumag
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 :N
comando na última linhaA maioria das versões de
sed
saída sem imprimir nada quando oN
comando é emitido na última linha de um arquivo. O GNUsed
imprime o espaço do padrão antes de sair, a menos que a-n
opção de comando tenha sido especificada. Essa escolha é por design.Por exemplo, o comportamento de
sed N foo bar
depende de foo ter um número par ou ímpar de linhas. Ou, ao escrever um script para ler as próximas linhas após uma correspondência de padrão, as implementações tradicionais desed
forçariam você a escrever algo como, em/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }
vez de apenas/foo/{ N;N;N;N;N;N;N;N;N; }
.Em qualquer caso, a solução mais simples é usar
$d;N
em scripts que dependem do comportamento tradicional ou definir aPOSIXLY_CORRECT
variável como um valor não vazio.A
POSIXLY_CORRECT
variável de ambiente é mencionada porque o POSIX especifica que, sesed
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, qualquersed
comando que aceite um parâmetro arbitrário delimita em uma linha de\n
ew no script. Então os comandos ...... é provável que todos tenham um desempenho irregular, dependendo da
sed
implementação que os lê. Portably eles devem ser escritos:O mesmo vale para
r
,w
,t
,a
,i
, ec
(e, possivelmente, um pouco mais que eu estou esquecendo no momento) . Em quase todos os casos, eles também podem ser escritos:... onde a nova
-e
instrução xecution representa o\n
delimitador de linha de ew. Portanto, onde oinfo
texto GNU sugere que uma implementação tradicionalsed
forçaria você a fazer :... deveria ser ...
... é 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:
... que imprime:
... porque o
t
comando est - como a maioria dossed
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á oh
espaço antigo. Os buffers são os próximos ex
alterados e umas///
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 fort
bem-sucedido, o script se ramifica para o rótulon
otd
elete - o que faz um okl
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 umsed
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 en
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 osed
ciclo da linha é apenas um loop de leitura simples - se seu objetivo é apenas coletar informações indiscriminadamente, provavelmente é mais fácil:... que reunirá todo o arquivo ou será estourado.
uma observação lateral
N
e comportamento de última linha ...fonte
H
primeiro lugar é adorável.:a;$!{N;ba}
como mencionei acima - é mais fácil usar o formulário padrão a longo prazo quando você tenta executar regexps em sistemas desconhecidos. Mas não foi exatamente isso que eu quis dizer: você implementa um loop fechado - você não pode entrar no meio com tanta facilidade quando quiser, como deseja ramificando - removendo dados indesejados - e deixando o ciclo acontecer. É como uma coisa de cima para baixo - tudo o quesed
faz é um resultado direto do que acabou de fazer. Talvez você o veja de maneira diferente - mas se você tentar, pode achar que o script é mais fácil.Ele falha porque o
N
comando vem antes da correspondência do padrão$!
(não da última linha) e o sed sai antes de executar qualquer trabalho:Isso pode ser facilmente corrigido para funcionar também com entrada de linha única (e, de fato, para ser mais claro em qualquer caso), basta agrupar os comandos
N
eb
após o padrão:Funciona da seguinte maneira:
:a
crie um rótulo chamado 'a'$!
se não for a última linha, entãoN
anexe a próxima linha ao espaço do padrão (ou saia se não houver uma linha seguinte) e oba
ramo (vá para) rotule 'a'Infelizmente, não é portátil (pois depende de extensões GNU), mas a seguinte alternativa (sugerida por @mikeserv) é portátil:
fonte
:a;N;$!ba;
.