Eu tenho um arquivo com várias linhas e quero juntar as linhas se ambas se enquadrarem em um padrão específico.
Eu sei que posso encontrar linhas que se encaixem no padrão e obter a próxima linha com:
grep -E -A1 'Pattern' filename
Mas como posso verificar se a próxima linha também se enquadra no padrão e como faria para unir as duas?
Por exemplo, tenho um arquivo como este:
Hello
i
am
John
Smith
Um padrão de exemplo poderia ser o seguinte:
'^[A-Z][a-z]+'
Então, neste caso, eu gostaria de combinar as linhas, se ambas começarem com letras maiúsculas.
A saída que eu gostaria de alcançar seria:
Hello
i
am
John Smith
Responder1
/^[A-Z][a-z]+/{
:a
N
/\n[A-Z][a-z]+/{
s/\n/ /
b a
}
}
Salve como join.sed
e para executar: sed -Ef join.sed file
.
Se a linha corresponder ao padrão, iniciamos um loop que anexa a próxima linha ao espaço do padrão e substitui o caractere de nova linha por um espaço, desde que essa linha também corresponda ao padrão.
Para GNU Sed você pode reduzi-lo para uma linha:
sed -E '/^[A-Z][a-z]+/{:a;N;/\n[A-Z][a-z]+/{s/\n/ /;b a}}' file
Alternativamente, um script Awk join.awk
, para o qual o padrão deve ser fornecido como p
:
{
if($0~p)c+=1
else c=0
printf "%s%s", (c>1 ? " " : ors), $0
ors=ORS
}
END{print ""}
Executar: awk -f join.awk p='^[A-Z][a-z]+' file
.
Responder2
Usando sed
caractere nulo como separador ( -z
):
$ sed -z 's/\([A-Z][a-z]\+\)\n\([A-Z][a-z]\+\)/\1 \2/'
Hello
i
am
John Smith
Responder3
Usando Raku (anteriormente conhecido como Perl_6)
raku -e 'given lines.join("\n") { S/ $<first>=[<upper><lower>+] \n $<last>=[<upper><lower>+] /$<first> $<last>/.put};'
Entrada de amostra:
Hello
i
am
John
Smith
goodbye
Saída de amostra:
Hello
i
am
John Smith
goodbye
Acima está uma solução codificada em Raku, um membro da família de linguagens Perl. Os dados são given
para Raku na forma de lines
, mas como lines
a entrada automática de rotina do Raku, os dados são join
editados com novas linhas. Embora isso possa parecer um pouco complicado, a vantagem é que a lines
rotina do Raku lê os dados preguiçosamente, ou seja, o código acimadeveria estareficiente em termos de memória.
Raku implementa um S///
operador "não destrutivo", que é semelhante (se não idêntico) ao s///
operador familiar (Raku também tem esse). O S
operador de capital tem uma vantagem na medida em que"deixa a string original intacta e retorna a string resultante em vez de $/ (a variável de correspondência)."
Dentro da metade correspondente (esquerda) do S///
operador,capturas nomeadasestão empregados. O mecanismo regex primeiro procura [<upper><lower>+]
e atribui-o ao capture nomeado $<first>
, depois procura por a \n
(nova linha) e, finalmente, procura outro [<upper><lower>+]
, desta vez atribuindo-o ao capture nomeado $<last>
. Para finalizar, dentro da metade de substituição (direita) do S///
operador, as duas capturas nomeadas $<first> $<last>
são usadas para substituir a correspondência do lado esquerdo, emboracomum espaço esema \n
nova linha no meio.
Uma maneira alternativa de fazer a mesma coisa está abaixo. O código omite capturas nomeadas, em vez disso, usa <(\n)>
para descartar tudo do objeto de correspondência, exceto o que está dentro dos <(…)>
marcadores de captura. Então, na substituição, \n
é substituído por espaço:
raku -e 'put S/ [<upper><lower>+] <(\n)> [<upper><lower>+] / / given lines.join("\n");'
[Observe que o código acima só será reduzido George\nHerbert\nWalker\nBush
de 4 linhas para 3 ( George Herbert\nWalker\nBush
). Se você quiser que todas as ocorrências consecutivas em linha sejam [<upper><lower>+]
retornadas em uma linha, sinta-se à vontade para postar essa pergunta].
https://docs.raku.org/linguagem/regexes#S///_non-destructive_substitution
https://docs.raku.org/language/regexes#index-entry-regex__Named_captures-Named_captures
https://raku.org