Como detectar o fim da linha com o sed

14

Estou procurando uma maneira de executar a substituição apenas quando o último caractere for uma nova linha, usando sed.

Por exemplo:

lettersAtEndOfLine

é substituído, mas isso não é:

lettersWithCharacterAfter&

Como sednão funciona bem com novas linhas, não é tão simples quanto

$ sed -E "s/[a-zA-Z]*\n/replace/" file.txt

Como pode ser isto alcançado?

Matthew D. Scholefield
fonte

Respostas:

20

Com o padrão sed, você nunca verá uma nova linha no texto lido de um arquivo. Isso ocorre porque sedlê linha por linha e, portanto, não há nova linha no final do texto da linha atual no sedespaço do padrão. Em outras palavras, sedlê dados delimitados por nova linha e os delimitadores não fazem parte do que um sedscript vê.

Expressões regulares podem ser ancoradas no final da linha usando $(ou no início, usando ^). A ancoragem de uma expressão no início / fim de uma linha obriga a corresponder exatamente lá, e não apenas em qualquer lugar na linha.

Se você deseja substituir algo que corresponda ao padrão [A-Za-z]*no final da linha por algo, ancore o padrão da seguinte maneira:

[A-Za-z]*$

... forçará a correspondência no final da linha e em nenhum outro lugar.

No entanto, como [A-Za-z]*$também não corresponde a nada (por exemplo, a cadeia vazia presente no final de cada linha), você precisa forçar a correspondência de algo , por exemplo, especificando

[A-Za-z][A-Za-z]*$

Portanto, sua linha de comando sed será assim

$ sed 's/[A-Za-z][A-Za-z]*$/replace/' file.txt

Eu não usei o -Eswitch aqui porque não é necessário. Com isso, você poderia ter escrito

$ sed -E 's/[A-Za-z]+$/replace/' file.txt

É uma questão de gosto.

Kusalananda
fonte
Embora eu saiba como fazer isso, você receberá um +1 apenas pelo uso do termo técnico. :) Então isso é chamado de ancoragem - é bom saber. Por enquanto, eu sempre tive que parafrasear ... Outra observação sobre+ : você pode usá-lo mesmo sem usar o regex extendido, lembre-se de escrevê-lo como \+. Então sed -e 's/[A-Za-z]\+$/replace/' file.txt, funcionará perfeitamente mesmo sem o GNU sedinstalado. E não se esqueça: Não use -E, pois o GNU sednão suporta .
SyntaxError
1
@ syntaxerror - Eu acho que você pode remover a última frase ou, pelo menos, desdobrar, como gnu seddefinitivamente suporta-E .
don_crissti
@don_crissti Bem, eu pensei que você estivesse nesta rede há tempo suficiente para saber que não há como desdobrar partes de um comentário (a menos que você o reescreva inteiramente). Então, deixe-me corrigir: O GNU sedpode suportar "silenciosamente" -E, mas não está documentado na página de manual (nem no manual do Texinfo (verifique os dois)). Por isso, presumi que não era suportado (o que era uma suposição errada, afinal). De qualquer forma, você está certo, porque pelo menos o GNU sednão vai reclamar se você usar esta opção.
Syntaxerror #
@don_crissti Fico feliz que você fez! Portanto, pelo menos foi confirmado que sed será necessária uma opção específica que ainda não foi devidamente documentada. Isso sempre é útil; se ninguém estiver ciente da falta de documentação, ninguém nunca a corrigirá.
Syntaxerror 15/11/2015
@syntaxerror, consulte unix.stackexchange.com/a/310454/135943 . Obviamente, se você precisar trabalhar com sistemas antigos como o RHEL 5, estará usando uma versão GNU sed que não suporta -E.
Wildcard
3
sed "s/[a-zA-Z]*$/replace/" input.txt > result.txt

Ou, a maneira desnecessária e complexa:

Eu descobri, isso pode ser feito, ainda usando sed, com a ajuda de tr. Você pode atribuir outro caractere para representar o final da linha. Outro caractere temporário deve ser usado, neste caso "" ". Vamos usar "~" para representar o final da linha:

tr '\n' '`' <input.txt >output.txt
sed -i "s/`/~`/" output.txt
tr '`' '\n' <output.txt >result.txt

E, em seguida, para executar a pesquisa real e substituir, use "~" em vez de "\ n":

sed -i -E "s/[a-zA-Z]*~/replace/" result.txt

E limpe o caractere extra nas outras linhas:

sed -i "s/~//" result.txt

Obviamente, tudo isso pode ser canalizado, resultando em algo como:

tr '\n' '`' <input.txt | sed -e "s/`/~`/" | tr '`' '\n' | sed -E -e "s/[a-zA-Z]*~/replace/" | sed "s/~//" > result.txt
Matthew D. Scholefield
fonte
3
Não tenho certeza de que entendi ... Por que você não se ancora no final da linha $? por exemplos/[a-zA-Z]*$/replace/
don_crissti 01/06/2015
1
2 pontos: 1) É melhor usar ao \+invés de, *pois o último permite zero letras no final da string; 2) Você pode usar uma classe de personagem [[:alpha:]]. Então:sed 's/[[:alpha:]]\+$/replace/' file
glenn jackman
@glennjackman Para que serve a barra invertida antes do sinal de mais? Isso não corresponderia ao caractere de adição?
Matthew D. Scholefield
1
O GNU sed sem a -ropção usa esta sintaxe de expressão regular .
Glenn Jackman
0

No snippet de código (quebrado) que você postou, parece que você deseja substituir a nova linha também. Nesse caso, a ancoragem regex por si só não pode ajudá-lo. A seguir está uma solução:

sed '/[[:alpha:]]\+$/{N;s/[[:alpha:]]\+\n/replace/}' your_file

Quebrado:

  • /[a-zA-Z]\+$/{} significa aplicar o que estiver dentro dos curlies às linhas que correspondem ao regex.
  • O regex é aquele que usa a ancoragem como visto em sua própria resposta , modificado para levar em consideração os comentários de glenn jackman .
  • Dentro das curvas, Nsignifica "anexar a próxima linha ao buffer ativo" (o que sedchama de 'espaço padrão')
  • Finalmente, a s///declaração é a sua substituição necessária. Agora funciona porque o espaço do padrão contém duas linhas sucessivas e, portanto, a nova linha faz parte dele.
Joseph R.
fonte
0

Para encontrar o final da linha, basta usar o $ -sign :

Sem âncora no final da linha:

sed -n '/pattern/p' file 

Sem âncora no final da linha:

sed -n '/pattern$/p' file
Usuário desconhecido
fonte