ตรวจสอบว่าสตริงตรงกับ regex ใน Bash script หรือไม่


204

หนึ่งในข้อโต้แย้งที่สคริปต์ของฉันได้รับคือวันที่ในรูปแบบต่อไปนี้: yyyymmddหนึ่งของการขัดแย้งที่สคริปต์ของฉันได้รับคือวันในรูปแบบต่อไปนี้:

ฉันต้องการตรวจสอบว่าฉันได้รับวันที่ถูกต้องเป็นอินพุตหรือไม่

ฉันจะทำสิ่งนี้ได้อย่างไร ฉันพยายามใช้ regex เช่น:[0-9]\{\8}


ตรวจสอบว่ารูปแบบที่ถูกต้องเป็นเรื่องง่าย แต่ฉันไม่คิดว่าคุณสามารถในการทุบตี (กับตัว -ins) ตรวจสอบว่าวันที่ถูกต้อง
RedX

คำตอบ:


317

คุณสามารถใช้โครงสร้างการทดสอบ[[ ]]พร้อมกับตัวดำเนินการจับคู่นิพจน์ทั่วไป=~เพื่อตรวจสอบว่าสตริงตรงกับรูปแบบ regex หรือไม่

สำหรับกรณีเฉพาะของคุณคุณสามารถเขียน:

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

นั่นคือคุณสามารถกำหนด regex ในBash ที่ตรงกับรูปแบบที่คุณต้องการ วิธีนี้คุณสามารถทำได้:

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

โดยที่คำสั่งหลังจากนั้น&&จะถูกดำเนินการหากการทดสอบประสบความสำเร็จและคำสั่งหลังจากนั้น||จะถูกดำเนินการหากการทดสอบไม่ประสบความสำเร็จ

หมายเหตุนี้จะขึ้นอยู่กับวิธีการแก้ปัญหาโดย Aleks แดเนียล Jakimenko ในการป้อนข้อมูลของผู้ใช้ในการตรวจสอบรูปแบบวันที่ในทุบตี


ในเปลือกหอยอื่น ๆ ที่คุณสามารถใช้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
ฉันตระหนักถึงสิ่งนั้น แต่ฉันก็ชอบที่จะพิจารณาว่าใครเป็นคนถามและพวกเขาจะต้องทุบตีนานแค่ไหน หากเราให้เงื่อนไขที่ซับซ้อนมากพวกเขาจะไม่เรียนรู้อะไรเลยและกลับมาเมื่อใดก็ตามที่พวกเขามีข้อสงสัยอีก ฉันชอบให้คำตอบที่เข้าใจตัวเองได้มากกว่า
fedorqui 'ดังนั้นหยุดการทำร้าย'

7
หึ วิธีเดียวที่จะเรียนรู้ได้คือการอ่านรหัสที่ดีมากมาย หากคุณให้รหัสเท็จที่เข้าใจง่าย แต่ไม่แนะนำให้ใช้นั่นเป็นวิธีที่ไม่ดีในการสอน นอกจากนี้ฉันค่อนข้างแน่ใจว่าสำหรับผู้ที่เพิ่งเริ่มเรียนรู้การทุบตี (อาจรู้บางบิตของภาษาอื่น) จะเข้าใจไวยากรณ์ bash สำหรับ regex ได้ง่ายกว่าgrepคำสั่งบางอย่างที่มี-Eธง
Aleks-Daniel Jakimenko-A

8
@ Aleks-DanielJakimenko ฉันทำโพสต์นี้อีกครั้งและตอนนี้ฉันเห็นด้วยที่ดีที่สุดคือใช้ bash regex ขอบคุณที่ชี้ไปในทิศทางที่ดีคำตอบที่อัปเดตแล้ว
fedorqui 'ดังนั้นหยุดทำอันตราย'

4
Upvote ที่ช่วยให้การใช้งานเกินกว่าน้อยกว่าคำถาม OP สำหรับการดวลจุดโทษเช่น ..
Dereckson

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


