Regular Expressions (Regex) คืออะไร?
Regular expressions หรือ regex ทำงานโดยการกำหนด pattern ที่ใช้ค้นหาตัวอักษรหรือคำเฉพาะภายใน string
เมื่อกำหนด pattern แล้ว คุณสามารถแก้ไข ลบตัวอักษร/คำ แทนที่ สกัดข้อมูลที่เกี่ยวข้องจากไฟล์หรือ string ที่มี pattern นั้นๆ ได้
ทำไมควรเรียน Regex?
Regex ช่วยประมวลผลข้อความแบบประหยัดเวลา และเพิ่มความสนุกในกระบวนการ ทำให้การค้นหาข้อมูลง่ายขึ้น และสามารถ batch edit/replace/delete หรือประมวลผลได้ทีละหลายรายการ
ตัวอย่างการใช้งานจริง: batch file renaming, parsing logs, validating forms, mass edits ใน codebase, recursive search
Regex Basics
Regular expression คือลำดับตัวอักษรที่ match pattern นอกจาก literal characters (เช่น 'abc') ยังมี meta characters (*,+,? ฯลฯ) ที่มีวัตถุประสงค์พิเศษ และ features เช่น character classes ที่ช่วยลดความซับซ้อน
ก่อนเขียน regex ต้องเข้าใจ edge cases ทั้งหมดของ pattern ที่ต้องการ เช่น ต้องการ match 'Hello World' - บรรทัดต้องเริ่มด้วย 'Hello' หรือไม่? เว้นวรรค 1 ช่องหรือมากกว่า? มีตัวอักษรหลัง 'World' ได้หรือไม่? Case sensitive หรือไม่?
Exact Match
รูปแบบพื้นฐานที่สุด: match ลำดับตัวอักษรคล้าย Ctrl-F ใน text editor
Character Set
Character sets ให้ match ตัวอักษรตัวใดตัวหนึ่งจากกลุ่ม โดยล้อมด้วย square brackets []
ตัวอย่าง: t[ah]i match "tai" และ "thi" - 't' และ 'i' คงที่ ระหว่างกลางเป็น 'a' หรือ 'h'
Match Ranges ใน Regex
ใช้ - เป็น range operator สำหรับกลุ่มตัวอักษรที่เรียงลำดับ:
| Range | Matches |
|---|---|
[A-Z] | ตัวพิมพ์ใหญ่ A-Z |
[a-z] | ตัวพิมพ์เล็ก a-z |
[0-9] | ตัวเลข 0-9 |
ระบุ partial ranges ได้ เช่น [b-e] match 'bcde' หรือ [3-6] match '3456'
รวมหลาย ranges และตัวอักษรอื่นๆ ได้ เช่น [3-6u-w;] match '3456uvw;'
Match ตัวอักษรที่ไม่อยู่ใน Set
ขึ้นต้น set ด้วย ^ เพื่อ inverse operation: [^A-Z0-9] match ทุกอย่างยกเว้นตัวพิมพ์ใหญ่และตัวเลข
Character Classes
Class สำหรับกลุ่มตัวอักษรที่ใช้บ่อย ทำให้ expression สะอาดขึ้น:
| Class | Matches | Equivalent |
|---|---|---|
. | อะไรก็ได้ยกเว้น newline | [^\n\r] |
\w | word character | [a-zA-Z0-9_] |
\W | non-word character | [^\w] |
\d | digits | [0-9] |
\D | non-digits | [^\d] |
\s | space, tab, newlines | [ \t\r\n\f] |
\S | non-whitespace | [^\s] |
Class . ช่วยไม่ต้องเขียนทุกตัวอักษรใน set เช่น t.. match เริ่มด้วย t ตามด้วยตัวอักษร 2 ตัว
Quantifiers
Quantifiers ใช้สำหรับ repetition แทนการเขียนซ้ำ:
| Quantifier | Matches |
|---|---|
* | 0 หรือมากกว่า |
? | 0 หรือ 1 |
+ | 1 หรือมากกว่า |
{n} | ตรง n ครั้ง |
{n,} | n ครั้งขึ้นไป |
{n,m} | n ถึง m ครั้ง |
ตัวอย่าง: can\s+write match 'can' + 1+ whitespace + 'write' (ไม่ match 'canwrite' เพราะ \s+ ต้องมีอย่างน้อย 1 whitespace)
Capture Groups
Capture groups คือ sub-expressions ในวงเล็บ () สร้างได้ไม่จำกัด และ nested ได้
(The ){2} match 'The ' 2 ครั้ง แต่ The {2} จะ match 'The' + 2 spaces (quantifier ใช้กับ space ไม่ใช่ group)
Logical OR ใน Regex
ใช้ | match หลาย pattern: This is (good|bad|sweet) match 'This is ' + 'good' หรือ 'bad' หรือ 'sweet'
ความสำคัญของ capture group: This is good|bad|sweet (ไม่มี group) จะ match 'This is good' หรือ 'bad' หรือ 'sweet' แยกกัน
Reference Capture Groups
อ้างอิง nth captured group ด้วย \n (ใน replacement บาง tool ใช้ $n)
(This) is \1 power capture 'This' อ้างอิงด้วย \1 match 'This is This power'
Named Capture Groups
ตั้งชื่อ group: (?<name>pattern) backreference: \k<name> replacement: $<name> (syntax เปลี่ยนได้ตามภาษา)
ใช้ Regex กับ Command Line Tools
Recursive Regex Search ด้วย grep
grep ค้นหา pattern ในไฟล์ หรือ recursive search
Windows: winget install -e --id GnuWin32.Grep
ตัวอย่าง CTF challenge: zip file หลายระดับ ไฟล์เยอะ Flag format: coderush{flag is here} ค้นหา pattern coderush{.*}
unzip ripG.zip && cd ripG
grep --color -R "coderush{.*}"
-R enable recursive search
Substitution ด้วย sed
sed ใช้ insertion, deletion, substitution บน text files ด้วย regex
sed 's/pattern/replacement/g' filename
echo "${text}" | sed 's/pattern/replacement/g'
g = replace all occurrences -n suppress default print, ใช้ p แทน g เพื่อ print เฉพาะบรรทัดที่ถูกแทนที่
ตัวอย่าง: แทนที่ Henlo number ด้วย Hello number เฉพาะบรรทัดที่มี "GREP"
grep "GREP" texts.txt | sed -En 's/Henlo ([0-9]+)/Hello \1/p'
# หรือ
sed -En '/GREP/ s/Henlo ([0-9]+)/Hello \1/p' texts.txt
Advanced Regex: Lookarounds
Lookaheads และ Lookbehinds (รวมเรียกว่า lookarounds) ตรวจสอบการมีอยู่ของ pattern โดยไม่รวมใน match - เรียกว่า zero-width assertions
Lookbehinds
Match 'linux' ก็ต่อเมื่อ 'GNU' อยู่ก่อนหน้า และ match แค่ 'linux' เท่านั้น
Positive lookbehind: (?<=GNU.*)linux - match 'linux' ก็ต่อเมื่อ 'GNU.*' อยู่ก่อนหน้า
Negative lookbehind: (?<!GNU.*)linux - match 'linux' ก็ต่อเมื่อ 'GNU.*' ไม่อยู่ก่อนหน้า
Lookaheads
Match 'Hello' ก็ต่อเมื่อ 'World' อยู่หลัง และ match แค่ 'Hello'
Positive lookahead: Hello(?=.*World)
Negative lookahead: Hello(?!.*World) - match 'Hello' ก็ต่อเมื่อ 'World' ไม่อยู่หลัง
| Operation | RegEx |
|---|---|
| Positive lookahead | (?=C)X |
| Negative lookahead | (?!C)X |
| Positive lookbehind | (?<=C)X |
| Negative lookbehind | (?<!C)X |
Practical Examples
Logs Parsing
Log lines:
[1/10000] Train loss: 11.30368, Valid loss: 8.95446, Elapsed_time: 7.58941
[500/10000] Train loss: 0.96180, Valid loss: 0.20098, Elapsed_time: 82.48651
Extract training/validation loss: Train loss: (\d+\.\d+) และ Valid loss: (\d+\.\d+)
Python example:
import re
f = open("log_train.txt", "r").read()
train_loss = re.findall(r'Train loss: (\d+\.\d+)', f)
valid_loss = re.findall(r'Valid loss: (\d+\.\d+)', f)
train_loss = [float(i) for i in train_loss]
valid_loss = [float(i) for i in valid_loss]
Bulk File Renaming
ไฟล์ชื่อ Random_Episode_1.mp4 ต้องการ rename เป็น 1.mp4, 2.mp4...
Regex: .*Episode_([0-9]+)\.mp4 capture หมายเลข replacement: \1.mp4
for i in *.mp4; do
newname=$(echo $i | sed -En 's/.*Episode_([0-9]+)\.mp4/\1.mp4/p')
mv $i $newname
done; ls
Email Validation
Simple regex: ^[^@ ]+@[^@.]+\.\w+$ match format A@B.C
Password Constraints
ใช้ positive lookaheads สำหรับ constraints:
- Min 8, Max 16 chars:
.{8,16} - อย่างน้อย 1 lowercase:
(?=.*[a-z]) - อย่างน้อย 1 uppercase:
(?=.*[A-Z]) - อย่างน้อย 1 digit:
(?=.*\d)
Regex รวม: ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,16}$
ต้องการอย่างน้อย 5 uppercase: ^(?=.*[a-z])(?=(.*[A-Z]){5,})(?=.*\d).{8,16}$
Reject ถ้ามี 'pass' หรือ '1234': ^(?!.*(pass|1234)).*$ (negative lookahead)
Final Words
เข้าใจ core concepts และรู้ว่า regex ทำได้อะไรสำคัญกว่าจำ syntax ลืมก็ google หรือ cheatsheet ได้ ฝึกมากขึ้น จะเขียน regex ซับซ้อนได้ offline
ไม่บังคับใช้ regex ถ้า string.find() เพียงพอ แต่ถ้าอยู่ใน terminal บ่อย regex จะช่วยมาก