Просто сопоставьте ту же обратную ссылку в sed:
sed ':l; s/\(^\|[^[:alpha:]]\)\([[:alpha:]]\{1,\}\)[^[:alpha:]]\{1,\}\2\($\|[^[:alpha:]]\)/\1\2\3/g; tl'
Как это работает:
:l
- создайте метку l
для перехода. См. tl
ниже. s
- замена /
\(^\|[^[:alpha:]]\)
- совпадение начала строки или не алфавитного c символа. Это делается для того, чтобы следующая часть соответствовала всему слову, а не только суффиксу. \([[:alpha:]]\{1,\}\)
- соответствует слову - один или несколько букв c символов. [^[:alpha:]]\{1,\}
- соответствует не-слово - один или несколько не алфавитных c символов. \2
- совпадают с тем же, что и во втором \(...\)
- ie. соответствует слову. \($\|[^[:alpha:]]\)
- соответствует концу строки или соответствует не алфавиту c символа. Таким образом, мы сопоставляем все второе слово, а не только его префикс. /
\1\2\3
- замените его на <beginning of the line or non-alphabetic prefix character><the word><end of the line or non-alphabetic suffix character found>
/
g
- заменить глобально. Но поскольку регулярное выражение никогда не возвращается назад, оно будет заменять 2 слова за раз.
tl
- Перейти к метке l
, если последняя команда s
была успешной. Это здесь, так что когда есть 3 одинаковых слова, например true true true
, они должным образом заменяются одним true
.
Без \(^\|[^[:alpha:]]\)
и \($\|[^[:alpha:]]\)
, без их, например, true rue
будет заменено на true
, потому что суффикс rue rue
будет соответствовать.
Ниже приведены другие мои решения, которые также удаляют повторяющиеся слова в строках.
My Первое решение было с uniq
. Поэтому сначала я преобразую ввод в пары в формате <non-alphabetical sequence separating words encoded in hex> <a word>
. Затем запустите его через uniq -f1
, игнорируя первое поле, а затем выполните обратное преобразование. Это будет очень медленно:
# recreate input
cat <<EOF |
true true, rohith Rohith;
cold burn, and fact and fact good good?
EOF
# insert zero byte after each word and non-word
# the -z option is from GNU sed
sed -r -z 's/[[:alpha:]]+/\x00&\x00/g' |
# for each pair (non-word, word)
xargs -0 -n2 sh -c '
# ouptut hexadecimal representation of non-word
printf "%s" "$1" | xxd -p | tr -d "\n"
# and output space with the word
printf " %s\n" "$2"
' -- |
# uniq ignores empty fields - so make sure field1 always has something
sed 's/^/-/' |
# uniq while ignoring first field
uniq -f1 |
# for each pair (non-word in hex, word)
xargs -n2 bash -c '
# just `printf "%s" "$1" | sed 's/^-//' | xxd -r -p` for posix shell
# change non-word from hex to characters
printf "%s" "${1:1}" | xxd -r -p
# output word
printf "%s" "$2"
' --
Но затем я заметил, что sed
хорошо справляется с токенизацией ввода - он помещает нулевые байты между каждым словом и несловесными токенами. Так что я мог легко читать поток. Я могу игнорировать повторяющиеся слова в awk, прочитав разделенный нулями поток в GNU awk и сравнив последнее прочитанное слово:
cat <<EOF |
true true, rohith Rohith;
cold burn, and fact and fact good good?
EOF
sed -r -z 's/[[:alpha:]]+/\x00&\x00/g' |
gawk -vRS='\0' '
NR%2==1{
nonword=$0
}
NR%2==0{
if (length(lastword) && lastword != $0) {
printf "%s%s", lastword, nonword
}
lastword=$0
}
END{
printf "%s%s", lastword, nonword
}'
Вместо нулевого байта в качестве разделителя записей можно использовать что-то уникальное, например ^
символ, таким образом, его можно использовать с не-GNU awk-версией, протестированной с mawk, доступной в repl. Сократил скрипт, используя здесь более короткие имена переменных:
cat <<EOF |
true true, rohith Rohith;
cold burn, and fact and fact good good?
EOF
sed -r 's/[[:alpha:]]+/^&^/g' |
awk -vRS='^' '
NR%2{ n=$0 }
NR%2-1 && length(l) && l != $0 { printf "%s%s", l, n }
NR%2-1 { l=$0 }
END { printf "%s%s", l, n }
'
Проверено на repl . Вывод фрагментов:
true, rohith Rohith;
cold burn, and fact and fact good?