Eu concordo com você - provavelmente é um problema genérico. Alguns utilitários comuns têm algumas facilidades para lidar com isso, no entanto.
nl
nl
, por exemplo, separa a entrada em páginas lógicas, como -d
elimitado por um delimitador de seção de dois caracteres . Três ocorrências em uma linha sozinhas indicam o início de um cabeçalho , duas no corpo e uma no rodapé . Ele substitui qualquer um dos encontrados na entrada por uma linha em branco na saída - que são as únicas linhas em branco que já imprime
Alterei seu exemplo para incluir outra seção e inseri-la ./infile
. Então fica assim:
line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
@@start
line M
line N
line O
@@end
Então eu executei o seguinte:
sed 's/^@@.*start$/@@@@@@/
s/^@@.*end$/@@/' <infile |
nl -d@@ -ha -bn -w1
nl
pode ser dito para acumular o estado nas páginas lógicas, mas isso não ocorre por padrão. Em vez disso, numerará as linhas de sua entrada de acordo com os estilos e por seção . Então, -ha
significa o número todos os cabeçalho linhas e -bn
significa há linhas do corpo - como ele começa em um corpo estado.
Até eu aprender isso, costumava usar nl
para qualquer entrada, mas depois de perceber que isso nl
poderia distorcer a saída de acordo com seu -d
elimitador padrão \:
, aprendi a ter mais cuidado com ele e comecei a usar grep -nF ''
entradas não testadas. Mas outra lição aprendida naquele dia foi que nl
pode ser aplicada de maneira muito útil em outros aspectos - como este - se você apenas modificar um pouco sua entrada - como eu faço sed
acima.
RESULTADO
line A
line B
1 line X
2 line Y
3 line Z
line C
line D
1 line M
2 line N
3 line O
Aqui está um pouco mais sobre nl
- você notou acima como todas as linhas, exceto as numeradas, começam com espaços? Ao nl
numerar linhas, ele insere um certo número de caracteres na cabeça de cada um. Para essas linhas, ele não é numerado - nem mesmo em branco - ele sempre corresponde ao recuo, inserindo ( -w
idth count + -s
eparator len) * espaços no início das linhas não numeradas. Isso permite reproduzir o conteúdo não numerado exatamente comparando-o com o conteúdo numerado - e com pouco esforço. Quando você considera que nl
dividirá sua entrada em seções lógicas para você e que pode inserir -s
sequências arbitrárias no início de cada linha numerada, fica muito fácil lidar com sua saída:
sed 's/^@@.*start$/@@@@@@/
s/^@@.*end/@@/; t
s/^\(@@\)\{1,3\}$/& /' <infile |
nl -d@@ -ha -bn -s' do something with the next line!
'
As impressões acima ...
line A
line B
1 do something with the next line!
line X
2 do something with the next line!
line Y
3 do something with the next line!
line Z
line C
line D
1 do something with the next line!
line M
2 do something with the next line!
line N
3 do something with the next line!
line O
GNU sed
Se nl
não é o seu aplicativo de destino, um GNU sed
pode e
executar um comando shell arbitrário para você, dependendo de uma correspondência.
sed '/^@@.*start$/!b
s//nl <<\\@@/;:l;N
s/\(\n@@\)[^\n]*end$/\1/
Tl;e' <infile
Acima sed
coleta a entrada no espaço do padrão até que tenha o suficiente para passar com êxito a substituição T
est e parar a b
criação de gado de volta ao :l
abel. Quando isso ocorre, ele e
executa nl
com a entrada representada como um <<
documento aqui para todo o restante de seu espaço de padrão.
O fluxo de trabalho é assim:
/^@@.*start$/!b
- Se uma
^
linha inteira $
que !
não /
coincidir com /
o padrão acima, então é b
criados em rancho fora do script e autoprinted - por isso a partir deste ponto estamos apenas trabalhando com uma série de linhas que começou com o padrão.
s//nl <<\\@@/
- o
s//
campo vazio /
representa o último endereço sed
tentado corresponder - portanto, este comando substitui a @@.*start
linha inteira nl <<\\@@
.
:l;N
- O
:
comando define um rótulo de filial - aqui eu defino um chamado :l
abel. O N
comando ext anexa a próxima linha de entrada ao espaço do padrão seguido por um \n
caractere ewline. Essa é uma das poucas maneiras de obter uma linha de \n
ew em um sed
espaço de padrão - o \n
caractere de ewline é um delimitador seguro para um sed
der que faz isso há algum tempo.
s/\(\n@@\)[^\n]*end$/\1/
- essa
s///
ubstituição só pode ser bem-sucedida depois que uma partida é encontrada e somente na primeira ocorrência seguinte de uma linha final . Ele atuará apenas em um espaço de padrão no qual a linha de \n
ew final será imediatamente seguida pela @@.*end
marcação do final $
do espaço de padrão. Quando ele age, ele substitui toda a cadeia correspondente pelo \1
primeiro \(
grupo \)
, ou \n@@
.
Tl
- o
T
comando est ramifica para um rótulo (se fornecido) se uma substituição bem-sucedida não ocorreu desde a última vez que uma linha de entrada foi puxada para o espaço do padrão (como eu faço N
) . Isso significa que toda vez que uma linha de \n
ew é anexada ao espaço do padrão que não corresponde ao seu delimitador final, o T
comando est falha e se ramifica de volta ao :l
abel, o que resulta em sed
puxar a N
linha ext e girar até obter êxito.
e
Quando a substituição para o jogo final é bem sucedido e o script não suporta a filial de uma falha T
est, sed
vai e
xecute um comando que l
ooks como este:
nl <<\\@@\nline X\nline Y\nline Z\n@@$
Você pode ver isso editando a última linha lá para parecer Tl;l;e
.
Imprime:
line A
line B
1 line X
2 line Y
3 line Z
line C
line D
1 line M
2 line N
3 line O
while ... read
Uma última maneira de fazer isso, e talvez a maneira mais simples, é usar um while read
loop, mas por um bom motivo. A concha - (principalmente uma bash
concha) - normalmente é bastante abismal ao lidar com entradas em grandes quantidades ou em fluxos constantes. Isso também faz sentido - o trabalho do shell é manipular caracteres de entrada por caractere e chamar outros comandos que possam lidar com coisas maiores.
Mas, o mais importante é que, em relação ao seu papel, o shell não deve read
sobrecarregar muito a entrada - ele é especificado para não armazenar em buffer a entrada ou a saída a ponto de consumir tanto ou não retransmitir o suficiente a tempo de que os comandos que ele chama sejam deixados em falta. - para o byte. Portanto, read
é um excelente teste de entrada - para return
informações sobre se há entrada restante e você deve chamar o próximo comando para lê-la - mas, de outra forma, geralmente não é o melhor caminho a percorrer.
Aqui está um exemplo, no entanto, de como alguém pode usar read
e outros comandos para processar a entrada em sincronia:
while IFS= read -r line &&
case $line in (@@*start) :;; (*)
printf %s\\n "$line"
sed -un "/^@@.*start$/q;p";;
esac;do sed -un "/^@@.*end$/q;=;p" |
paste -d: - -
done <infile
A primeira coisa que acontece para cada iteração é read
puxar uma linha. Se for bem-sucedido, significa que o loop ainda não atingiu o EOF e, portanto, no case
correspondente ao delimitador de início, o do
bloco é executado imediatamente. Else, printf
imprime a $line
ele read
e sed
é chamado.
sed
vai p
rint cada linha até encontrar o início marcador - quando q
UITS entrada inteiramente. O -u
switch nbuffered é necessário para o GNU sed
porque ele pode armazenar um buffer com avidez, mas - de acordo com a especificação - outros POSIX sed
s devem funcionar sem nenhuma consideração especial - desde que <infile
seja um arquivo comum.
Quando o primeiro sed
q
sai, o shell executa o do
bloco do loop - que chama outro sed
que imprime todas as linhas até encontrar o marcador final . Ele canaliza sua saída para paste
, porque imprime números de linhas cada um em sua própria linha. Como isso:
1
line M
2
line N
3
line O
paste
em seguida, cola os :
caracteres nos caracteres, e toda a saída se parece com:
line A
line B
1:line X
2:line Y
3:line Z
line C
line D
1:line M
2:line N
3:line O
Estes são apenas exemplos - tudo poderia ser feito nos blocos de teste ou de execução aqui, mas o primeiro utilitário não deve consumir muita entrada.
Todos os utilitários envolvidos leram a mesma entrada - e imprimiram seus resultados - cada um por sua vez. Esse tipo de coisa pode ser difícil de pegar o jeito - porque diferentes utilitários vai tamponar mais do que outros - mas você pode geralmente dependem de dd
, head
e sed
para fazer a coisa certa (embora, para o GNU sed
, você precisa do interruptor cli) e você sempre deve poder confiar read
- porque é, por natureza, muito lento . E é por isso que o loop acima chama apenas uma vez por bloco de entrada.
nl
não precisa acumular estado . Vejanl -d
e verifique suasman
/info
páginas para obter informações sobrenl
o delimitador de seção .