Como posso excluir uma nova linha à direita no bash?
10
Estou procurando por algo que se comporte como o de Perl chomp. Estou procurando um comando que simplesmente imprima sua entrada, menos o último caractere se for uma nova linha:
$ printf "one\ntwo\n"| COMMAND_IM_LOOKING_FOR ; echo " done"
one
two done
$ printf "one\ntwo"| COMMAND_IM_LOOKING_FOR ; echo " done"
one
two done
(A substituição de comandos no Bash e no Zsh exclui todas as novas linhas à direita, mas estou procurando por algo que exclua uma nova linha no máximo.)
Se você quer um equivalente exato chomp, o primeiro método que me vem à mente é a solução awk que o LatinSuD já publicou . Vou adicionar alguns outros métodos que não são implementados, chompmas implementam algumas tarefas comuns chompusadas com frequência.
Quando você coloca algum texto em uma variável, todas as novas linhas no final são removidas. Portanto, todos esses comandos produzem a mesma saída de linha única:
Se você quiser acrescentar algum texto à última linha de um arquivo ou da saída de um comando, sedpode ser conveniente. Com o GNU sed e a maioria das outras implementações modernas, isso funciona mesmo que a entrada não termine em uma nova linha¹; no entanto, isso não adicionará uma nova linha se ainda não houver uma.
sed '$ s/$/ done/'
¹ No entanto, isso não funciona com todas as implementações do sed: sed é uma ferramenta de processamento de texto e um arquivo que não está vazio e não termina com um caractere de nova linha não é um arquivo de texto.
Isso não é exatamente equivalente a chomp, pois chompapenas exclui no máximo uma nova linha à direita.
Flimm
@Limlim Sim, o equivalente exato mais óbvio chompseria a solução awk que o LatinSuD já publicou. Mas, em muitos casos, chompé apenas uma ferramenta para realizar um trabalho, e eu forneço maneiras de executar algumas tarefas comuns. Deixe-me atualizar minha resposta para esclarecer isso.
Gilles 'SO- stop be evil'
1
Outra perlabordagem. Este lê toda a entrada na memória, por isso pode não ser uma boa ideia para grandes quantidades de dados (use o cuonglm ou a awkabordagem para isso):
$ printf "one\ntwo\n"| perl -0777pe's/\n$//'; echo " done"
one
two done
Essa é uma solução rápida, pois precisa ler apenas um caractere do arquivo e removê-lo diretamente ( truncate) sem ler o arquivo inteiro.
No entanto, ao trabalhar com dados de stdin (um fluxo), os dados devem ser lidos, todos eles. E é "consumido" assim que é lido. Sem retorno (como no truncado). Para encontrar o final de um fluxo, precisamos ler o final do fluxo. Nesse ponto, não há como voltar ao fluxo de entrada, os dados já foram "consumidos". Isso significa que os dados devem ser armazenados em alguma forma de buffer até correspondermos ao final do fluxo e, em seguida, fazer algo com os dados no buffer.
A solução mais óbvia é converter o fluxo em um arquivo e processá-lo. Mas a pergunta pede algum tipo de filtro do fluxo. Não é sobre o uso de arquivos adicionais.
variável
A solução ingênua seria capturar toda a entrada em uma variável:
FilterOne(){ filecontents=$(cat; echo "x");# capture the whole input
filecontents=${filecontents%x};# Remove the "x" added above.
nl=$'\n';# use a variable for newline.
printf '%s'"${filecontents%"$nl"}";# Remove newline (if it exists).}
printf 'one\ntwo'|FilterOne; echo 1done
printf 'one\ntwo\n'|FilterOne; echo 2done
printf 'one\ntwo\n\n'|FilterOne; echo 3done
memória
É possível carregar um arquivo inteiro na memória com o sed. No sed, é impossível evitar a nova linha à direita na última linha. O GNU sed pode evitar a impressão de uma nova linha à direita, mas apenas se o arquivo de origem já estiver em falta. Portanto, não, o simples sed não pode ajudar.
Exceto no GNU awk com a -zopção:
sed -z 's/\(.*\)\n$/\1/'
Com awk (qualquer awk), solte o fluxo inteiro e printfsem a nova linha à direita.
Carregar um arquivo inteiro na memória pode não ser uma boa ideia, pode consumir muita memória.
Duas linhas na memória
No awk, podemos processar duas linhas por loop armazenando a linha anterior em uma variável e imprimindo a atual:
awk 'NR>1{print previous} {previous=$0} END {printf("%s",$0)}'
Processamento direto
Mas poderíamos fazer melhor.
Se imprimirmos a linha atual sem uma nova linha e só imprimirmos quando houver uma linha seguinte, processaremos uma linha por vez e a última linha não terá uma nova linha à direita:
chomp
, poischomp
apenas exclui no máximo uma nova linha à direita.chomp
seria a solução awk que o LatinSuD já publicou. Mas, em muitos casos,chomp
é apenas uma ferramenta para realizar um trabalho, e eu forneço maneiras de executar algumas tarefas comuns. Deixe-me atualizar minha resposta para esclarecer isso.Outra
perl
abordagem. Este lê toda a entrada na memória, por isso pode não ser uma boa ideia para grandes quantidades de dados (use o cuonglm ou aawk
abordagem para isso):fonte
Eu peguei isso de um repositório do github em algum lugar, mas não consigo encontrar onde
delete-trailing-blank-lines-sed
fonte
resumo
Imprimir linhas sem nova linha, adicione uma nova linha somente se houver outra linha para imprimir.
Outras soluções
Se estivéssemos trabalhando com um arquivo, podemos truncar apenas um caractere (se terminar em uma nova linha):
removeTrailNewline () {[[$ (tail -c 1 "$ 1")]] || truncar -s-1 "$ 1"; }
Essa é uma solução rápida, pois precisa ler apenas um caractere do arquivo e removê-lo diretamente (
truncate
) sem ler o arquivo inteiro.No entanto, ao trabalhar com dados de stdin (um fluxo), os dados devem ser lidos, todos eles. E é "consumido" assim que é lido. Sem retorno (como no truncado). Para encontrar o final de um fluxo, precisamos ler o final do fluxo. Nesse ponto, não há como voltar ao fluxo de entrada, os dados já foram "consumidos". Isso significa que os dados devem ser armazenados em alguma forma de buffer até correspondermos ao final do fluxo e, em seguida, fazer algo com os dados no buffer.
A solução mais óbvia é converter o fluxo em um arquivo e processá-lo. Mas a pergunta pede algum tipo de filtro do fluxo. Não é sobre o uso de arquivos adicionais.
variável
A solução ingênua seria capturar toda a entrada em uma variável:
memória
É possível carregar um arquivo inteiro na memória com o sed. No sed, é impossível evitar a nova linha à direita na última linha. O GNU sed pode evitar a impressão de uma nova linha à direita, mas apenas se o arquivo de origem já estiver em falta. Portanto, não, o simples sed não pode ajudar.
Exceto no GNU awk com a
-z
opção:Com awk (qualquer awk), solte o fluxo inteiro e
printf
sem a nova linha à direita.Carregar um arquivo inteiro na memória pode não ser uma boa ideia, pode consumir muita memória.
Duas linhas na memória
No awk, podemos processar duas linhas por loop armazenando a linha anterior em uma variável e imprimindo a atual:
Processamento direto
Mas poderíamos fazer melhor.
Se imprimirmos a linha atual sem uma nova linha e só imprimirmos quando houver uma linha seguinte, processaremos uma linha por vez e a última linha não terá uma nova linha à direita:
awk 'NR == 1 {printf ("% s", $ 0); próximo}; {printf ("\ n% s", $ 0)} '
Ou, escrito de alguma outra maneira:
Ou:
Assim:
fonte