Bir dizenin Bash betiğindeki normal ifadeyle eşleşip eşleşmediğini kontrol edin


208

Benim senaryom aldığı bağımsız değişkenlerden biri aşağıdaki biçimde bir tarihtir: yyyymmdd.

Giriş olarak geçerli bir tarih alıp almadığımı kontrol etmek istiyorum.

Bunu nasıl yapabilirim? Ben gibi bir regex kullanmaya çalışıyorum:[0-9]\{\8}


Formatın doğru olup olmadığını kontrol etmek kolaydır. Ama bash (yerleşik) ile, tarihin geçerli olup olmadığını kontrol edebilirsiniz sanmıyorum.
RedX

Yanıtlar:


322

Dizenin [[ ]]normal ifade eşleme işleciyle birlikte =~bir dizenin normal ifade modeliyle eşleşip eşleşmediğini kontrol etmek için test yapısını kullanabilirsiniz .

Özel durumunuz için şunları yazabilirsiniz:

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

Veya daha doğru bir test:

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

Yani bir regex tanımlayabilir olan Bash istediğiniz biçimiyle eşleşen. Bu şekilde şunları yapabilirsiniz:

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

burada &&test başarılı ||olursa sonraki komutlar yürütülür ve test başarısız olursa sonraki komutlar yürütülür.

Bunun, Aleks-Daniel Jakimenko'nun Kullanıcı giriş tarihi biçim doğrulamasındaki bash çözümüne dayandığını unutmayın .


Diğer mermilerde grep kullanabilirsiniz . Kabuğunuz POSIX uyumluysa,

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

In balık POSIX uyumlu değil, yapabileceğiniz

echo "$date" | grep -Eq "^regex\$"; and echo "matched"; or echo "did not match"

19
Bunun farkındayım, ama bash ile kimin sorduğunu ve ne kadar uzak olduklarını da hesaba katmayı seviyorum. Çok karmaşık koşullar sağlarsak, hiçbir şey öğrenmezler ve başka bir şüphe duyduklarında geri gelirler. Daha anlaşılır bir cevap vermeyi tercih ederim.
fedorqui 'SO

8
Heh. Öğrenmenin tek yolu birçok iyi kodu okumaktır. Anlaşılması kolay ancak kullanılması tavsiye edilmeyen yanlış kod verirseniz - bu öğretmenin kötü bir yoludur. Ayrıca bash öğrenmeye yeni başlayanlar için (muhtemelen başka bir dilin bazı bitlerini zaten biliyorlar) regex için bash sözdizimini bayraklı bir grepkomuttan daha kolay anlayacağından eminim -E.
Aleks-Daniel Jakimenko-A.

8
@ Aleks-DanielJakimenko Bu yazıyı tekrar gözden geçirdim ve şimdi bash regex kullanmanın en iyisi olduğunu kabul ediyorum. İyi yönde işaret ettiğiniz için teşekkürler, güncellenmiş cevap.
fedorqui 'SO' zarar vermeyi durdur '10

4
Örneğin, sh için OP sorusunun biraz ötesinde kullanılmasına izin veren,
oy verin

3
Aleks-DanielJakimenko grep kullanarak @ kullandığınız takdirde en iyi seçenek gibi görünüyor sh, fishya da diğer daha az donanımlı kabukları.
tomekwi

47

Bash sürüm 3'te '= ~' operatörünü kullanabilirsiniz:

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

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

NOT: Çift parantez içindeki [[]] eşleşen işleçteki alıntı artık Bash sürüm 3.2'den itibaren gerekli değildir


20
Char "düzenli ekspresion kullanmamalısınız? Çünkü expresion kullandığımda çalışmıyor
Dawid Drozd

Ayrıca, ters eğik çizgiden {ve} kaçması da benzer şekilde sorunludur.
kbulgrien

32

Bir dizenin doğru tarih olup olmadığını test etmenin iyi bir yolu komut tarihini kullanmaktır:

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

Yorumdan: biçimlendirmeyi kullanabilirsiniz

if [ "2017-01-14" == $(date -d "2017-01-14" '+%Y-%m-%d') ] 

9
Tarih işlevinin, hataya açık regex'lerle değil tarihlerle ilgilenmesini sağladığından yanıtınızı yüksek oranda derecelendirin
Ali

Bu, geniş tarih seçeneklerini kontrol etmek için iyidir, ancak belirli bir tarih biçimini doğrulamanız gerekiyorsa, bunu yapabilir mi? Örneğin, ben yaparsam date -d 2017-11-14e14 Kasım 05:00:00 UTC 2017 döndürür, ancak bu benim komutumu kıracak.
Josiah

1
Böyle bir şey kullanabilirsiniz: eğer ["2017-01-14" == $ (tarih -d "2017-01-14" '+% Y-% m-% d')] Tarihin doğru olup olmadığını test eder ve sonucun girdiğiniz verilerle aynı olup olmadığını kontrol edin. Bu arada, yerelleştirilmiş tarih biçimine çok dikkat edin (örneğin Ay-Gün-Yıl ile Gün-Ay-Yıl)
Django Janny

