Перевірте, чи збігається рядок з регулярним виразом у сценарії Bash


204

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

Я хочу перевірити, чи отримую я справжню дату як введення.

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


Перевірити правильність формату дуже просто. Але я не думаю, що ви можете в баші (із вбудованими модулями) перевірити, чи дата є дійсною.
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"

де команди після &&виконуються, якщо тест успішний, і команди після ||виконуються, якщо тест невдалий.

Зауважте, що це ґрунтується на рішенні Алекса-Даніеля Якименка у форматі перевірки формату дати введення користувачем у bash .


В інших оболонках можна використовувати греп . Якщо ваша оболонка сумісна з 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
Я це знаю, але мені також подобається враховувати, хто питає, і наскільки далеко вони з басом. Якщо ми забезпечимо дуже складні умови, вони нічого не дізнаються і просто повернуться, коли виникнуть інші сумніви. Я вважаю за краще давати більш зрозумілу відповідь.
fedorqui 'ТАК перестаньте шкодити'

7
Хе. Ну, єдиний спосіб навчитися - читати багато хорошого коду. Якщо ви даєте помилковий код, який легко зрозуміти, але його не рекомендується використовувати - це поганий спосіб навчання. Також я впевнений, що для тих, хто тільки почав вивчати баш (можливо, вже знаючи деякі біти іншої мови) зрозуміти синтаксис bash для регулярного виразів легше, ніж якусь grepкоманду з -Eпрапором.
Алекс-Даніель Якименко-А.

8
@ Aleks-DanielJakimenko Я знову пройшов цю посаду, і тепер я згоден, що найкраще використовувати bash regex. Дякуємо, що вказали в хорошому напрямку, оновлена ​​відповідь.
fedorqui 'ТАК перестаньте шкодити'

4
Upvote, що дозволяє використовувати його трохи більше, ніж питання ОП, наприклад, ш.
Dereckson

3
@ Aleks-DanielJakimenko з допомогою Grep , здається, кращий варіант , якщо ви використовуєте sh, fishабо інші менш обладнані раковини.
tomekwi

47

У версії 3 bash ви можете використовувати оператор '= ~':

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.


20
Ви не повинні використовувати char "у звичайній експресії? Тому що коли я використовую вираз, не працює
Dawid Drozd

Крім того, схоже проблематичне відхилення від косого кута {і}.
kbulgrien

32

Хороший спосіб перевірити, чи є рядок правильною датою, це використовувати дату команди:

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
Ви можете використовувати щось подібне: якщо ["2017-01-14" == $ (дата -d "2017-01-14" '+% Y-% m-% d')] Він перевіряє, чи дата правильна і перевірте, чи результат збігається з вашими введеними даними. До речі, будьте дуже обережні з локалізованим форматом дати (наприклад, місяць-день-рік проти дня-місяць-рік)
Джанго Джанні

1
Можливо, це не працює, залежно від вашої мови. Дати в американському форматі, що використовують MM-DD-YYYY, не працюватимуть ніде в світі, використовуючи або DD-MM-YYYY (Європа), або YYYY-MM-DD (деякі місця в Азії)
Павло

@Paul, що може не працювати? Як написано в коментарі, можна використовувати параметри форматування ...
Betlista

4

Я б використав expr matchзамість =~:

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

Це краще, ніж прийнятий на даний момент відповідь використання, =~оскільки =~він також відповідатиме порожнім рядкам, чого IMHO не повинен. Припустимо 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 statusбуде правдою (0), якщо leadвідповідатиме провідному фрагменту string(Чи є для цього ім’я?). Наприклад, expr match "abcdefghi" "abc"виходи true, але expr match "abcdefghi" "bcd"виходи false. (Подяка @Carlo Wood за те, що він це вказав.


7
=~не відповідає порожнім рядкам, ви порівнюєте рядок із порожнім шаблоном у наведеному прикладі. Синтаксис є string =~ pattern, а порожній шаблон відповідає всім.
bstpierre

2
Це не відповідає підрядку, вона повертає (до stdout) кількість провідних символів, які відповідали, і статус виходу є істинним, якщо принаймні 1 символ збігався. Ось чому порожній рядок (що відповідає 0 символам) має статус виходу помилковим. Наприклад 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, де порожню змінну можна легко та випадково ввести будь-коли такими засобами, як неправильне написання, я не думаю, що можливість порівняння порожніх змінних є надійною функцією. Мабуть, автор ГНУ 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.