Periksa apakah string cocok dengan regex dalam skrip Bash

204

Salah satu argumen bahwa script saya menerima adalah tanggal dalam format berikut: yyyymmdd.

Saya ingin memeriksa apakah saya mendapatkan tanggal yang valid sebagai input.

Bagaimana saya bisa melakukan ini? Saya mencoba menggunakan regex seperti:[0-9]\{\8}

Peter Nijem
sumber
Memeriksa apakah formatnya benar mudah. Tetapi saya tidak berpikir bahwa Anda dapat, dalam bash (dengan built-in), memeriksa apakah tanggal tersebut valid.
RedX

Jawaban:

317

Anda dapat menggunakan konstruksi uji [[ ]],, bersama dengan operator pencocokan ekspresi reguler =~,, untuk memeriksa apakah string cocok dengan pola regex.

Untuk kasus spesifik Anda, Anda dapat menulis:

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

Atau lebih banyak tes yang akurat:

[[ $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

Artinya, Anda bisa mendefinisikan regex di Bash yang cocok dengan format yang Anda inginkan. Dengan cara ini Anda dapat melakukan:

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

di mana perintah setelah &&dieksekusi jika tes berhasil, dan perintah setelah ||dijalankan jika tes tidak berhasil.

Catatan ini didasarkan pada solusi oleh Aleks-Daniel Jakimenko di verifikasi format tanggal input Pengguna di bash .


Di shell lain Anda dapat menggunakan grep . Jika shell Anda kompatibel dengan POSIX, lakukan

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

Pada ikan , yang tidak sesuai dengan POSIX, Anda bisa melakukannya

echo "$date" | grep -Eq "^regex\$"; and echo "matched"; or echo "did not match"
fedorqui 'SO berhenti merugikan'
sumber
19
Saya menyadari hal itu, tetapi saya juga suka mempertimbangkan siapa yang bertanya dan seberapa jauh mereka dengan bash. Jika kami menyediakan kondisi yang sangat kompleks, mereka tidak akan belajar apa pun dan kembali lagi kapan saja mereka ragu. Saya lebih suka memberikan jawaban yang lebih bisa dimengerti sendiri.
fedorqui 'SO berhenti merugikan'
7
Heh. Ya, satu-satunya cara untuk belajar adalah membaca banyak kode yang bagus. Jika Anda memberikan kode palsu yang mudah dimengerti tetapi tidak disarankan untuk digunakan - itu cara yang buruk untuk mengajar. Juga saya cukup yakin bahwa bagi mereka yang baru mulai belajar bash (mungkin sudah mengetahui beberapa bit bahasa lain) akan memahami sintaks bash untuk regex lebih mudah daripada beberapa grepperintah dengan -Eflag.
Aleks-Daniel Jakimenko-A.
8
@ Aleks-DanielJakimenko Saya membaca posting ini lagi dan sekarang saya setuju yang terbaik adalah menggunakan bash regex. Terima kasih telah menunjukkan arah yang baik, jawaban yang diperbarui.
fedorqui 'SO stop harming'
4
Suara positif
3
@ Aleks-DanielJakimenko menggunakan grep tampaknya menjadi pilihan terbaik jika Anda menggunakan sh, fishatau kerang yang kurang lengkap lainnya.
tomekwi
47

Dalam bash versi 3 Anda dapat menggunakan operator '= ~':

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

Referensi: http://tldp.org/LDP/abs/html/bashver3.html#REGEXMATCHREF

CATATAN: Kutipan dalam operator yang cocok dalam kurung ganda, [[]], tidak lagi diperlukan pada Bash versi 3.2

aliasav
sumber
20
Anda tidak boleh menggunakan char "di expresion reguler? Karena ketika saya menggunakan expresion tidak bekerja
Dawid Drozd
Lebih jauh lagi, backslash yang lolos dari {dan} juga bermasalah.
kbulgrien
32

Cara yang baik untuk menguji apakah string adalah tanggal yang benar adalah dengan menggunakan tanggal perintah:

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

dari komentar: orang dapat menggunakan format

if [ "2017-01-14" == $(date -d "2017-01-14" '+%Y-%m-%d') ] 
Django Janny
sumber
9
Beri peringkat tinggi pada jawaban Anda karena memungkinkan fungsi tanggal menangani tanggal dan bukan regex yang rawan kesalahan '
Ali
Ini bagus untuk memeriksa opsi tanggal luas, tetapi jika Anda perlu memverifikasi format tanggal tertentu, dapatkah itu melakukannya? Misalnya jika saya date -d 2017-11-14emengembalikannya Selasa 14 November 05:00:00 UTC 2017, tapi itu akan merusak skrip saya.
Josiah
1
Anda dapat menggunakan sesuatu seperti itu: jika ["2017-01-14" == $ (date -d "2017-01-14" '+% Y-% m-% d')] Ini menguji apakah tanggalnya benar dan periksa apakah hasilnya sama dengan data yang Anda masukkan. Ngomong-ngomong, berhati-hatilah dengan format tanggal yang dilokalkan (Bulan-Hari-Tahun vs. Hari-Bulan-Tahun misalnya)
Django Janny
1
Mungkin tidak berfungsi, tergantung pada lokasi Anda. Tanggal yang diformat Amerika menggunakan MM-DD-YYYY tidak akan bekerja di tempat lain di dunia, baik menggunakan DD-MM-YYYY (Eropa) atau YYYY-MM-DD (beberapa tempat di Asia)
Paul
@ Paul, apa yang mungkin tidak berhasil? Seperti yang ditulis dalam komentar, seseorang dapat menggunakan opsi pemformatan ...
Betlista
4

Saya akan menggunakan expr matchsebagai gantinya =~:

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

Ini lebih baik daripada jawaban yang saat ini diterima menggunakan =~karena =~juga akan cocok dengan string kosong, yang seharusnya tidak IMHO. Misalkan badvartidak didefinisikan, lalu [[ "1234" =~ "$badvar" ]]; echo $?memberi (salah) 0, sambil expr match "1234" "$badvar" >/dev/null ; echo $?memberikan hasil yang benar 1.

Kita harus menggunakan >/dev/nulluntuk menyembunyikan expr match's nilai output , yang merupakan jumlah karakter cocok atau 0 jika tidak ada kecocokan ditemukan. Perhatikan nilai outputnya berbeda dari status keluarnya . Status keluar adalah 0 jika ada kecocokan yang ditemukan, atau 1 jika tidak.

Secara umum, sintaks untuk expradalah:

expr match "$string" "$lead"

Atau:

expr "$string" : "$lead"

di mana $leadekspresi reguler. Itu exit statusakan benar (0) jika leadcocok dengan irisan terkemuka string(Apakah ada nama untuk ini?). Misalnya expr match "abcdefghi" "abc"keluar true, tetapi expr match "abcdefghi" "bcd"keluar false. (Kredit ke @Carlo Wood karena menunjukkan ini.

Penghe Geng
sumber
7
=~tidak cocok dengan string kosong, Anda mencocokkan string dengan pola kosong pada contoh yang Anda berikan. Sintaksnya adalah string =~ pattern, dan pola kosong cocok dengan semuanya.
bstpierre
2
Ini tidak cocok dengan substring, ia mengembalikan (ke stdout) jumlah karakter utama yang cocok dan status keluar benar jika jika setidaknya 1 karakter cocok. Inilah sebabnya mengapa string kosong (yang cocok dengan 0 karakter) memiliki status keluar salah. Misalnya expr match "abcdefghi" "^" && echo Matched || echo No match- dan expr match "abcdefghi" "bcd" && echo Matched || echo No match- keduanya kembali "0\nNo match". Dimana pencocokan "a.*f"akan kembali "6\nMatched". Oleh karena itu, penggunaan '^' dalam contoh Anda juga tidak perlu dan sudah tersirat.
Carlo Wood
@ bstpierre: intinya di sini bukanlah apakah seseorang dapat merasionalisasi perilaku =~mencocokkan string kosong. Ini karena perilaku ini mungkin tidak terduga dan dapat menyebabkan kesalahan. Saya menulis jawaban ini secara khusus karena saya dibakar olehnya.
Penghe Geng
@PengheGeng Perilaku tak terduga? Jika suatu pola tidak memiliki definisi atau kendala, maka sebenarnya cocok dengan apa pun. Tidak adanya suatu pola cocok untuk semuanya. Menulis kode yang kuat adalah jawabannya, bukan membenarkan penjelasan yang buruk.
Anthony Rutledge
@AnthonyRutledge "kode kuat" menuntut penggunaan terbaik alat yang tersedia untuk mencegah kesalahan pengkodean yang tidak disengaja. Dalam kode Shell di mana variabel kosong dapat dengan mudah dan tidak sengaja diperkenalkan kapan saja dengan cara seperti salah mengeja, saya tidak berpikir mengizinkan variabel kosong untuk dicocokkan adalah fitur yang kuat. Rupanya penulis GNU exprsetuju dengan saya.
Penghe Geng
0

Jika penggunaan regex dapat membantu untuk menentukan apakah urutan karakter suatu tanggal benar, itu tidak dapat digunakan dengan mudah untuk menentukan apakah tanggal tersebut valid. Contoh berikut akan melewati ekspresi reguler, tetapi semua tanggal tidak valid: 20180231, 20190229, 20190431

Jadi jika Anda ingin memvalidasi jika string tanggal Anda (sebut saja datestr) dalam format yang benar, yang terbaik untuk menguraikannya datedan meminta dateuntuk mengubah string ke format yang benar. Jika kedua string identik, Anda memiliki format dan tanggal yang valid.

if [[ "$datestr" == $(date -d "$datestr" "+%Y%m%d" 2>/dev/null) ]]; then
     echo "Valid date"
else
     echo "Invalid date"
fi
Kvantour
sumber