1
Bulunduğunuz yere bağlı olarak çalışmayabilir. AA-GG-YYYY kullanan Amerikan biçimli tarihler, DD-MM-YYYY (Avrupa) veya YYYY-AA-GG (Asya'nın bazı yerlerinde) kullanarak dünyanın başka hiçbir yerinde çalışmaz
Paul

@ Paul, ne işe yaramayabilir? Bir yorumda yazıldığı gibi, biçimlendirme seçeneklerini kullanabilirsiniz ...
Betlista

4

Bunun expr matchyerine kullanmak istiyorum =~:

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

Bu, şu anda kabul edilen kullanım cevabından daha iyidir, =~çünkü =~IMHO'nun yapmaması gereken boş dizelerle de eşleşecektir. Varsayalım badvartanımlanmamıştır, daha sonra [[ "1234" =~ "$badvar" ]]; echo $?(yanlış) verir 0iken, expr match "1234" "$badvar" >/dev/null ; echo $?doğru bir sonuç verir 1.

Biz kullanmak zorunda >/dev/nullgizlemek için expr match'ın çıkış değeri eşleşme bulursa karakter sayısı eşleşti veya 0,. Çıkış değerinin çıkış durumundan farklı olduğunu unutmayın . Bir eşleşme bulunursa çıkış durumu 0 veya aksi takdirde 1 olur.

Genellikle, söz dizimi expr:

expr match "$string" "$lead"

Veya:

expr "$string" : "$lead"

$leaddüzenli bir ifade nerede . (Bunun için bir isim var mı?) Öğesinin önde gelen dilimi ile eşleşirse, exit statustrue (0) olur . Örneğin çıkışlar , çıkışlar . (Bunu işaret ettiği için @Carlo Wood'a teşekkür ederiz.leadstringexpr match "abcdefghi" "abc"trueexpr match "abcdefghi" "bcd"false


7
=~boş dizelerle eşleşmiyorsa, bir dizeyi verdiğiniz örnekteki boş bir desenle eşleştiriyorsunuz . Sözdizimi string =~ patternve boş bir kalıp her şeyle eşleşir.
bstpierre

2
Bu bir alt uymuyor, bu (Stdout'a) döndürür sayısının lider eşleşen karakterler ve çıkış durumu en az 1 karakter eşleştirildi doğru iff olduğunu. Bu nedenle boş bir dizenin (0 karakterle eşleşen) çıkış durumu false değerine sahiptir. Örneğin expr match "abcdefghi" "^" && echo Matched || echo No match- ve expr match "abcdefghi" "bcd" && echo Matched || echo No match- ikisi de geri döner "0\nNo match". Eşleme olarak "a.*f"geri dönülecek "6\nMatched". Örneğinizde '^' kullanımı da gereksizdir ve zaten ima edilmiştir.
Carlo Wood

@bstpierre: Buradaki nokta, =~boş dizeleri eşleştirme davranışını rasyonelleştirip birleştiremeyeceği değildir . Bu davranış beklenmedik olabilir ve hatalara neden olabilir. Bu yanıtı özellikle yazdım çünkü yanmıştım.
Penghe Geng

@PengheGeng Beklenmedik davranış? Bir desenin tanımı veya kısıtlaması yoksa, aslında herhangi bir şeyle eşleşir. Bir paternin olmaması her şeyle eşleşir. Sağlam bir kod yazmak, zayıf bir açıklamayı haklı çıkarmak değil, cevaptır.
Anthony Rutledge

@AnthonyRutledge "sağlam kod", yanlışlıkla kodlama hatalarını önlemek için mevcut araçların en iyi şekilde kullanılmasını gerektirir. Boş bir değişkenin, yanlış yazım gibi herhangi bir zamanda kolayca ve yanlışlıkla girilebildiği Shell kodunda, boş değişkenlerin eşleştirilmesine izin vermenin sağlam bir özellik olduğunu düşünmüyorum. Görünüşe göre GNU'nun yazarı exprbenimle aynı fikirde.
Penghe Geng

0

Normal ifadenin kullanımının, bir tarihin karakter dizisinin doğru olup olmadığını belirlemeye yardımcı olabileceği durumlarda, tarihin geçerli olup olmadığını belirlemek için kolayca kullanılamaz. Aşağıdaki örnekler normal ifadeyi geçecek, ancak tümü geçersiz tarihlerdir: 20180231, 20190229, 20190431

Bu nedenle, tarih dizenizin (diyelim datestr) doğru biçimde olup olmadığını doğrulamak istiyorsanız , onu ayrıştırmak dateve datedizeyi doğru biçime dönüştürmek istemek en iyisidir . Her iki dize de aynıysa, geçerli bir biçiminiz ve geçerli bir tarihiniz vardır.

if [[ "$datestr" == $(date -d "$datestr" "+%Y%m%d" 2>/dev/null) ]]; then
     echo "Valid date"
else
     echo "Invalid date"
fi
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.