Как использовать регулярные выражения для сопоставления прописных слов, которые не являются последовательными дубликатами строчных слов в других местах файла - PullRequest
0 голосов
/ 13 декабря 2018

Я новичок в регулярных выражениях и мне нужна помощь.У меня есть базовое понимание классов, якорей и внешних выражений регулярных выражений, но этот конкретный вариант использования оказывается трудным для меня.

Я пытаюсь проанализировать вывод сценария, который выполняется ежемесячно, который выводит списокпользователи для инвентаризации.Я хочу использовать регулярное выражение для синтаксического анализа файла на соответствие следующим условиям:

  1. Регулярное выражение для поиска слов нижнего регистра, которые повторяются как слова верхнего регистра.Эти прописные слова не находятся в одной строке, не являются последовательными вхождениями и могут быть в новых строках или в другом месте файла.
  2. Мне нужно регулярное выражение, которое может отображать повторяющиеся совпадения в верхнем регистре
  3. Еще одно регулярное выражение, которое удаляет повторяющиеся совпадения в верхнем регистре

Вот пример вывода файла, который я пытаюсь проанализировать:

"hello","2018-11-19","unitelife"
"world","2018-11-09","unitelife"
"foo","2018-11-16","unitelife"
"bar","2018-10-05","unitelife"
"hello123","2018-09-06","unitelife"
"HELLO123","2018-11-18","unitelife"
"FOO","2018-11-20","unitelife"
"WOWMUCHHAPPY","2018-10-20","unitelife"
"suchjoy","2017-11-28","unitelife"

Требуемые совпадения Iя ищу:

HELLO123
FOO

Я пробовал несколько комбинаций, предложенных в ссылках, приведенных ниже, но мне кажется, что ничего не работает.Либо я пытаюсь использовать неправильные комбинации, либо эта функция невозможна.Кроме того, в большинстве тем обсуждаются последовательные слова или буквы / символы.

Я хочу заранее извиниться, если это обсуждалось ранее или было неправильно классифицировано.Пожалуйста, дайте мне знать, вместо того, чтобы голосовать по нему, чтобы я мог отредактировать, закрыть или реклассифицировать вопрос, чтобы соответствовать стандартам соответственно.

С уважением,

Смиренно преданный ученик

Ответы [ 3 ]

0 голосов
/ 13 декабря 2018

Вот решение, использующее просто bash, без регулярных выражений:

> cat filter.sh
#!/bin/bash

declare -A lower=()
declare -A upper=()

while IFS= read -r line; do
  eval "words=( $(tr ',' ' ' <<< "$line") )"
  for w in "${words[@]}"; do
    [[ "${w^^}" = "$w" ]] && upper["$w"]=1 || lower["$w"]=1
  done
done

for u in "${!upper[@]}"; do
  exists=${lower["${u,,}"]+foo}
  [[ -n "$exists" ]] && echo "$u"
done

Здесь я использую пару приемов.

Во-первых, я использую ассоциативные массивы, чтобы отсеять повторы.Например, если "HELLO123" появляется в файле несколько раз, он будет засчитан только один раз.

Во-вторых, я анализирую CSV, используя tr для замены запятых на пробелы, а затем eval для анализа строки в массиве, используя тот факт, что отдельные словавсегда заключено в двойные кавычки.

Наконец, я использую [[ "${w^^}" = "$w" ]] в качестве теста, чтобы проверить, содержит ли слово все заглавные буквы.Синтаксис ${w^^} представляет собой bash-ism, который преобразует переменную в верхний регистр.Я также использую ${u,,} во втором цикле, который преобразует $u в строчные буквы.Обратите внимание, что если у вас есть слово с сочетанием прописных и строчных букв, оно будет засчитано как строчное.Если это не соответствует вашим ожиданиям, вы можете изменить логику.

Первый цикл просто читает с stdin, разбивает каждую строку на отдельные слова и затем классифицирует каждое слово как в верхнем или нижнем регистре.Опять же, я использую ассоциативные массивы, так что каждое слово (независимо от регистра) учитывается только один раз.

