comando grep para exibir todas as linhas que começam e terminam com o mesmo caractere

8

Quero saber como usar greppara exibir todas as linhas que começam e terminam com o mesmo caractere.

Nayan Jariwala
fonte

Respostas:

14

POSIXly:

pattern='\(.\).*\1
.'
grep -x -- "$pattern" file

Não funcionará se a linha começar ou terminar com um caractere de byte inválido; se você quiser cobrir esse caso, poderá adicionar LC_ALL=C, embora LC_ALL=Cfuncione apenas com dados de caracteres de byte único.


perl6 parece ser a melhor ferramenta, se você a tiver em sua caixa:

$ printf '\ue7\u301 blah \u107\u327\n121\n1\n123\n' |
  perl6 -ne '.say if m/^(.).*$0$/ || /^.$/'
ḉ blah ḉ
121
1

Embora ainda engasgue com caracteres inválidos.


Observe que perl6o texto será alterado, transformando-o em NFCforma:

$ printf '\u0044\u0323\u0307\n' |
  perl6 -pe ''                  |
  perl -CI -ne 'printf "U+%04x\n", ord for split //'
U+1e0c
U+0307
U+000a

$ printf '\u0044\u0323\u0307\n' |
  perl -pe ''                   |
  perl -CI -ne 'printf "U+%04x\n", ord for split //'
U+0044
U+0323
U+0307
U+000a

Internamente, perl6armazena a string no NFGformulário (stand for Normalization Form Grapheme), que é uma perl6maneira inventada de lidar adequadamente com grafemas não pré-compostos:

$ printf '\u0044\u0323\u0307\n' | perl6 -ne '.chars.say'
1
$ printf '\u0044\u0323\u0307\n' | perl6 -ne '.codes.say'
2
cuonglm
fonte
2
O manuseio de texto Unicode pelo Perl é nada menos que exemplar, a ponto de muitas tarefas "simples" no Perl serem praticamente impossíveis de serem implementadas usando outras ferramentas, pelo menos com o mesmo nível de correção.
Dietrich Epp
1
Note-se que perl6, embora o texto seja alterado (gire-o para NFC (formulário de normalização "composto")).
Stéphane Chazelas
@ StéphaneChazelas: Sim, ponto justo. Observe também que a string in perl6é armazenada no NFGformulário ( Gfor Grapheme), que é uma perl6maneira de lidar adequadamente com grafemas não pré-compostos.
cuonglm
10

Não grep, mas awk:

awk -F "" 'NF && $1 == $NF'

Esses casos especiais são tratados:

  • não imprime linhas vazias
  • sempre imprime linhas de 1 caractere

Um FS vazio divide o registro em um caractere por campo em gawk, mawke busybox awk(bytes, não caracteres para os dois últimos), mas não é padrão e não funciona nas implementações awkderivadas do original por A, W e K, como nos BSDs e nos escritórios comerciais. Mais portátil, mas mais para digitar:

awk '/./ && substr($0,1,1) == substr($0,length)'
rudimeier
fonte
1
Observe que, FScomo a string vazia não é padrão e não funcionará em alguma awkimplementação.
cuonglm
2
Alternativa que evite a divisão e é totalmente portátil (até mesmo para 'velho' awk o maximamente terrível Solaris) awk 'length&&substr($0,1,1)==substr($0,length)'(argumento padrão nota lengthé $0, e ação padrão é {print $0})
dave_thompson_085
@ dave_thompson_085: thx, estou apenas usando sua dica de ação padrão para ter o comando mais curto.
rudimeier 21/09/16
Firne. Uma pequena correção; meu teste para Solaris awk velho estava enganado (eu acidentalmente tinha XPG4 on), mas este método não funciona em nawkque é quase tão ruim :-)
dave_thompson_085
8
grep -xe '\(.\).*\1' -e .

Exemplo:

$ printf '%s\n' il y était cet été  | grep -xe '\(.\).*\1' -e .
y
été

-xé para correspondência exata (correspondência em toda a linha). \1sendo uma referência ao personagem capturado em \(.\). Nós adicionamos a -e .para cuidar do caso especial de uma linha que contém um único caractere.

Ele assume que a entrada contém texto válido no código do idioma atual.

A correspondência está no caractere , não no byte (aqueles que estão em UTF-8 são os dois bytes 0xc3 0xa9, por exemplo), nem no cluster graphem (não funcionaria se aqueles fossem escritos em sua forma decomposta e eseguidos pelo U + 0301 combinação de sotaque agudo, por exemplo).

Para trabalhar em clusters de graphem, com um grepsuporte -Ppara PCRE:

$ printf 'e\u0301te\u0301\n' | grep -xPe '(\X).*\1|\X'
été

Isso pressupõe que a decomposição é a mesma para os dois grupos, por exemplo, um expresso como c U+0301 U+0327não corresponderia a um expresso como c U+0327 U+0301ou ć( U+0107) U+0327ou ç( U+00E7) U+0301ou ḉ ( U+1E09). Para isso, você precisa fazer a verificação em um formulário normalizado:

$ printf '\ue7\u301 blah \u107\u327\n' |
  perl -MUnicode::Normalize -C -ne '
    print if /^\X$/ || NFC($_) =~ /^(\X).*\1$/'
ḉ blah ḉ
Stéphane Chazelas
fonte
1
Se você tiver perl6, então perl6 -ne '.say if m/^(.).*$0$/ || /^.$/'deve fazer todos os trabalhos para você.
cuonglm
1

Alternativa rápida para python2:

python -c 'import sys;[sys.stdout.write(l) for l in sys.stdin if len(l)>1 and l.rstrip("\n").endswith(l[0])]' < input.txt

Exemplo:

$ python -c 'import sys;[sys.stdout.write(l) for l in sys.stdin if len(l)>1 and l.rstrip("\n").endswith(l[0])]' < input.txt  | cat -A 
nathan$
 ookie $
a line a$
Sergiy Kolodyazhnyy
fonte
Falha se a linha contiver espaços à direita ou à esquerda, exemplo `121`.
cuonglm
@cuonglm isso é verdade. Mas era necessário seguir ou liderar um espaço em branco? Isso faz o trabalho solicitado - verifique se o caractere principal e o último são os mesmos. Espaço em branco ainda é um personagem ascii, não?
Sergiy Kolodyazhnyy
@cuonglm o seu também falhou com o espaço à direita e à esquerda, a propósito :) #
Sergiy Kolodyazhnyy
Seu código remove os espaços em branco iniciais e finais, alterando a linha de entrada. Também dá um erro para linhas vazias.
Rudimeier # 21/16
@Serg: Como? minha resposta apenas grepping, não modifica a entrada.
cuonglm