20
คุณไม่ควรใช้ถ่าน "ที่ expresion ปกติเพราะเมื่อใช้ i expresion ไม่ทำงานหรือไม่
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 จัดการกับวันที่และไม่ใช่ regex ที่มีข้อผิดพลาด '
Ali

สิ่งนี้เป็นสิ่งที่ดีสำหรับการตรวจสอบตัวเลือกวันที่แบบกว้าง ๆ แต่ถ้าคุณต้องการตรวจสอบรูปแบบวันที่ที่เฉพาะเจาะจงมันสามารถทำได้หรือไม่? ตัวอย่างเช่นถ้าฉันdate -d 2017-11-14eส่งคืนอ. พ.ย. 14 05:00:00 UTC 2017 แต่นั่นจะทำให้สคริปต์ของฉันพัง
โยสิยาห์

1
คุณสามารถใช้บางสิ่งเช่นนั้น: ถ้า ["2017-01-14" == $ (date -d "2017-01-14" '+% Y-% m-% d')] จะทดสอบว่าวันที่นั้นถูกต้องหรือไม่ และตรวจสอบว่าผลลัพธ์นั้นตรงกับข้อมูลที่คุณป้อนหรือไม่ อย่างไรก็ตามโปรดระมัดระวังด้วยรูปแบบวันที่แปลเป็นภาษาท้องถิ่น (เช่นเดือนวันปีและวันเดือนปี)
Django Janny

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ของมูลค่าการส่งออกซึ่งเป็นจำนวนตัวอักษรที่ตรงกันหรือ 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"ดังนั้นการใช้ '^' ในตัวอย่างของคุณจึงไม่จำเป็นและโดยนัยแล้ว
Carlo Wood

@bstpierre: จุดที่นี่ไม่ใช่ว่าจะสามารถหาเหตุผลเข้าข้างตนเองของพฤติกรรมของการ=~จับคู่สตริงว่าง พฤติกรรมนี้อาจไม่คาดคิดและอาจทำให้เกิดข้อผิดพลาด ฉันเขียนคำตอบนี้โดยเฉพาะเพราะฉันถูกไฟไหม้
Penghe Geng

@PengheGeng พฤติกรรมที่ไม่คาดคิด? หากรูปแบบไม่มีคำนิยามหรือข้อ จำกัด แสดงว่ารูปแบบนั้นตรงกับสิ่งใด การขาดลวดลายนั้นเป็นการจับคู่กับทุกสิ่ง การเขียนโค้ดที่มีประสิทธิภาพคือคำตอบไม่ใช่การอธิบายเหตุผลที่ไม่ดี
Anthony Rutledge

@AnthonyRutledge "รหัสที่มีประสิทธิภาพ" ต้องใช้เครื่องมือที่มีอยู่อย่างดีที่สุดเพื่อป้องกันข้อผิดพลาดในการเขียนโค้ดโดยไม่ตั้งใจ ในรหัสเชลล์ที่สามารถนำตัวแปรว่างมาใช้ได้อย่างง่ายดายและบังเอิญได้ทุกเวลาโดยวิธีการสะกดคำฉันไม่คิดว่าการอนุญาตให้จับคู่ตัวแปรว่างเป็นคุณลักษณะที่มีประสิทธิภาพ เห็นได้ชัดว่าผู้เขียน GNU exprเห็นด้วยกับฉัน
Penghe Geng

0

ในกรณีที่การใช้ regex มีประโยชน์ในการพิจารณาว่าลำดับอักขระของวันที่นั้นถูกต้องหรือไม่นั้นไม่สามารถใช้งานได้ง่ายเพื่อกำหนดว่าวันที่นั้นถูกต้องหรือไม่ ตัวอย่างต่อไปนี้จะผ่านนิพจน์ทั่วไป แต่เป็นวันที่ไม่ถูกต้องทั้งหมด: 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.