Проверьте, соответствует ли строка регулярному выражению в скрипте Bash

204

Один из аргументов , что мой сценарий получает это дата в следующем формате: yyyymmdd.

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

Как я могу это сделать? Я пытаюсь использовать регулярные выражения, такие как:[0-9]\{\8}

Питер Найджем
источник
Проверить правильность формата очень просто. Но я не думаю, что вы можете в bash (со встроенными модулями) проверить, действительна ли дата.
RedX

Ответы:

317

Вы можете использовать тестовую конструкцию [[ ]]вместе с оператором соответствия регулярного выражения, =~чтобы проверить, соответствует ли строка шаблону регулярного выражения.

Для вашего конкретного случая вы можете написать:

[[ $date =~ ^[0-9]{8}$ ]] && echo "yes"

Или более точный тест:

[[ $date =~ ^[0-9]{4}(0[1-9]|1[0-2])(0[1-9]|[1-2][0-9]|3[0-1])$ ]] && echo "yes"
#           |^^^^^^^^ ^^^^^^ ^^^^^^  ^^^^^^ ^^^^^^^^^^ ^^^^^^ |
#           |   |     ^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^^^^^^^ |
#           |   |          |                   |              |
#           |   |           \                  |              |
#           | --year--   --month--           --day--          |
#           |          either 01...09      either 01..09     end of line
# start of line            or 10,11,12         or 10..29
#                                              or 30, 31

То есть вы можете определить регулярное выражение в Bash, соответствующее желаемому формату. Таким образом, вы можете сделать:

[[ $date =~ ^regex$ ]] && echo "matched" || echo "did not match"

где команды after &&выполняются, если проверка прошла успешно, и команды after ||выполняются, если проверка не удалась.

Обратите внимание, что это основано на решении Алекса-Даниэля Якименко в « Проверка формата ввода даты пользователем в bash» .


В других оболочках вы можете использовать grep . Если ваша оболочка совместима с POSIX, сделайте

(echo "$date" | grep -Eq  ^regex$) && echo "matched" || echo "did not match"

В рыбе , которая не соответствует POSIX, вы можете сделать

echo "$date" | grep -Eq "^regex\$"; and echo "matched"; or echo "did not match"
Федорки "ТАК прекратить вредить"
источник
19
Я знаю об этом, но мне также нравится принимать во внимание, кто спрашивает и как далеко они находятся с Bash. Если мы предоставим очень сложные условия, они ничего не изучат и просто вернутся, когда у них возникнут другие сомнения. Я предпочитаю давать более понятный ответ.
Федорки 'ТАК прекрати вредить'
7
Хех. Ну, единственный способ научиться это читать много хорошего кода. Если вы даете ложный код, который легко понять, но не рекомендуется использовать - это плохой способ обучения. Также я уверен, что для тех, кто только начал изучать bash (вероятно, уже знает некоторые биты на другом языке), легче понять синтаксис bash для регулярных выражений, чем какую-либо grepкоманду с -Eфлагом.
Алекс-Даниил Якименко-А.
8
@ Aleks-DanielJakimenko Я снова просмотрел этот пост, и теперь я согласен, что лучше всего использовать регулярное выражение bash. Спасибо за указание в хорошем направлении, обновленный ответ.
Федорки "ТАК прекратить вредить"
4
Upvote, что позволяет использовать его немного больше, чем вопрос OP, например, для sh ..
Дерексон
3
@ Aleks-DanielJakimenko использование grep, кажется, лучший вариант, если вы используете sh, fishили другие менее оборудованные оболочки.
Tomekwi
47

В bash версии 3 вы можете использовать оператор '= ~':

if [[ "$date" =~ ^[0-9]{8}$ ]]; then
    echo "Valid date"
else
    echo "Invalid date"
fi

Ссылка: http://tldp.org/LDP/abs/html/bashver3.html#REGEXMATCHREF

ПРИМЕЧАНИЕ. Кавычки в операторе сопоставления в двойных скобках [[]] больше не нужны в Bash версии 3.2.

aliasav
источник
20
Вы не должны использовать char "в регулярном выражении? Потому что, когда я использую выражение, не работает
Давид Дрозд
Кроме того, экранирование обратной косой черты {и} также проблематично.
kbulgrien
32

Хороший способ проверить правильность строки: использовать команду date:

if date -d "${DATE}" >/dev/null 2>&1
then
  # do what you need to do with your date
else
  echo "${DATE} incorrect date" >&2
  exit 1
fi

из комментария: можно использовать форматирование

