Команда grep для отображения всех строк, начинающихся и заканчивающихся одним и тем же символом

8

Я хочу знать, как использовать grepдля отображения всех строк, которые начинаются и заканчиваются одним и тем же символом.

Наян Яривала
источник

Ответы:

14

POSIXly:

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

Это не будет работать, если строка начинается или заканчивается недействительным байтовым символом, если вы хотите охватить этот случай, вы можете добавить LC_ALL=C , хотя LC_ALL=Cработает только с однобайтовыми символьными данными.


perl6 кажется лучшим инструментом, если он у вас в коробке:

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

Хотя это все еще задыхается от недопустимых символов.


Обратите внимание, что perl6изменит ваш текст, превратив его в NFCформу:

$ 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

Внутри perl6хранится строка в NFGформе (обозначать Normalization Form Grapheme), которая perl6придумана для правильной работы с предварительно не скомпонованными графемами:

$ printf '\u0044\u0323\u0307\n' | perl6 -ne '.chars.say'
1
$ printf '\u0044\u0323\u0307\n' | perl6 -ne '.codes.say'
2
cuonglm
источник
2
Работа Perl с текстом Unicode - не что иное, как пример, до такой степени, что многие «простые» задачи в Perl практически невозможно реализовать с помощью других инструментов, по крайней мере, с тем же уровнем корректности.
Дитрих Эпп
1
Следует отметить, что perl6изменит текст, хотя (превратить его в NFC (нормализованная форма "составлено")).
Стефан Шазелас
@ StéphaneChazelas: Да, справедливо. Также обратите внимание, что строка in perl6- это store in NFGform ( Gfor Grapheme), что позволяет perl6правильно обрабатывать нескомпонованные графемы.
Cuonglm
10

Не grep, а awk:

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

Эти особые случаи обрабатываются:

  • он не печатает пустые строки
  • всегда печатает 1-символьные строки

Пустой FS разделяет запись на один символ на поле в gawk, mawkи busybox awk(байты, а не символы для последних двух), но не является стандартным и не работает в реализациях, awkпроизводных от исходного A, W и K, как на BSDs и коммерческих единицах. Более портативный, но больше для ввода:

awk '/./ && substr($0,1,1) == substr($0,length)'
rudimeier
источник
1
Обратите внимание, что FSпустая строка не является стандартной и не будет работать в некоторых awkреализациях.
Cuonglm
2
Альтернатива, которая позволяет избежать расщепления и является полностью переносимой (даже для максимально ужасного «старого» awk в Solaris) awk 'length&&substr($0,1,1)==substr($0,length)'(обратите внимание, что по умолчанию используется аргумент lengthis $0, а действие по умолчанию is {print $0})
dave_thompson_085
@ dave_thompson_085: спасибо, я использую подсказку по умолчанию только для самой короткой команды.
rudimeier
Firne. Одна незначительная коррекция; мой тест на старый awk для Solaris был ошибочным (у меня случайно был xpg4), но этот метод работает nawkпочти так же плохо :-)
dave_thompson_085
8
grep -xe '\(.\).*\1' -e .

Пример:

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

-xдля точного соответствия (совпадение по всей строке). \1являясь обратной ссылкой на персонажа, захваченного в \(.\). Мы добавляем a, -e .чтобы позаботиться о специальном случае строки, содержащей один единственный символ.

Предполагается, что ввод содержит действительный текст в текущей локали.

Соответствие идет по символу , а не по байту (например, é в UTF-8 - это два байта 0xc3 0xa9), или кластер graphem (он не будет работать, если эти é были записаны в разложенном виде с eпоследующим U + 0301 например, сочетая острый акцент).

Для работы на кластерах графема с grepподдержкой -PPCRE:

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

При этом предполагается, что разложение является одинаковым для двух кластеров, например, выражение, c U+0301 U+0327которое не соответствует, выражается как c U+0327 U+0301или ć( U+0107) U+0327или ç( U+00E7) U+0301или ḉ ( U+1E09). Для этого вам необходимо выполнить проверку в нормализованной форме:

$ printf '\ue7\u301 blah \u107\u327\n' |
  perl -MUnicode::Normalize -C -ne '
    print if /^\X$/ || NFC($_) =~ /^(\X).*\1$/'
ḉ blah ḉ
Стефан Шазелас
источник
1
Если у вас есть perl6, то perl6 -ne '.say if m/^(.).*$0$/ || /^.$/'должны сделать все работы за вас.
Cuonglm
1

Быстрая альтернатива 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

Пример:

$ 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$
Сергей Колодяжный
источник
Сбой, если строка содержит завершающие или начальные пробелы, например `121`.
Cuonglm
@cuonglm это правда. Но было ли обязательным условием отставание или лидирующие пробелы? Это выполняет задание - проверьте, совпадают ли первый и последний символы. Пробел все еще является символом ascii, нет?
Сергей Колодяжный
@cuonglm Ваш, кстати, тоже не справился с трейлингом и лидирующим пробелом :)
Сергей Колодяжный
Ваш код удаляет начальные и конечные пробелы, поэтому он меняет строку ввода. Также выдает ошибку для пустых строк.
rudimeier
@Serg: как? мой ответ только скупой, он не меняет ввод.
Cuonglm