У меня есть такой ввод
LTCYMM SVNNDA DTVEV QLOPGO CUPUR
MMWVJM LIVLI WBSVD UQCMW HBMDA HVVFY BWYSS
NOGWOS JIKKDI GCIQAD MXJNWE SMVFCB GIZVPA GZOHZR WJBMZS
KKPQBP BKDKRU ZTPDPL ZRLUEL HRZZKO KXSKCU YZQTBT RISNKS
VYQQC BFAWI NSZDV HKPGI KVJOC COPPS
JGU YLN MXW ACR BZA HOP
TMCVPT HBNGIH IQYGCI DTQPON WXANKG GMIYZS
CWVT BUBA NSGR MUPO LDNS
Я пытаюсь напечатать строки, где каждое слово содержитпо крайней мере два одинаковых персонажа, используя grep command
Самая длинная строка содержит 8 слов , я думаю, что я могу решить это таким образом , но я чувствую, что это неправильный способ ,
grep '^.*\([A-Z]\)[^ ]*\1[^ ]* [^ ]*\([A-Z]\)[^ ]*\2[^ ]* [^ ]*\([A-Z]\)[^ ]*\3[^ ]* [^ ]*\([A-Z]\)[^ ]*\4[^ ]* [^ ]*\([A-Z]\)[^ ]*\5[^ ]* [^ ]*\([A-Z]\)[^ ]*\6[^ ]* [^ ]*\([A-Z]\)[^ ]*\7[^ ]* [^ ]*\([A-Z]\)[^ ]*\8[^ ]*$/| .... for 7 words | for 6 ...
ожидаемый результат
LTCYMM SVNNDA DTVEV QLOPGO CUPUR
KKPQBP BKDKRU ZTPDPL ZRLUEL HRZZKO KXSKCU YZQTBT RISNKS
решение1
С perl
:
$ perl -ne 'print unless grep {!/(.).*\1/} /\S+/g' file
LTCYMM SVNNDA DTVEV QLOPGO CUPUR
KKPQBP BKDKRU ZTPDPL ZRLUEL HRZZKO KXSKCU YZQTBT RISNKS
Или с grep
реализациями с поддержкой регулярных выражений в стиле Perl:
$ grep -Pve '(?<!\S)(?!\S*(\S)\S*\1)\S' file
LTCYMM SVNNDA DTVEV QLOPGO CUPUR
KKPQBP BKDKRU ZTPDPL ZRLUEL HRZZKO KXSKCU YZQTBT RISNKS
Это печатает строки, которые делаютнет(с -v
) содержат \S
(непробельный символ), которому не предшествует другой непробельный символ ( (?<!\S)
) (или IOW, который является началом слова, разделенного пробелом) и не является началом последовательности непробельных символов, один из которых повторяется ( (?!\S*(\S)\S*\1)
). Так что по сути это похоже на (хотя и менее разборчиво) perl
подход, описанный выше.
Обратите внимание, что они также печатают пустые строки (поскольку не содержат слов, в которых нет повторяющихся символов). Если они вам не нужны, вы можете исключить их, что должно быть тривиально (например, добавив a -e '^\s*$'
в grep
one).
решение2
Использование любого awk в любой оболочке на любой машине Unix:
awk '{
for ( fldNr=1; fldNr<=NF; fldNr++ ) {
numChars = length($fldNr)
numUnq = 0
split("",seen) # you could use delete(seen) here in most awks
for ( charNr=1; charNr<=numChars; charNr++ ) {
if ( !seen[substr($fldNr,charNr,1)]++ ) {
numUnq++
}
}
if ( numUnq == numChars ) {
next
}
}
print
}' file
LTCYMM SVNNDA DTVEV QLOPGO CUPUR
KKPQBP BKDKRU ZTPDPL ZRLUEL HRZZKO KXSKCU YZQTBT RISNKS
решение3
Используя perl
вместе с all
методом из List::Util
модуля мы можем обнаружить искомые строки (все слова с хотя бы одним повторяющимся символом)
perl -MList::Util=all -lane '
print if all { /(.).*\1/ } @F;
' file
Используя GnU sed
мы можем выбрать нужные строки, убедившись, что все нужные поля простираются от начала строки до конца.
$ sed -En '/^\s*(\S*(\S)\S*\2\S*(\s+|$))+$/p' file
Другой способ sed
— это постепенная проверка на наличие повторяющихся символов среди непробельных символов и не выводить шаблон сразу, как только не будет найдено ни одного повторяющегося символа в последовательности непробельных символов.
sed -Ee 'h
:loop
s/^\s+|\s+$//g
s/\S+/&\n/
/(\S).*\1.*\n/!d
s/^[^\n]*\n//
/./bloop
g
' file
Мы используем awk, а затем циклически проходим по каждому слову и каждому символу в слове. Разбиваем слово по символу и проверяем, разбивается ли оно более чем на 2 части => в этом слове обнаружен дубликат. Аналогично в конце текущей строки, если количество обнаруженных дубликатов равно количеству полей => строка помещается для печати.
awk '
{
for (p=i=1+(w=0); i<=NF; i++) {
while (p <= length($i)) {
c = substr($i,p++,1)
if (split($i,a,c) > 2) {
w += p = 1
break
}
}
}
}
w==NF
' file
решение4
Вот еще одно решение на чистом Bash — нет perl
, нет grep
, нет awk
.
#!/bin/bash
set -euo pipefail
containssametwice() {
local -Ai chars=()
local -i i
for ((i = 0; i < ${#1}; ++i)); do
((++chars["${1:i:1}"] < 2)) || return 0
done
return 1
}
while IFS= read -r line; do
read -ra words <<< "$line"
for word in "${words[@]}"; do
containssametwice "$word" || continue 2
done
printf '%s\n' "$line"
done