if [ "2017-01-14" == $(date -d "2017-01-14" '+%Y-%m-%d') ] 
Джанго Дженни
источник
9
Высоко оцените ваш ответ, поскольку он позволяет функции даты обрабатывать даты, а не подверженные ошибкам регулярные выражения '
Али
Это хорошо для проверки широких параметров даты, но если вам нужно проверить конкретный формат даты, можно ли это сделать? Например, если я это сделаю, date -d 2017-11-14eон вернет вт 14 ноября 05:00:00 UTC 2017, но это сломает мой сценарий.
Иосия
1
Вы можете использовать что-то вроде этого: if ["2017-01-14" == $ (date -d "2017-01-14" '+% Y-% m-% d')] Проверяет правильность даты и проверьте, совпадает ли результат с введенными вами данными. Кстати, будьте очень осторожны с локализованным форматом даты (например, месяц-день-год против дня-месяца-года)
Джанго Дженни
1
Может не работать, в зависимости от вашей локали. Американские даты в формате MM-DD-YYYY не будут работать нигде в мире, используя либо DD-MM-YYYY (Европа), либо YYYY-MM-DD (некоторые места в Азии)
Пол,
@ Пол, что может не сработать? Как написано в комментарии, можно использовать параметры форматирования ...
Бетлиста
4

Я бы использовал expr matchвместо =~:

expr match "$date" "[0-9]\{8\}" >/dev/null && echo yes

Это лучше, чем принятый в настоящее время ответ об использовании, =~поскольку =~он также будет соответствовать пустым строкам, что, по-моему, не должно. Предположим, badvarчто не определен, затем [[ "1234" =~ "$badvar" ]]; echo $?дает (неправильно) 0, а expr match "1234" "$badvar" >/dev/null ; echo $?дает правильный результат1 .

Мы должны использовать , >/dev/nullчтобы скрыть expr match«s выходное значение , которое число символов соответствует или 0 , если совпадений не найдено. Обратите внимание, что его выходное значение отличается от его состояния выхода . Статус выхода равен 0, если совпадение найдено, или 1 в противном случае.

Как правило, синтаксис для expr:

expr match "$string" "$lead"

Или:

expr "$string" : "$lead"

где $leadрегулярное выражение Значение exit statustrue (0), если leadсовпадает с ведущим фрагментом string(есть имя для этого?). Например, expr match "abcdefghi" "abc"выходит true, но expr match "abcdefghi" "bcd"выходит false. (Благодарю @Carlo Wood за указание на это.

Пенге Гэн
источник
7
=~не соответствует пустым строкам, в приведенном вами примере вы сопоставляете строку с пустым шаблоном . Синтаксис есть string =~ pattern, и пустой шаблон соответствует всему.
Bstpierre
2
Это не соответствует подстроке, он возвращает (стандартный вывод) число ведущих символов , которые соответствуют и статусу выхода истинно тогда и только тогда , по крайней мере один символ был подобран. Вот почему пустая строка (которая соответствует 0 символам) имеет статус выхода false. Например expr match "abcdefghi" "^" && echo Matched || echo No match- и expr match "abcdefghi" "bcd" && echo Matched || echo No match- оба возвращаются "0\nNo match". Куда как совпадение "a.*f"вернется "6\nMatched". Поэтому использование «^» в вашем примере также не нужно и уже подразумевается.
Карло Вуд
@bstpierre: дело не в том, можно ли рационализировать поведение =~сопоставления пустых строк. Это то, что это поведение может быть неожиданным и может привести к ошибкам. Я написал этот ответ специально, потому что я был сожжен этим.
Penghe Geng
@PengheGeng Неожиданное поведение? Если шаблон не имеет определения или ограничений, то он фактически соответствует чему-либо. Отсутствие паттерна подходит ко всему. Написание надежного кода - это ответ, а не оправдание плохого объяснения.
Энтони Ратледж
«Надежный код» @AnthonyRutledge требует наилучшего использования доступных инструментов для предотвращения случайных ошибок кодирования. В коде Shell, где пустую переменную можно легко и случайно ввести в любое время с помощью таких средств, как опечатка, я не думаю, что возможность сопоставления пустых переменных является надежной функцией. Видимо автор GNU exprсогласен со мной.
Penghe Geng
0

В тех случаях, когда использование регулярного выражения может быть полезным для определения правильности последовательности символов даты, его нельзя легко определить, является ли дата действительной. Следующие примеры передают регулярное выражение, но все они являются недопустимыми датами: 20180231, 20190229, 20190431

Поэтому, если вы хотите проверить datestrправильность формата строки даты (давайте назовем ее ), лучше всего проанализировать ее dateи попросить dateпреобразовать строку в правильный формат. Если обе строки идентичны, у вас есть правильный формат и действительная дата.

if [[ "$datestr" == $(date -d "$datestr" "+%Y%m%d" 2>/dev/null) ]]; then
     echo "Valid date"
else
     echo "Invalid date"
fi
kvantour
источник