Второй цикл просто проходит по ключам ассоциативного массива upper ({${!upper[@]}), которые являются только всеми заглавными словами, встречающимися на входе.Для каждого слова проверяется, встречалось ли также совпадение строчных слов.Синтаксис ${lower["${u,,}"]+foo} просто проверяет, существует ли строчное слово в массиве lower.Часть foo - это просто произвольная строка.Вы также можете использовать bar или exists или abc.Вот как вы проверяете наличие ключа в ассоциативном массиве в bash.Если ключ существует в массиве, выражение будет иметь значение "foo", в противном случае оно будет выглядеть как пустая строка.Вот что проверяет последующий -n тест.

Пример:

> cat input.txt
"hello","2018-11-19","unitelife"
"world","2018-11-09","unitelife"
"foo","2018-11-16","unitelife"
"bar","2018-10-05","unitelife"
"hello123","2018-09-06","unitelife"
"HELLO123","2018-11-18","unitelife"
"FOO","2018-11-20","unitelife"
"WOWMUCHHAPPY","2018-10-20","unitelife"
"suchjoy","2017-11-28","unitelife"

> cat input.txt | ./filter.sh
FOO
HELLO123

ПРИМЕЧАНИЕ: Пожалуйста, не используйте eval в рабочем коде.Он подвержен всевозможным злоупотреблениям и неудачам из-за неожиданных вещей, появляющихся на входе.Например, рассмотрим, что произойдет, если вы вставите в строку ввода следующую строку:

"); rm -rf *; foo=("

Тогда eval в конечном итоге оценит строку "words=(); rm -rf *; foo=()".Определенно не хорошо.Я использовал здесь только eval как быстрый и грязный способ разбора CSV.Есть намного лучших (и более безопасных ) способов анализа CSV в bash.Смысл этого решения заключается в использовании ассоциативных массивов для отслеживания прописных и строчных слов при фильтрации дубликатов.

Edit: Также обратите внимание, что появляются FOO и HELLO123вышли из строя на выходе.Это связано с тем, что ассоциативные массивы не хранят ключи в том порядке, в котором вы их создали.Поэтому, когда вы делаете ${!hash[@]}, то это порядок, в котором будут располагаться ключи. Если это проблема для вас, вы можете сохранить отдельный регулярный массив для сохранения порядка.

0 голосов
/ 13 декабря 2018

не с регулярным выражением, но с использованием функций awk s toupper () и tolower ()

$ awk -F, '{lower=tolower($1)} lower==$1 {a[$1];next} 
      toupper($1)==$1 && lower in a{print $1}' file

"HELLO123"
"FOO"

, если поле строчное, добавьте к набору, если прописные и строчные буквы находятся в заданной печати.

здесь важен порядок (строчные должны стоять перед прописными), как в вашем примере.Если нет, нужно конвертировать в двухпроходную версию.Также легко удалить цитаты, если не нужно.

0 голосов
/ 13 декабря 2018

Вы можете использовать шаблон

(?sm)^"([a-z\d]+)"(?i)(?=(?:[^\n]*\n)+?"(?=\1")(?-i)[A-Z\d]+")

https://regex101.com/r/nM3iBH/2

Идея состоит в том, чтобы начать с учета регистра (не i) и сопоставить строку в нижнем регистрев начале строки внутри кавычек.Затем, включите регистр в чувствительном флаге и посмотрите на ту же строку в начале строки внутри кавычек.Снова включите чувствительность к регистру, и снова совпадет с той же самой строкой в ​​предвкушении, разрешив только заглавные буквы (и цифры).

Обратите внимание, что это (как и любой алгоритм регулярных выражений для этой задачи) имеет сложность O(N^2), поскольку при любом совпадении необходимо проверять всю оставшуюся подстроку на соответствие (в верхнем регистре).

Также обратите внимание на использование " s вместо \b s, которые вымы подумали - использование " s вместо этого является более точным с учетом такого рода ввода и приведет к значительно меньшему количеству шагов в целом.

Шаблон довольно строг, ради сокращения шагов.Выделено:

  • (?sm)^"([a-z\d]+)" - начальные флаги, захватывать слова в нижнем регистре в начале строки
  • (?i) - включить регистр символов без учета, чтобы будущее \1 обратная ссылка будет работать правильно
  • (?=(?:[^\n]*\n)+?"(?=\1")(?-i)[A-Z\d]+") Большой прогноз для:
    • (?:[^\n]*\n)+ - совпадение с символами новой строки, за которыми следует символ новой строки
    • "(?=\1") обратная ссылкаоригинальное совпавшее слово в кавычках в начале строки
    • (?-i) Повторно включить чувствительность к регистру, чтобы можно было проверить заглавные буквы
    • [A-Z\d]+" - сопоставить заглавные буквы и цифры, а затемна "

Чтобы заменить строку заглавного слова пустой строкой, вместо использования большого заглядывания, соответствует все междуслово в нижнем регистре и слово в верхнем регистре в группе (таким образом, у вас есть две группы, слово в нижнем регистре и все, что следует за ним до слова в верхнем регистре), затем сопоставьте слово в верхнем регистре и замените натолько первые два грupps (тем самым заменяя строку слова в верхнем регистре):

(?sm)^("[a-z\d]+")(?i)((?:[^\n]*\n)*[^\n]*)\n(?=\1)(?-i)"[A-Z\d]+"[^\n]*

замените на

\1\2

(или эквивалент в вашей среде)

https://regex101.com/r/nM3iBH/3

Обратите внимание, что если у вас совпадающие совпадения, вам придется делать это итеративно, пока не останется совпадений.

...