সরাসরি পর্বে যান
পর্ব ৩০ · ৫৯-এর মধ্যে · মডিউল ৪

regular expression — খোঁজা, যাচাই আর বদলানো

Regular Expressions in JavaScript — Patterns, Flags, Groups, Unicode & Bangla Text, and Avoiding ReDoS

This Bangla lesson teaches regular expressions as a small pattern language: literal versus RegExp construction and escaping, character classes, anchors and quantifiers, greedy versus lazy matching and backtracking, groups, backreferences and lookaround, the test, exec, matchAll and replace methods with the lastIndex trap, Unicode property escapes for Bangla text, real format validation and avoiding ReDoS.

এই পর্বে যা শিখবেন

  • regex কেন একটি আলাদা ছোট ভাষা, কখন এটি সেরা টুল আর কখন একেবারেই নয়
  • /…/flags বনাম new RegExp(), escaping, ব্যবহারকারীর লেখা থেকে নিরাপদে regex বানানো, আর আটটি flag
  • character class, anchor, quantifier, greedy বনাম lazy, group, backreference আর lookaround — engine ধাপে ধাপে কী করে
  • test, exec, match, matchAll, replace, split — আর lastIndex-এর ফাঁদ
  • বাংলা লেখায় regex, বাংলাদেশি ফোন-তারিখ-NID-এর format যাচাই, ReDoS থেকে বাঁচা আর পড়ার মতো regex লেখা

১ · regular expression কী — লেখার “আকৃতি” বর্ণনার ছোট একটি ভাষা

একটি ফর্মে মোবাইল নম্বর নেওয়া হচ্ছে। নম্বরটি ঠিক আছে কি না যাচাই করতে চান। নিয়মটা মুখে বললে সহজ: “শুরুতে 0 আর 1, তারপর 3 থেকে 9-এর একটি অঙ্ক, তারপর ঠিক আটটি অঙ্ক — ব্যস, এর বেশি কিছু নয়।” কিন্তু সাধারণ JavaScript-এ লিখতে গেলে লাগে দৈর্ঘ্য যাচাই, একটি লুপ, প্রতিটি অক্ষর অঙ্ক কি না দেখা, তৃতীয় অক্ষরের আলাদা শর্ত — দশ-বারো লাইন, আর প্রতিটি লাইনে ভুলের সুযোগ।

Regular expression (সংক্ষেপে regex বা regexp) এই মুখের কথাটিকেই সরাসরি লেখার একটি ছোট ভাষা। উপরের নিয়মটি regex-এ:

/^01[3-9]\d{8}$/
 │ │ │    │   └─ $   : লেখা এখানেই শেষ
 │ │ │    └───── \d{8}: ঠিক ৮টি অঙ্ক
 │ │ └────────── [3-9]: 3 থেকে 9-এর যেকোনো একটি
 │ └──────────── 01   : হুবহু 0, তারপর 1
 └────────────── ^    : লেখার একেবারে শুরু
মূল ধারণা

Regex কোনো নির্দিষ্ট লেখা নয়, বরং লেখার একটি আকৃতির বর্ণনা — “কেমন দেখতে লেখা চাই”। একটি regex engine সেই বর্ণনা নিয়ে আসল লেখার ওপর দিয়ে হাঁটে আর বলে দেয়, কোথায় কোথায় আকৃতিটি মেলে। এই এক ক্ষমতা থেকেই চারটি কাজ আসে: যাচাই (মেলে কি না), খোঁজা (কোথায় মেলে), টুকরো তোলা (মিলের ভেতরের অংশ) আর বদলানো (মিলের জায়গায় অন্য লেখা)।

পোস্ট অফিসের বাছাইকর্মীর কথা ভাবুন। তিনি প্রতিটি খাম পড়েন না; শুধু দেখেন ঠিকানার শেষে চার অঙ্কের পোস্ট কোড আছে কি না, আর প্রথম অঙ্কটি কত — 1 হলে ঢাকার বাক্সে, 9 হলে খুলনার বাক্সে। তাঁর মাথায় একটি “আকৃতি” আছে, আসল ঠিকানা নয়। Regex ঠিক এমন একটি আকৃতি, যা আপনি মেশিনকে লিখে দেন — আর মেশিন সেকেন্ডে লক্ষ লাইন বাছাই করতে পারে।

কখন regex, কখন নয়

Regex শক্তিশালী, কিন্তু এর দুটি দাম আছে: পড়তে কঠিন (এক লাইনে অনেক চিহ্ন), আর খারাপভাবে লিখলে ধীর — এমনকি এত ধীর যে সার্ভার আটকে যায় (অংশ ৮)। তাই নিয়ম হলো: নকশা ছোট রাখুন, প্রতিটি নকশা পরীক্ষার ডেটা দিয়ে যাচাই করুন, আর সহজ string method দিয়ে কাজ হলে সেটাই ব্যবহার করুন।

কাজসেরা টুলকেন
লেখায় “bKash” আছে কি নাincludes("bKash")হুবহু লেখা খুঁজতে regex লাগে না (পর্ব ০৯)
ফোন নম্বর, পোস্ট কোড, তারিখের formatregexআকৃতি বর্ণনাই এখানে কাজ
SMS থেকে সব TrxID বা অর্ডার নম্বর তোলাregex + matchAllঅজানা জায়গায় একই আকৃতি বারবার
HTML পাতা থেকে লিংক বের করাDOM / DOMParserHTML-এর নেস্টেড গঠন regex দিয়ে নির্ভরযোগ্যভাবে পড়া যায় না (পর্ব ৩৭)
JSON বা CSV পড়াJSON.parse, CSV লাইব্রেরিউদ্ধৃতিচিহ্ন, escape, নেস্টিং — parser-এর কাজ (পর্ব ১৯)
ইমেইল ঠিকানা সত্যিই কার্যকর কি নাconfirmation ইমেইল পাঠানোformat ঠিক হলেও ঠিকানাটি অস্তিত্বহীন হতে পারে

২ · regex বানানোর দুই উপায় — /…/flags, new RegExp আর escaping

JavaScript-এ regex একটি object — RegExp class-এর instance. বানানোর দুই উপায়:

// ১) literal — নকশা আগে থেকেই জানা থাকলে (সবচেয়ে প্রচলিত)
const phone = /^01[3-9]\d{8}$/;
const word  = /dhaka/gi;          // শেষে flag: g আর i

// ২) constructor — নকশা চলার সময় তৈরি হলে
const city  = "Dhaka";
const re    = new RegExp(city, "gi");       // /Dhaka/gi
const digits = new RegExp("\\d+");          // string-এ backslash দুবার!
const same   = new RegExp(String.raw`\d+`); // অথবা String.raw

এই কোডটি দেখানোর জন্য। Regex literal /…/ script লোড হওয়ার সময়ই পরীক্ষা হয় — ভুল নকশা হলে সাথে সাথে SyntaxError. Constructor-এ নকশা যায় একটি সাধারণ string হিসেবে, তাই আগে string-এর escape নিয়ম খাটে: "\d" লিখলে JavaScript \d-কে অচেনা escape ভেবে শুধু d রাখে, আর regex হয়ে যায় /d+/ — “এক বা একাধিক d অক্ষর”! আমরা Node.js-এ যাচাই করেছি: new RegExp("\d+").source দেয় d+. তাই string-এ লিখুন "\\d+", অথবা পর্ব ০৯-এ দেখা String.raw ব্যবহার করুন — সেখানে backslash যেমন লেখেন তেমনই থাকে।

বিশেষ অক্ষর আর escaping

নকশার কিছু অক্ষরের বিশেষ মানে আছে — এদের বলে metacharacter: . * + ? ^ $ { } ( ) [ ] | \ /. এদের হুবহু অক্ষর হিসেবে খুঁজতে আগে backslash দিন: দাম “৳1.50” খুঁজতে /1\.50/; শুধু /1.50/ লিখলে . মানে “যেকোনো একটি অক্ষর”, তাই “1x50”-ও মিলে যাবে।

ব্যবহারকারীর লেখা দিয়ে regex — আগে escape

একটি সার্চ বক্সে ব্যবহারকারী লিখলেন C++ (basic), আর আপনি সেটি দিয়ে সরাসরি new RegExp(input) বানালেন। ফল: SyntaxError: Invalid regular expression: /C++ (basic)/: Nothing to repeat (V8-এর বার্তা) — কারণ + আর ( ) নকশার চিহ্ন। আরও খারাপ: কেউ ইচ্ছা করে (a+)+$-এর মতো লেখা দিলে আপনার নিজের সার্ভার ধীর হয়ে যেতে পারে (অংশ ৮)। নিয়ম: বাইরের লেখা regex-এ বসানোর আগে সব বিশেষ অক্ষর escape করুন, যাতে সেগুলো হুবহু অক্ষর হিসেবে খোঁজা হয়।

// সব জায়গায় চলে এমন ছোট escape function
const escapeRegex = s => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
escapeRegex("C++ (basic)");   // "C\+\+ \(basic\)"

// ES2025-এর built-in (নতুন ব্রাউজারে)
RegExp.escape("C++ (basic)");  // প্রতিটি বিশেষ অক্ষর নিরাপদে escape করে

প্রথম function-এর regex বলে “এই বিশেষ অক্ষরগুলোর যেকোনো একটি”, আর বদলি "\\$&" মানে “একটি backslash, তারপর যা মিলেছে” ($& = পুরো মিল, অংশ ৬)। RegExp.escape ES2025-এ যুক্ত হয়েছে; Chrome ও Edge-এ ১৩৬ সংস্করণ (২০২৫) থেকে আছে, আর আমাদের যাচাই করা Node.js 24-এ আছে, কিন্তু Node.js 20-এ নেই। এটি শুরুর অক্ষর আর কিছু চিহ্নকে \x31-এর মতো hex escape-এ লেখে, তাই ফলটি দেখতে অদ্ভুত লাগলেও নিরাপদ। পুরোনো ব্রাউজারও সমর্থন করতে হলে নিজের function-টিই রাখুন।

আটটি flag — পুরো নকশার আচরণ বদলানোর সুইচ

Flag লেখা হয় literal-এর শেষ /-এর পরে, বা constructor-এর দ্বিতীয় argument হিসেবে। re.flags পড়লে সবগুলো একসাথে পাওয়া যায়।

Flagনামকী করেকবে থেকে
gglobalশুধু প্রথম নয়, সব মিল; lastIndex মনে রাখে (অংশ ৬)শুরু থেকে
iignoreCaseবড়-ছোট হাতের পার্থক্য উপেক্ষা (বাংলায় এর কোনো প্রভাব নেই)শুরু থেকে
mmultiline^ আর $ প্রতিটি লাইনের শুরু-শেষেও মেলেশুরু থেকে
sdotAll. নতুন লাইনও (\n) মেলায়ES2018
uunicodecode point ধরে চলে, \p{…} চালু করে, ভুল escape-এ error দেয়ES2015
ystickyঠিক lastIndex-এর জায়গা থেকেই মিলতে হবে — tokenizer লেখায় কাজেরES2015
dhasIndicesপ্রতিটি group কোথায় শুরু-শেষ, তা m.indices-এ দেয়ES2022
vunicodeSetsu-এর উন্নত রূপ: class-এর ভেতরে বিয়োগ (--), ছেদ (&&)ES2024

দুটি তথ্য মনে রাখুন। এক, u আর v একসাথে দেওয়া যায় না — v নিজেই u-এর সব সুবিধা দেয়। দুই, v flag Chrome/Edge ১১২ (২০২৩) থেকে আছে, আর Node.js 20-এও চলে; খুব পুরোনো ব্রাউজার সমর্থন করতে হলে u-ই নিরাপদ।

৩ · নকশার ইট — character class, anchor আর quantifier

যেকোনো regex মাত্র কয়েক রকম ইট দিয়ে তৈরি। প্রথম ইট হলো হুবহু অক্ষর: /bKash/ শুধু “bKash” মেলায়। দ্বিতীয় ইট character class — একটি ঘরের জন্য কয়েকটি বিকল্প।

লিখবেনমানে (একটি অক্ষর)সাবধানতা
[abc]a, b বা c-এর যেকোনো একটিভেতরে ক্রম গুরুত্বহীন
[3-9], [a-z], [০-৯]পরিসরের যেকোনো একটিপরিসর হয় Unicode নম্বর ধরে
[^0-9]অঙ্ক নয় এমন যেকোনো একটি^ শুধু [-এর ঠিক পরে থাকলে “উল্টো” মানে
\d / \Dঅঙ্ক 0–9 / অঙ্ক নয়শুধু ইংরেজি অঙ্ক — বাংলা ৫ নয়, u দিলেও নয়
\w / \W[A-Za-z0-9_] / তার বাইরেবাংলা অক্ষর \w নয়!
\s / \Sফাঁকা জায়গা (space, tab, newline, Unicode space) / তার বাইরেএটি Unicode-সচেতন
.নতুন লাইন ছাড়া যেকোনো অক্ষরs flag দিলে নতুন লাইনও

তৃতীয় ইট anchor — যা কোনো অক্ষর খায় না, শুধু একটি অবস্থান দাবি করে। ^ = লেখার শুরু, $ = লেখার শেষ (m flag দিলে প্রতিটি লাইনের)। \b হলো word boundary — যেখানে একদিকে \w আর অন্যদিকে নয়। /\bcat\b/ “cat” শব্দ পায়, কিন্তু “category”-র ভেতরের cat নয়। যেহেতু \w শুধু ইংরেজি চেনে, \b-ও বাংলায় অকেজো — অংশ ৭-এ দেখবেন /\bআম\b/.test("আম") পর্যন্ত false!

চতুর্থ ইট quantifier — ঠিক আগের জিনিসটি কতবার আসবে:

Quantifierমানেউদাহরণ
?০ বা ১ বার (ঐচ্ছিক)/colou?r/ → color, colour
*০ বা তার বেশি/\s*/ → ফাঁকা থাকুক বা না থাকুক
+১ বা তার বেশি/\d+/ → এক বা একাধিক অঙ্ক
{n}ঠিক n বার/\d{4}/ → পোস্ট কোড
{n,m}, {n,}n থেকে m বার / অন্তত n বার/\d{10,17}/

Quantifier শুধু তার ঠিক আগের একটি জিনিসের ওপর খাটে: /ab+/ মানে “a, তারপর এক বা একাধিক b”. পুরো “ab” বারবার চাইলে বন্ধনী: /(ab)+/. এবার দেখুন engine এই ইটগুলো দিয়ে আসলে কীভাবে হাঁটে। “পরে” চাপুন:

10        হুবহু '0'21        হুবহু '1'3[3-9]    3 থেকে 9-এর একটি4\d{8}    ঠিক ৮টি অঙ্ক5(শেষ)    নকশা পুরো মিলেছে
/01[3-9]\d{8}/.test(লেখা)
লেখা
শুরুর ঘর
এ পর্যন্ত মিলেছে
এই ধাপ
  1. প্রথম অক্ষর। Engine লেখার ঘর ০-তে দাঁড়িয়ে নকশার প্রথম ইট দেখে: হুবহু '0'. লেখায়ও '0' — মিলল, দুজনেই এক ঘর এগোল।
  2. দ্বিতীয় অক্ষর। নকশা চায় '1', লেখায় '1'. মিলল। এ পর্যন্ত “01”.
  3. Character class — একটি ঘর, অনেক বিকল্প। ঘর ২-এ '7'. সেটি 3 থেকে 9-এর মধ্যে, তাই মিলল। পুরো [3-9] মাত্র একটি অক্ষর খায়। এই ধাপটিই 012 বা 011 দিয়ে শুরু হওয়া নম্বর বাদ দেয়।
  4. Quantifier — ঠিক আটবার। \d{8} পরপর আটটি অক্ষর নেয়: 1, 2, 3, 4, 5, 6, 7, 8 — সবই অঙ্ক। মাঝে একটি হাইফেন থাকলে বা সাতটি অঙ্ক থাকলে এখানেই ব্যর্থ হতো।
  5. নকশা শেষ — true. নকশার সব ইট মিলেছে, তাই test() দেয় true. খেয়াল করুন, engine জানতেও চায়নি লেখার পরে আর কিছু আছে কি না।
  6. ভুল নম্বর: 01212345678. ঘর ২-এ '2', যা 3–9-এ নেই — এই শুরুর জায়গা থেকে মিল নেই। কিন্তু anchor না থাকায় engine হাল ছাড়ে না: শুরুর ঘর এক করে সরিয়ে (১, ২, ৩…) আবার পুরো নকশা চেষ্টা করে। কোথাও “01[3-9]” পায় না, তাই শেষে false.
  7. বিপদ: ভেতরে কোথাও মিললেই হলো। “নম্বর: 0171234567890” লেখায় ঘর ৭ থেকে ১১টি অক্ষর নকশার সাথে মেলে, তাই true — অথচ লেখাটি একটি বৈধ নম্বর নয় (শেষে আরও দুটি অঙ্ক, সামনে শব্দ)। যাচাইয়ের সময় তাই সবসময় /^01[3-9]\d{8}$/: ^ শুরুকে আর $ শেষকে বেঁধে দেয়, ফলে পুরো লেখাটিকেই আকৃতির সাথে মিলতে হয়।

Anchor ছাড়া যাচাই = যাচাই নয়। test()-এর প্রশ্ন হলো “লেখার ভেতরে কোথাও এই আকৃতি আছে কি?” — “পুরো লেখাটি কি এই আকৃতির?” নয়। ফর্ম যাচাই, ফাইলের নাম যাচাই বা নিরাপত্তার কোনো শর্তে ^…$ ভুলে গেলে আক্রমণকারী আসল মানের আগে-পরে যা খুশি জুড়ে দিতে পারে। AI-এর লেখা যাচাই-কোডে এটিই সবচেয়ে সাধারণ ভুল।

৪ · greedy বনাম lazy — quantifier কতটুকু খাবে, আর backtracking

.+ মানে “এক বা একাধিক যেকোনো অক্ষর”। কিন্তু ঠিক কতগুলো? JavaScript-এর quantifier ডিফল্টে greedy: প্রথমে যত বেশি সম্ভব খায়, তারপর বাকি নকশা না মিললে একটি একটি করে ফেরত দেয়। Quantifier-এর পরে ? দিলে (+?, *?, ??, {n,m}?) সেটি হয় lazy: যত কম সম্ভব খায়, দরকার হলে একটি একটি করে বাড়ায়। এই “ফেরত দিয়ে আবার চেষ্টা”-র নাম backtracking.

1s.match(/<.+>/)       // greedy2s.match(/<.+?>/g)     // lazy3s.match(/<[^>]+>/g)  // যা চাই না, তা বাদ
s = "<b>এক</b><b>দুই</b>" (length 19)
.+ ধরেছে
পরের দাবি '&gt;'
  1. শুরু। তিনটি নকশাই প্রথমে < চায়, আর ঘর ০-তেই পায়। এবার quantifier-এর পালা।
  2. Greedy সব খেয়ে ফেলল। .+ লোভীর মতো লেখার শেষ পর্যন্ত সব নিল — ঘর ১ থেকে ১৮. এখন নকশার শেষ ইট > একটি অক্ষর চায়, কিন্তু লেখায় আর কিছু বাকি নেই। ব্যর্থ — তাই backtracking.
  3. একটি ফেরত — মিলে গেল। .+ শেষ অক্ষর > ছেড়ে দিল; এখন > ইটটি সেটি পেল। মিল: পুরো লেখা! আপনি চেয়েছিলেন একটি ট্যাগ, পেলেন প্রথম < থেকে শেষ > পর্যন্ত বিশাল টুকরো।
  4. Lazy — যত কম, তত ভালো। .+? অন্তত একটি অক্ষর নিতে বাধ্য, তাই নিল শুধু b. তারপর সাথে সাথে দেখল > মেলে কি না — ঘর ২-এ মেলে। প্রথম মিল <b>.
  5. g flag — পরের মিল খোঁজা। খোঁজা শুরু হলো ঘর ৩ থেকে। এ, ক — কোনোটি < নয়। ঘর ৫-এ <; .+? নিল /, কিন্তু পরের অক্ষর b, > নয় — তাই একটি বাড়িয়ে /b; এবার ঘর ৮-এ > মিলল। এভাবে চারটি ট্যাগই আলাদা আলাদা পাওয়া গেল।
  6. আরও ভালো: যা চান না, তা বাদ দিন। <[^>]+> বলে “> ছাড়া যেকোনো অক্ষর”। এটি কখনো > পেরিয়ে যায় না, তাই ফেরত দেওয়া বা একটি একটি বাড়ানোর দরকারই পড়ে না — একই চারটি ফল, কম কাজে। ট্যাগের ভেতরের লেখা চাইলে: /<b>(.+?)<\/b>/g দেয় এক আর দুই (group-এ, অংশ ৫)। তবে মনে রাখুন, আসল HTML পড়তে regex নয়, DOM (পর্ব ৩৭)।
মূল ধারণা

Greedy বনাম lazy প্রশ্নের সেরা উত্তর প্রায়ই তৃতীয় পথ: negated class. “উদ্ধৃতিচিহ্নের ভেতরের লেখা” চাইলে /"[^"]*"/, “পরের কমা পর্যন্ত” চাইলে /[^,]*/. নকশা নিজেই তার উদ্দেশ্য বলে, আর engine-কে অন্ধের মতো পিছিয়ে আসতে হয় না।

Backtracking কাছ থেকে — একটি সহজ নকশা

Backtracking শুধু greedy-lazy-এর ব্যাপার নয়; এটি JavaScript-এর regex engine-এর কাজ করার মূল কৌশল। একটি বাস্তব উদাহরণ: “12500” লেখাকে হাজারের আগের অংশ আর শেষ তিন অঙ্কে ভাগ করা — /^(\d+)(\d{3})$/.

"12500".match(/^(\d+)(\d{3})$/)
(\d+) নিয়েছে
বাকি লেখা
(\d{3}) পারল?
ফল
  1. Greedy প্রথমে সব নেয়। \d+ পাঁচটি অঙ্কই নিল। এখন \d{3}-এর জন্য কিছু বাকি নেই — ব্যর্থ।
  2. প্রথমবার পিছোনো। Engine মনে রেখেছিল, \d+ চাইলে কম নিতে পারে। শেষ অঙ্ক “0” ফেরত দিল। কিন্তু একটি অঙ্কে \d{3} হয় না।
  3. আবার পিছোনো। এবার \d+ = “125”, বাকি “00” — এখনো তিনটি নয়।
  4. মিলে গেল। \d+ = “12”, বাকি “500” — \d{3} খুশি, $-ও খুশি (লেখা শেষ)। ফল: group 1 = “12”, group 2 = “500”.
  5. কাজের হিসাব। এখানে engine সর্বোচ্চ লেখার দৈর্ঘ্যের সমান বার পিছোয় — ইনপুট দ্বিগুণ হলে কাজ মোটামুটি দ্বিগুণ। নিরীহ। কিন্তু নকশায় একটি quantifier আরেকটির ভেতরে থাকলে (যেমন (\d+)+) ভাগ করার উপায় সূচকীয় হারে বাড়ে — অংশ ৮-এর ReDoS ঠিক এখান থেকেই জন্মায়।

৫ · group, backreference, alternation আর lookaround

বন্ধনী ( ) দুটি কাজ করে। এক, ভেতরের জিনিসগুলোকে একটি একক বানায়, যাতে quantifier বা | পুরোটার ওপর খাটে। দুই, মিলের সেই অংশটুকু মনে রাখে — একে বলে capturing group. বাঁ দিক থেকে খোলা বন্ধনী গুনে নম্বর হয় ১, ২, ৩…; মিলের ফলে m[0] হলো পুরো মিল, m[1] প্রথম group.

লেখার ধরননামকখন
(\d{4})capturing groupটুকরোটি পরে m[1] বা $1 দিয়ে চাই
(?<year>\d{4})named group (ES2018)নম্বরের বদলে নাম: m.groups.year, বদলিতে $<year>
(?:\+?880|0)non-capturing groupশুধু একক বানাতে; মনে রাখার দরকার নেই, নম্বরও নেয় না
(\w)\1, \k<c>backreferenceআগে ধরা টুকরো হুবহু আবার চাই — “book”-এর “oo”
bKash|Nagad|Rocketalternationকয়েকটি বিকল্পের যেকোনো একটি; বাঁ থেকে প্রথম যেটি মেলে সেটিই

|-এর অগ্রাধিকার সবচেয়ে কম। /^bKash|Nagad$/ মানে “শুরুতে bKash” অথবা “শেষে Nagad” — তাই "bKash123"-ও পাস করে (যাচাই করা)। পুরো লেখাটি তিনটির একটি হতে হলে বিকল্পগুলো group-এ রাখুন: /^(?:bKash|Nagad|Rocket)$/.

Lookaround — দেখে নেয়, কিন্তু খায় না

কখনো শর্ত দরকার, কিন্তু সেই অংশটি মিলের ভেতরে চান না। “টাকা” শব্দের ঠিক আগের সংখ্যাটি চাই, “টাকা” নয়। এর জন্য lookahead আর lookbehind — anchor-এর মতোই এরা কোনো অক্ষর খায় না, শুধু পাশের লেখা পরীক্ষা করে।

লেখামানেউদাহরণ → মিল
X(?=Y)X, যার ঠিক পরে Y আছে/\d+(?=টাকা)/ on “500টাকা” → 500
X(?!Y)X, যার পরে Y নেই/\d+(?!\d|%)/ → শতাংশ নয় এমন সংখ্যা
(?<=Y)XX, যার ঠিক আগে Y আছে (ES2018)/(?<=৳)\d+/ on “দাম ৳250” → 250
(?<!Y)XX, যার আগে Y নেই (ES2018)অংশ ৭-এ বাংলা শব্দের সীমানা বানাতে

একটি ক্লাসিক কৌশল: "1234567".replace(/\B(?=(\d{3})+(?!\d))/g, ",") দেয় 1,234,567 — “যেখানে ডানে তিনের গুণিতক সংখ্যক অঙ্ক বাকি, সেখানে কমা”। তবে বাংলাদেশে লাখ-কোটির নিয়মে কমা বসে (১২,৩৪,৫৬৭), আর এমন কাজে regex-এর চেয়ে Intl.NumberFormat("bn-BD") অনেক নির্ভরযোগ্য (পর্ব ২০)। JavaScript-এর lookbehind যেকোনো দৈর্ঘ্যের নকশা নিতে পারে — Python-এর মতো স্থির দৈর্ঘ্যের বা Java-র মতো সীমিত দৈর্ঘ্যের বাধ্যবাধকতা নেই।

৬ · regex-এর method — test, exec, match, matchAll, replace, split

কিছু method থাকে regex-এর ওপর (re.test(s)), কিছু string-এর ওপর (s.match(re)). কোনটি কী ফেরত দেয়, সেটিই আসল কথা:

Methodফেরত দেয়g flag-এর প্রভাব
re.test(s)true/falseg বা y থাকলে lastIndex থেকে খোঁজে আর বদলায় — ফাঁদ!
re.exec(s)একটি মিলের array (index, groups-সহ) বা nullg থাকলে প্রতি কলে পরের মিল
s.match(re)g ছাড়া: exec-এর মতো এক মিল; g-সহ: শুধু মিলের লেখাগুলোর arrayg-সহ group আর index হারিয়ে যায়; মিল না পেলে null, খালি array নয়
s.matchAll(re)সব মিলের iterator, প্রতিটিতে group আর indexg বাধ্যতামূলক, না দিলে TypeError (ES2020)
s.search(re)প্রথম মিলের index বা -1উপেক্ষা করে
s.replace(re, x)নতুন stringg ছাড়া প্রথমটি, g-সহ সব
s.replaceAll(re, x)নতুন stringg বাধ্যতামূলক, না দিলে TypeError
s.split(re)টুকরোর arrayউপেক্ষা করে; নকশায় group থাকলে বিভাজকও array-তে ঢোকে

matchAll-এ g না দিলে Node.js আর Chrome/Edge-এ বার্তা আসে TypeError: String.prototype.matchAll called with a non-global RegExp argument; replaceAll-এ একই বার্তা, শুধু method-এর নামটি আলাদা। এই নিয়ম ইচ্ছাকৃত: “সব মিল” চাওয়ার সময় ভুলে “একটি মিল”-এর regex দিলে চুপচাপ ভুল ফলের বদলে স্পষ্ট error পাওয়া ভালো।

lastIndex-এর ফাঁদ — g flag মানে regex-এর ভেতরে স্মৃতি

g বা y flag-যুক্ত regex একটি সংখ্যা মনে রাখে — lastIndex: পরের খোঁজা কোন ঘর থেকে শুরু হবে। exec দিয়ে লুপে সব মিল বের করতে এটি দরকারি। কিন্তু একই regex দিয়ে বারবার test() ডাকলে এই স্মৃতিই বাগ হয়ে দাঁড়ায়:

1const re = /01[3-9]\d{8}/g;2re.test("01712345678");3re.test("01712345678");4re.test("01712345678");5// সমাধান: g বাদ, অথবা re.lastIndex = 0
regex object-এর ভেতরের অবস্থা
খোঁজা শুরু
re.lastIndex
  1. Regex তৈরি। g flag-সহ একটি regex object; lastIndex শুরুতে 0. মনে রাখুন, regex একটি object — তার ভেতরের অবস্থা কলের পর কল টিকে থাকে।
  2. প্রথম test() — true. ঘর ০ থেকে খুঁজে মিল পেল ০–১১-এ। g থাকায় engine lastIndex-কে মিলের শেষে, 11-এ রেখে দিল — “পরের বার এখান থেকে খুঁজব”।
  3. দ্বিতীয় test() — false! একই লেখা, একই regex — কিন্তু খোঁজা শুরু হলো ঘর ১১ থেকে, যেখানে লেখা শেষ। কোনো মিল নেই, তাই false, আর ব্যর্থ হওয়ায় lastIndex ফিরে গেল 0-তে।
  4. তৃতীয় test() — আবার true. এভাবে true, false, true পালা করে আসে। একটি ফোন-তালিকায় list.filter(p => re.test(p)) লিখলে প্রতি দ্বিতীয় বৈধ নম্বর বাদ পড়তে পারে — কোনো error ছাড়াই (অনুশীলন ৩)।
  5. সমাধান। হ্যাঁ/না যাচাইয়ের regex-এ g দেবেন না — এর কোনো দরকারই নেই। g দরকার শুধু “সব মিল” চাওয়ার সময়, আর সেখানে matchAll বা replace ব্যবহার করুন — এরা নিজেরাই lastIndex সামলায়। বাধ্য হলে কলের আগে re.lastIndex = 0.

এবার সব একসাথে চালিয়ে দেখুন — ফোন-তালিকা যাচাই, anchor ভুলে যাওয়ার বিপদ, lastIndex-এর ফাঁদ আর SMS থেকে অর্ডার নম্বর তোলা:

// ১) বাংলাদেশি মোবাইল নম্বর — শুরু থেকে শেষ পর্যন্ত মিলতে হবে
const BD_MOBILE = /^(?:\+?880|0)1[3-9]\d{8}$/;
const list = ["01712345678", "+8801812345678", "01212345678",
  "0171234567", "আমার নম্বর 01712345678", "01712345678 "];
for (const p of list) console.log(BD_MOBILE.test(p) ? "✓" : "✗", JSON.stringify(p));

// ২) anchor ছাড়া: লেখার ভেতরে কোথাও মিললেই true
console.log(/01[3-9]\d{8}/.test("নম্বর: 0171234567890"));

// ৩) g flag-এর ফাঁদ: একই regex, একই লেখা, দুবার test()
const once = /01[3-9]\d{8}/g;
console.log(once.test("01712345678"), once.lastIndex, once.test("01712345678"), once.lastIndex);

// ৪) SMS থেকে সব অর্ডার নম্বর — matchAll-এ g লাগবেই
const sms = "ORD-1042 পাঠানো হয়েছে, ORD-1043 প্রস্তুত, ORD-99 বাতিল";
for (const m of sms.matchAll(/ORD-(\d{4})\b/g)) console.log(m[1], "index", m.index);

প্রথম ছয় লাইনে শুধু প্রথম দুটি ✓: “01712345678” আর “+8801812345678”. (?:\+?880|0) মানে “+880 বা 880, অথবা 0” — দেশের কোড দিলে সামনের 0 বাদ যায়, তাই দুটিই একই নকশায় মেলে। “01212345678” বাদ পড়ল [3-9]-এর জন্য, “0171234567”-এ অঙ্ক একটি কম, আর শেষ দুটি বাদ পড়ল anchor-এর জন্য — সামনে শব্দ, বা শেষে একটি space. (বাস্তবে যাচাইয়ের আগে trim() করে নিন।) পরের লাইনে true — anchor-হীন নকশা ১৩ অঙ্কের লেখাকেও পাস করাল। তারপর true 11 false 0 — ডায়াগ্রামের ফাঁদ, নিজের চোখে। শেষে 1042 index 0 আর 1043 index 24: “ORD-99” বাদ, কারণ চারটি অঙ্ক নেই; আর \b নিশ্চিত করে যে “ORD-12345”-এর প্রথম চার অঙ্ক ভুল করে ধরা পড়বে না। m[1] হলো group-এর লেখা, m.index মিলটি কোন ঘরে শুরু।

replace-এর বদলি লেখা আর function

replace-এর দ্বিতীয় argument string হলে তার ভেতরে কয়েকটি বিশেষ সংকেত কাজ করে: $1, $2 = group, $<name> = named group, $& = পুরো মিল, $$ = একটি আসল $ চিহ্ন। যেমন "15/03/2026".replace(/(\d{2})\/(\d{2})\/(\d{4})/, "$3-$2-$1") দেয় 2026-03-15.

দ্বিতীয় argument একটি function হলে প্রতিটি মিলের জন্য সেটি ডাকা হয়, আর যা ফেরত দেয় সেটিই বসে। Function পায় (match, p1, p2, …, offset, string, groups) — পুরো মিল, প্রতিটি group, মিলের অবস্থান, পুরো লেখা আর named group-এর object. হিসাব করে বদলাতে (দাম দ্বিগুণ, অঙ্ক রূপান্তর, বড় হাতের অক্ষর) এটিই পথ — পরের অংশের কোডে দেখবেন।

split-এ regex দিলে একাধিক বিভাজক একসাথে সামলানো যায়: "a, b;c".split(/[,;]\s*/) দেয় ["a", "b", "c"]. নকশায় capturing group থাকলে বিভাজকও ফলে ঢোকে: "a1b".split(/(\d)/) → ["a", "1", "b"].

৭ · বাংলা লেখায় regex — u flag, \p{…} আর বাংলা অঙ্ক

JavaScript-এর regex-এর জন্ম ১৯৯০-এর দশকে, ইংরেজি-কেন্দ্রিক Perl-এর আদলে। তাই পুরোনো সংক্ষিপ্ত রূপগুলো শুধু ইংরেজি চেনে, আর এ নিয়ম u flag দিলেও বদলায় না — পুরোনো কোড যাতে না ভাঙে:

  • \w = [A-Za-z0-9_]. বাংলা অক্ষর এর বাইরে, তাই /\w+/.exec("বাংলা") দেয় null.
  • \b নির্ভর করে \w-এর ওপর। “আম” লেখায় কোনো \w নেই, তাই কোনো সীমানাও নেই: /\bআম\b/.test("আম") হলো false — আমরা Node.js আর Edge-এ যাচাই করেছি।
  • \d = শুধু 0–9. /\d/.test("৫") হলো false. বাংলা অঙ্কের জন্য লিখুন [০-৯] — ০ থেকে ৯ Unicode-এ পরপর সাজানো (U+09E6–U+09EF), তাই পরিসর কাজ করে।

Unicode property escape — অক্ষরকে তার বৈশিষ্ট্য দিয়ে চেনা

ES2018 থেকে u (বা v) flag দিলে Unicode property escape লেখা যায়: \p{…} মানে “এই বৈশিষ্ট্যের যেকোনো একটি অক্ষর”, \P{…} উল্টো। প্রতিটি code point-এর সাথে Unicode কিছু তথ্য রাখে — কোন লিপির, অক্ষর না চিহ্ন না অঙ্ক — আর \p সেই তথ্য দিয়ে খোঁজে।

লিখবেনমেলেবাংলায় সাবধানতা
\p{Script=Bengali}বাংলা লিপির যেকোনো code point — অক্ষর, কার, হসন্ত, অঙ্ক, ৳দাঁড়ি “।” এতে নেই (সেটি একাধিক লিপির সাধারণ চিহ্ন)
\p{L}যেকোনো লিপির “অক্ষর” (Letter)া, ি, ্-এর মতো কার-চিহ্ন \p{L} নয়, \p{M} (Mark)!
[\p{L}\p{M}]যেকোনো ভাষার অক্ষর আর তার চিহ্নবহু-ভাষার “শব্দ” খুঁজতে ভালো
\p{Nd}যেকোনো লিপির দশমিক অঙ্ক — 5, ৫, ٥…“অঙ্ক তো” — কিন্তু Number() বাংলা অঙ্ক বোঝে না, আগে রূপান্তর লাগে

u flag ভুলে গেলে কী হয়? /\p{L}/ লিখলে \p হয়ে যায় সাধারণ “p” অক্ষর, আর নকশাটি খোঁজে আক্ষরিক লেখা “p{L}”! কোনো error আসে না — /\p{L}/.test("p{L}") হলো true. এজন্যই নিয়ম: বাংলা বা ইমোজি থাকতে পারে এমন লেখায় সবসময় u বা v flag. u আরেকটি কাজও করে: . তখন একটি পুরো code point ধরে, তাই /^.$/u.test("🙂") হলো true, কিন্তু u ছাড়া false — কারণ পর্ব ০৯-এ দেখা surrogate pair-এর দুটি ঘর।

এবার বাস্তব কাজ — বাংলা অঙ্ক রূপান্তর, তারিখের format বদলানো, নম্বর ঢাকা আর বাংলা শব্দ গোনা:

// ১) বাংলা অঙ্ক → ইংরেজি অঙ্ক: বদলি হিসেবে একটি function
const BN = "০১২৩৪৫৬৭৮৯";
const toLatin = s => s.replace(/[০-৯]/g, d => BN.indexOf(d));
console.log(toLatin("০১৭১২৩৪৫৬৭৮"), /\d/.test("৫"));

// ২) named group দিয়ে তারিখ ঘোরানো: 15/03/2026 → 2026-03-15
const DATE = /^(?<d>\d{2})\/(?<m>\d{2})\/(?<y>\d{4})$/;
console.log("15/03/2026".replace(DATE, "$<y>-$<m>-$<d>"));

// ৩) নম্বর ঢাকা: প্রথম ৩টি আর শেষ ৩টি অঙ্ক রেখে
console.log("01712345123".replace(/^(\d{3})\d{5}(\d{3})$/, "$1XXXXX$2"));

// ৪) বাংলা শব্দ: \w ব্যর্থ, \p{L} অর্ধেক, Script=Bengali সফল
const text = "আমি ঢাকায় থাকি। I love Dhaka, মেট্রোরেল চমৎকার!";
console.log(text.match(/\w+/g));
console.log("ঢাকায় থাকি".match(/\p{L}+/gu));
const words = text.match(/\p{Script=Bengali}+/gu);
console.log(words.length, words);

প্রথম লাইনে 01712345678 false: function প্রতিটি বাংলা অঙ্কের জন্য ডাকা হলো, BN.indexOf("৭") দিল 7 — আর \d যে বাংলা ৫ চেনে না, তারও প্রমাণ। ফর্মে মানুষ বাংলা অঙ্কে নম্বর লিখলে আগে এই রূপান্তর, তারপর ফোন-যাচাই। দ্বিতীয় লাইনে 2026-03-15 — $<y> নাম ধরে group বসাল; নকশায় নতুন group যোগ হলেও নম্বর সরে যাওয়ার ভয় নেই। তৃতীয় লাইনে 017XXXXX123. চতুর্থ অংশে \w+ শুধু [ 'I', 'love', 'Dhaka' ] পেল — বাংলা পুরো অদৃশ্য। \p{L}+ দিল [ 'ঢ', 'ক', 'য', 'থ', 'ক' ] — প্রতিটি কার-চিহ্নে শব্দ ভেঙে গেছে, কারণ কার \p{L} নয়। আর \p{Script=Bengali}+ ঠিকঠাক পাঁচটি শব্দ দিল: আমি, ঢাকায়, থাকি, মেট্রোরেল, চমৎকার; দাঁড়ি “।” শব্দের অংশ হয়নি।

দুটি সূক্ষ্ম ব্যতিক্রম। এক, “র‍্যাব”-এর মতো কিছু বানানে ZWJ (U+200D) বা ZWNJ (U+200C) নামের অদৃশ্য জোড়-চিহ্ন থাকে, যা বাংলা লিপির নয় — তাই \p{Script=Bengali}+ সেখানে শব্দ ভেঙে ফেলে (আমরা পেয়েছি [ 'র', '্যাব' ])। সমাধান: /[\p{Script=Bengali}\u200C\u200D]+/gu. দুই, regex কাজ করে code point ধরে, চোখের অক্ষর (grapheme) ধরে নয়। “প্রথম ৫টি অক্ষর” বা “উল্টানো”-র মতো কাজে regex নয়, পর্ব ০৯-এর Intl.Segmenter.

বাংলায় word boundary বানানো

\b অকেজো হলে lookaround দিয়ে নিজের সীমানা বানান: /(?<![\p{L}\p{M}])আম(?![\p{L}\p{M}])/u মানে “আম, যার আগে বা পরে কোনো অক্ষর বা কার-চিহ্ন নেই”। এটি “আম খাই”-তে মেলে, কিন্তু “আমার” বা “জাম”-এ নয় (যাচাই করা)। এই ধরনের নকশা হুবহু মুখস্থ রাখার দরকার নেই — মনে রাখুন, বাংলায় \b লেখা দেখলেই সন্দেহ করুন।

v flag — class-এর ভেতরে অঙ্ক কষা

ES2024-এর v flag (unicodeSets) character class-এ set-এর হিসাব আনে: -- বিয়োগ আর && ছেদ। /[\p{Script=Bengali}&&\p{Nd}]/v = “বাংলা লিপির দশমিক অঙ্ক” — ৫ মেলে, ক মেলে না। /[\p{L}--\p{Script=Latin}]/v = “ইংরেজি ছাড়া যেকোনো অক্ষর”। v দিয়ে \p{RGI_Emoji}-এর মতো “লেখার property”-ও খোঁজা যায়, যা একাধিক code point-এর পুরো ইমোজি (যেমন 👍🏽) একসাথে ধরে।

৮ · বাস্তব যাচাই, ReDoS আর পড়ার মতো regex

বাংলাদেশি তথ্যের format যাচাই

নিচের নকশাগুলো শুধু format যাচাই করে — “দেখতে ঠিক আছে কি না”। নম্বরটি সত্যিই কারও কি না, NID আসল কি না, ঠিকানা আছে কি না — regex তা জানে না, জানার উপায়ও নেই। আসল যাচাই হয় OTP, confirmation ইমেইল বা সংশ্লিষ্ট কর্তৃপক্ষের সেবা দিয়ে।

তথ্যনকশামন্তব্য
মোবাইল নম্বর/^(?:\+?880|0)1[3-9]\d{8}$/013–019 অপারেটর কোড; আগে বাংলা অঙ্ক রূপান্তর, space-হাইফেন সরানো
পোস্ট কোড/^\d{4}$/৪ অঙ্ক, যেমন ঢাকা GPO 1000
NID নম্বর/^(?:\d{10}|\d{13}|\d{17})$/স্মার্ট কার্ডে ১০, পুরোনো কার্ডে ১৩ বা ১৭ অঙ্ক; শুধু format
ইমেইল/^[^\s@]+@[^\s@]+\.[^\s@]+$/ইচ্ছা করেই ঢিলেঢালা: “@-এর দুই পাশে কিছু, ডোমেইনে একটি ফোঁটা”

ইমেইলের জন্য ছোট নকশা কেন? ইমেইল ঠিকানার আনুষ্ঠানিক নিয়ম (RFC 5322) এত জটিল যে তাকে পুরোপুরি ধরা regex কয়েকশো অক্ষরের হয় — পড়া অসম্ভব, আর তবুও বলতে পারে না ঠিকানাটি কাজ করে কি না। ব্রাউজারে সহজ পথ <input type="email">: এটি নিজেই একটি যুক্তিসঙ্গত format যাচাই করে (পর্ব ৩৯)। তারপর একটি confirmation লিংক পাঠান — সেটিই একমাত্র আসল যাচাই।

Catastrophic backtracking আর ReDoS

অংশ ৪-এ দেখেছেন, “12500”-এ engine মাত্র তিনবার পিছিয়েছিল। এবার /^(a+)+$/ নকশাটি ভাবুন, আর লেখা “aaaaaaaaaaaaaaaaaaaaX” — অনেকগুলো a, শেষে একটি X, তাই মিল কখনোই হবে না। কিন্তু “মিল নেই” বলার আগে engine-কে প্রমাণ করতে হয় যে কোনো উপায়েই মেলে না। আর বাইরের + আর ভেতরের + মিলে n-টি a-কে ভাগ করার উপায় 2n−1টি: “aaaa” হতে পারে (aaaa), (aaa)(a), (aa)(aa), (a)(aaa), (a)(a)(aa)… engine সবগুলো চেষ্টা করে। একে বলে catastrophic backtracking.

a-এর সংখ্যা (n)ভাগ করার উপায় (2n−1)অনুভূতি
৫১৬চোখের পলক
১০৫১২এখনো টের পাবেন না
২০৫,২৪,২৮৮লক্ষণীয় দেরি
৩০৫৩,৬৮,৭০,৯১২পাতা বা সার্ভার “ঝুলে” যায়

প্রতিটি a যোগ করলে কাজ দ্বিগুণ। ব্রাউজারে এর মানে পাতা জমে যাওয়া, কারণ regex চলে main thread-এ (পর্ব ৩১-এ event loop দেখবেন)। আর Node.js সার্ভারে এর মানে আরও খারাপ: একটিমাত্র অনুরোধ পুরো প্রসেস আটকে দেয়, অন্য সব ব্যবহারকারী অপেক্ষায় থাকেন। কেউ ইচ্ছা করে এমন ইনপুট পাঠালে তার নাম ReDoS (Regular expression Denial of Service). আত্মরক্ষার উপায়:

পড়ার মতো regex — টুকরো দিয়ে গড়ুন, মন্তব্য লিখুন, পরীক্ষা রাখুন

Python বা Java-র regex-এ মন্তব্যসহ বহু-লাইনের “verbose” মোড আছে; JavaScript-এ নেই। তবে String.raw আর array-এর join দিয়ে একই সুবিধা পাওয়া যায় — প্রতিটি টুকরো আলাদা লাইনে, পাশে মন্তব্য:

const BD_MOBILE = new RegExp([
  "^",
  String.raw`(?:\+?880|0)`,  // দেশের কোড +880/880, অথবা শুরুর 0
  String.raw`1[3-9]`,        // অপারেটর কোড: 013–019
  String.raw`\d{8}`,         // বাকি ৮টি অঙ্ক
  "$",
].join(""));

// নকশার পাশেই পরীক্ষার তালিকা — নকশা বদলালে আবার চালান
const cases = [["01712345678", true], ["+8801312345678", true],
               ["01212345678", false], ["017123456789", false]];
for (const [input, want] of cases)
  console.assert(BD_MOBILE.test(input) === want, input);

এই কোডটি দেখানোর জন্য। চূড়ান্ত regex হুবহু আগের /^(?:\+?880|0)1[3-9]\d{8}$/, কিন্তু এখন প্রতিটি অংশের উদ্দেশ্য লেখা আছে। console.assert শর্ত মিথ্যা হলেই কেবল console-এ বার্তা দেখায় — সব ঠিক থাকলে চুপ। বড় প্রজেক্টে এই তালিকা চলে যায় টেস্ট ফাইলে (পর্ব ৪৮)। নকশা লেখার সময় regex101.com-এর মতো অনলাইন টুলে “ECMAScript (JavaScript)” মোড বেছে পরীক্ষা করুন — প্রতিটি টুকরোর ব্যাখ্যা আর ধাপে ধাপে মেলানো দেখায়। তবে গোপন বা ব্যক্তিগত ডেটা সেখানে পেস্ট করবেন না।

C/C++/Python/Java জানলে

নকশার ভাষা প্রায় একই — সবার উৎস Perl. Java-তে নকশা থাকে string-এ, তাই প্রতিটি backslash দুবার ("\\d"), আর possessive quantifier (++) আছে, যা JavaScript-এ নেই (Java-র regex পর্ব)। Python-এ re module, raw string r"\d", named group লেখে (?P<name>…), আর \w ডিফল্টে Unicode — বাংলা অক্ষরও ধরে, যা JavaScript-এর ঠিক উল্টো (Python-এর regex পর্ব)। C++-এ <regex> আছে, C-তে POSIX regcomp. JavaScript-এর বিশেষত্ব: /…/ literal ভাষার অংশ, আর g flag-এর lastIndex স্মৃতি।

পরীক্ষা ও ইন্টারভিউতে
  • Greedy আর lazy quantifier-এর পার্থক্য? — greedy (.+) যত বেশি সম্ভব খায় তারপর ফেরত দেয়; lazy (.+?) যত কম সম্ভব খায় তারপর বাড়ায়।
  • g flag-এর regex-এ পরপর দুবার test() কেন ভিন্ন ফল দেয়? — lastIndex প্রথম মিলের শেষে সরে যায়, দ্বিতীয় খোঁজা সেখান থেকে শুরু হয়।
  • match আর matchAll-এর পার্থক্য? — g-সহ match শুধু মিলের লেখা দেয়; matchAll প্রতিটি মিলের group আর index-সহ iterator দেয়, আর g বাধ্যতামূলক।
  • (?:…) আর (…)-এর পার্থক্য? — দুটিই group বানায়; প্রথমটি মিল মনে রাখে না, group নম্বরও নেয় না।
  • ReDoS কী, কীভাবে এড়াবেন? — নেস্টেড quantifier-যুক্ত নকশায় বিশেষ ইনপুটে সূচকীয় backtracking; সরল নকশা, দৈর্ঘ্যের সীমা, escape আর linear-time engine.

৯ · AI-এর যুগে — AI-এর লেখা regex যাচাই

Regex এমন একটি জায়গা, যেখানে AI-এর উত্তর দেখতে সবচেয়ে বিশ্বাসযোগ্য আর যাচাই করা সবচেয়ে কঠিন। এক লাইনের চিহ্নের স্তূপ পড়ে কেউ বলতে পারে না সেটি ঠিক কি না — চালিয়ে দেখতে হয়। AI যে কোড থেকে শিখেছে তার বেশিরভাগ ইংরেজি লেখা নিয়ে, তাই কয়েকটি ভুল বারবার ফিরে আসে:

AI যা লেখেকী হয়যা চাইবেন
শত অক্ষরের “RFC-সম্মত” ইমেইল regexকেউ পড়তে বা সারাতে পারে না; কিছু অংশ ReDoS-প্রবণ হতে পারেঢিলেঢালা নকশা + type="email" + confirmation ইমেইল
বাংলা নামের জন্য /^\w+$/ বা \bসব বাংলা নাম বাতিল, বা সীমানা ভুল জায়গায়u flag আর \p{Script=Bengali} বা [\p{L}\p{M}]
/01[3-9]\d{8}/ দিয়ে যাচাইanchor নেই — আগে-পরে যা খুশি জুড়ে পাস^…$
ফাইলের শুরুতে const re = /…/g, পরে re.test()true/false পালা করে আসেযাচাইয়ের regex-এ g নয়
new RegExp(userInput)SyntaxError, ভুল মিল, বা ReDoS-এর দরজাআগে escape
HTML বা JSON regex দিয়ে পড়ানেস্টেড গঠনে ভেঙে যায়DOM, DOMParser, JSON.parse

AI-কে regex লিখতে বলার সময় উদাহরণ, বাংলা প্রেক্ষাপট আর পরীক্ষার তালিকা একসাথে দিন — তাহলে ভুলগুলো আগেভাগেই ধরা পড়ে:

আমি JavaScript (ES2022+, ব্রাউজার আর Node.js 20) দিয়ে একটি regex চাই। কাজ: [যেমন: বাংলাদেশি মোবাইল নম্বর যাচাই] নিয়ম: ১. যাচাইয়ের regex হলে অবশ্যই ^ আর $ দেবে, আর g flag দেবে না। ২. ইনপুটে বাংলা থাকতে পারে: \w, \b, \d বাংলা চেনে না — দরকারে u flag, \p{Script=Bengali} আর [০-৯] ব্যবহার করো। ৩. নকশা ছোট রাখো; নেস্টেড quantifier ((a+)+ ধরনের) দেবে না। ReDoS-এর ঝুঁকি থাকলে আলাদা করে বলো। ৪. বড় নকশা হলে String.raw আর join দিয়ে টুকরো টুকরো লিখে প্রতিটির পাশে মন্তব্য দাও। ৫. অন্তত ৮টি পরীক্ষার ইনপুট দাও — বৈধ, অবৈধ, সীমার কাছের, বাংলা অঙ্কে লেখা, আগে-পরে space বা শব্দ জোড়া — আর প্রতিটির প্রত্যাশিত true/false. ৬. ব্যবহারকারীর লেখা থেকে regex বানাতে হলে আগে escape করবে।

AI-এর regex হাতে পেলে — যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।

প্র ০১Regular expression-এর জন্ম গণিতে, স্নায়ুকোষের মডেল থেকে। সেটি কীভাবে প্রোগ্রামারের টুল হলো, আর “grep” নামটি কোথা থেকে এল?

গল্পটি শুরু ১৯৪০-এর দশকে। ১৯৪৩ সালে Warren McCulloch আর Walter Pitts স্নায়ুকোষকে সরল অন-অফ যন্ত্র ধরে একটি গাণিতিক মডেল দেন। এই মডেল কী ধরনের “ঘটনার ক্রম” চিনতে পারে — সেই প্রশ্নের উত্তর খুঁজলেন গণিতবিদ Stephen Kleene. ১৯৫১ সালে RAND Corporation-এর একটি গবেষণা-প্রতিবেদনে, আর ১৯৫৬ সালে “Automata Studies” বইয়ে প্রকাশিত প্রবন্ধে তিনি দেখান: এমন যন্ত্র ঠিক এক শ্রেণির ক্রম চেনে, যাকে তিনি নাম দেন “regular events”. এগুলো বর্ণনার জন্য তিনি তিনটি ক্রিয়া ব্যবহার করেন — পাশাপাশি বসানো, বিকল্প (আজকের |) আর “যতবার খুশি পুনরাবৃত্তি”। শেষটির চিহ্ন আজও তাঁর নামে: Kleene star, অর্থাৎ *.

প্রায় এক যুগ এটি তত্ত্বই ছিল — finite automaton আর formal language-এর পাঠ্যবইয়ের বিষয়। তারপর ১৯৬৮ সালে Bell Labs-এর Ken Thompson “Communications of the ACM”-এ একটি ছোট প্রবন্ধ প্রকাশ করেন, যেখানে দেখান regular expression-কে কীভাবে দ্রুত চালানো যায়: নকশাকে একটি ছোট যন্ত্রে রূপ দিয়ে লেখার ওপর দিয়ে একবার হেঁটে সব সম্ভাব্য অবস্থা একসাথে অনুসরণ করা। তিনি এটি বসান QED নামের টেক্সট এডিটরে, যা চলত IBM 7094-এর মতো বড় কম্পিউটারে — তাঁর কোড নকশাটিকে সরাসরি machine code-এ অনুবাদ করত।

পরে Thompson Unix-এর এডিটর ed-এ regex আনেন। ed-এ একটি কমান্ড ছিল g/re/p — “global: regular expression মেলে এমন প্রতিটি লাইন print করো”। ১৯৭০-এর দশকের শুরুতে Thompson এই কাজটিকে আলাদা একটি ছোট প্রোগ্রাম হিসেবে লেখেন, আর নাম রাখেন সেই কমান্ড থেকেই: grep. তারপর sed, awk, vi, Emacs, Perl — এক এক করে সব টুল regex শিখে নেয়।

লক্ষ করার মতো ব্যাপার: Kleene-এর “regular” আর আজকের JavaScript regex এক জিনিস নয়। Backreference (\1) বা lookaround গাণিতিক অর্থে regular নয় — এগুলো দিয়ে এমন ভাষা চেনা যায়, যা কোনো finite automaton চিনতে পারে না। নামটি রয়ে গেছে, গণিত অনেকখানি বদলে গেছে — আর সেই বদলের দাম কী, তা প্রশ্ন ০৩-এ।

মূল উপলব্ধি: একটি তাত্ত্বিক প্রশ্ন (“স্নায়ুজাল কী চিনতে পারে?”) থেকে জন্ম নেওয়া গণিত দুই দশকের মধ্যে প্রতিটি প্রোগ্রামারের হাতিয়ার হয়েছে। ভালো তত্ত্ব ভালো টুলের ভিত্তি — আর টুলের সীমাবদ্ধতা বুঝতে সেই তত্ত্বে ফিরতে হয়।

প্র ০২JavaScript-এর regex-এর চেহারা Perl-এর মতো কেন? (?:…), +?, lookahead-এর মতো চিহ্নগুলো কোথা থেকে এল?

১৯৮০-এর দশকে প্রায় প্রতিটি Unix টুলের নিজস্ব regex “উপভাষা” ছিল — grep, egrep, sed, awk সামান্য আলাদা নিয়ম মানত। পরে POSIX মানদণ্ড এগুলোকে “basic” আর “extended” — দুই ভাগে গুছানোর চেষ্টা করে। এই সময় Henry Spencer একটি মুক্ত regex লাইব্রেরি লেখেন, যা বহু প্রোগ্রামে ঢুকে পড়ে।

১৯৮৭ সালের ডিসেম্বরে Larry Wall প্রকাশ করেন Perl — লেখা প্রক্রিয়াকরণের জন্য তৈরি একটি ভাষা, যার কেন্দ্রে ছিল regex. Perl-এ regex কোনো লাইব্রেরি-কল নয়, ভাষার অংশ: $s =~ /pattern/. Perl-এর জনপ্রিয়তার সাথে সাথে তার regex-ও সমৃদ্ধ হয়। বিশেষ করে ১৯৯৪ সালের Perl 5-এ আসে non-greedy quantifier (*?, +?), non-capturing group (?:…) আর lookahead (?=…) — (? দিয়ে শুরু করার এই কৌশলটি চতুর: আগে (? লেখা অর্থহীন ছিল, তাই নতুন সব বৈশিষ্ট্য পুরোনো নকশা না ভেঙে এই “দরজা” দিয়ে ঢোকানো গেল।

১৯৯৭ সালে Philip Hazel লেখেন PCRE (Perl Compatible Regular Expressions) — Perl-এর মতো regex অন্য প্রোগ্রামে আনার একটি C লাইব্রেরি। PHP, Apache, পরে আরও অনেকে এটি নেয়। ফলে “Perl-ধাঁচের regex” কার্যত পুরো শিল্পের মান হয়ে যায়। Java-র java.util.regex, Python-এর re, .NET — সবাই এই ধাঁচ অনুসরণ করে।

JavaScript-এ regex পূর্ণাঙ্গ রূপ পায় ১৯৯৯ সালের ECMAScript 3-এ, আর মানদণ্ডটি স্পষ্টভাবে Perl 5-এর বাক্যগঠন অনুসরণ করে। কিন্তু JavaScript দীর্ঘদিন পিছিয়ে ছিল: lookbehind, named group, s flag আর Unicode property escape এসেছে মাত্র ES2018-এ — Perl-এর প্রায় দুই দশক পরে। কারণ ওয়েবের ভাষায় প্রতিটি নতুন বৈশিষ্ট্য সব ব্রাউজারে একমত হয়ে যোগ করতে হয়। পরের ধাপগুলোও এভাবেই এসেছে: ES2022-এ d flag, ES2024-এ v flag, আর ES2025-এ RegExp.escape.

মূল উপলব্ধি: আজকের প্রায় সব ভাষার regex একই পরিবারের সদস্য, কারণ একটি সফল টুল (Perl) আর একটি লাইব্রেরি (PCRE) কার্যত মান বেঁধে দিয়েছিল। তাই এক ভাষায় শেখা regex অন্য ভাষায় ৯০% কাজে লাগে — বাকি ১০% (escape নিয়ম, flag, Unicode আচরণ) প্রতিবার যাচাই করে নিতে হয়।

প্র ০৩Backtracking engine বনাম automata engine — কোনটি ভালো? ReDoS কি সত্যিই বড় কোনো সেবা বন্ধ করেছে?

Regex চালানোর মূলত দুটি পথ। Automata পথ (Thompson-এর ১৯৬৮ সালের পদ্ধতি): নকশাকে একটি যন্ত্রে রূপ দিয়ে লেখার ওপর দিয়ে একবার হাঁটা, সব সম্ভাব্য অবস্থা একসাথে অনুসরণ করা। সময় লাগে লেখার দৈর্ঘ্যের সমানুপাতিক — কোনো বিস্ফোরণ নেই। কিন্তু backreference এভাবে চালানো যায় না। Backtracking পথ (Perl, PCRE, Java, Python, JavaScript-এর V8): একটি পথ ধরে চেষ্টা, ব্যর্থ হলে পিছিয়ে অন্য পথ। এতে backreference, lookaround সহজে যোগ করা যায়, আর সাধারণ নকশায় এটি খুব দ্রুত — কিন্তু খারাপ নকশায় সময় সূচকীয় হারে বাড়ে।

২০০৭ সালে Russ Cox “Regular Expression Matching Can Be Simple And Fast” নামের একটি প্রবন্ধে বিষয়টি সবার সামনে আনেন। তিনি দেখান, a?nan ধরনের একটি নকশায় Thompson-এর পুরোনো পদ্ধতি মাইক্রোসেকেন্ডে উত্তর দেয়, যেখানে তখনকার Perl, Python বা Java অনেক বেশি সময় নেয় — n একটু বাড়লেই সেকেন্ড পেরিয়ে যায়। Cox পরে Google-এ RE2 তৈরিতে মূল ভূমিকা রাখেন — একটি engine যা ইচ্ছা করেই backreference আর lookaround বাদ দিয়ে linear সময়ের নিশ্চয়তা দেয়। Google-এর কোড সার্চের মতো সেবায়, যেখানে ব্যবহারকারী নিজেই নকশা লেখেন, এই নিশ্চয়তা অপরিহার্য ছিল।

বাস্তব ঘটনা? দুটি সুপরিচিত। ২০১৬ সালের জুলাইয়ে Stack Overflow প্রায় আধা ঘণ্টা বন্ধ ছিল। তাদের প্রকাশিত ব্যাখ্যা অনুযায়ী, একটি পোস্টে প্রায় বিশ হাজার ফাঁকা অক্ষর ছিল, আর লেখার শেষের ফাঁকা জায়গা ছাঁটার একটি সাধারণ-দেখতে regex সেখানে backtracking-এ আটকে যায় (এখানে বৃদ্ধি ছিল বর্গীয়, সূচকীয় নয় — তবু যথেষ্ট)। ২০১৯ সালের ২ জুলাই Cloudflare-এর বিশ্বজুড়ে সেবা প্রায় আধা ঘণ্টা বিঘ্নিত হয়: তাদের ওয়েব অ্যাপ্লিকেশন ফায়ারওয়ালের একটি নতুন নিয়মের regex অতিরিক্ত backtracking করে সার্ভারের CPU পুরো দখল করে নেয়। দুটি ক্ষেত্রেই কোনো আক্রমণকারী লাগেনি — নিজেদের regex-ই যথেষ্ট ছিল।

JavaScript-এর জগতেও প্রতিক্রিয়া এসেছে। V8 দল ২০২১ সালে একটি পরীক্ষামূলক non-backtracking engine যোগ করে, যা ডিফল্টে চালু নয়। npm প্যাকেজের নিরাপত্তা-সতর্কবার্তায় ReDoS একটি নিয়মিত শ্রেণি।

মূল উপলব্ধি: ক্ষমতা আর নিশ্চয়তার মধ্যে একটি বিনিময় আছে। Backtracking বেশি বৈশিষ্ট্য দেয়, automata দেয় সময়ের গ্যারান্টি। আপনার নিজের লেখা ছোট নকশায় backtracking ঠিক আছে; কিন্তু যেখানে নকশা বা বড় ইনপুট বাইরে থেকে আসে, সেখানে প্রশ্ন করুন — “সবচেয়ে খারাপ ইনপুটে কী হবে?”

প্র ০৪\w কেন আজও বাংলা চেনে না, অথচ \p{Script=Bengali} চেনে? ES2018 আর ES2024 কোন সমস্যা মিটিয়েছে?

১৯৯৯ সালে ECMAScript 3 যখন regex মানদণ্ডে আনে, তখন \w, \d, \b-কে সংজ্ঞায়িত করা হয় ASCII দিয়ে — Perl-এর তখনকার আচরণ অনুসরণ করে। বিশ বছরে লক্ষ লক্ষ ওয়েবসাইট এই সংজ্ঞার ওপর দাঁড়িয়ে গেছে: কোনো পাসওয়ার্ড-নিয়ম হয়তো ধরে নেয় \w মানে শুধু ইংরেজি। \w-এর অর্থ বদলালে সেই কোড চুপচাপ ভিন্ন আচরণ করবে। ওয়েবের অলিখিত প্রথম নিয়ম — “ওয়েব ভাঙা যাবে না” — তাই পুরোনো অর্থ রেখে দেয়, এমনকি u flag-এও। (Python 3 এখানে উল্টো পথ নিয়েছিল: str-এর \w ডিফল্টে Unicode — কারণ Python 2 থেকে 3-এ যাওয়ার সময় তারা ভাঙন মেনে নিয়েছিল।)

ES2015-এর u flag প্রথম ধাপ: এটি regex-কে UTF-16 code unit নয়, code point ধরে চালায়, যাতে ইমোজির মতো surrogate pair ভেঙে না যায়। কিন্তু “বাংলা অক্ষর” বলার উপায় তখনো ছিল না — [\u0980-\u09FF]-এর মতো পরিসর হাতে লিখতে হতো, যা ভুলপ্রবণ আর অন্য লিপির জন্য আরও জটিল।

ES2018 আনল Unicode property escape: \p{Script=Bengali}, \p{L}, \p{Nd}. পুরোনো সংক্ষিপ্ত রূপের অর্থ না বদলে নতুন বাক্যগঠন যোগ করা হলো, আর u flag ছাড়া \p আগের মতো সাধারণ “p” থেকে গেল — পুরোনো কোড নিরাপদ। এই তথ্য আসে সরাসরি Unicode-এর ডেটাবেস থেকে, তাই Unicode-এর নতুন সংস্করণে লিপি যোগ হলে engine আপডেটের সাথে সাথে regex-ও তা চেনে।

তবু কিছু অভাব ছিল। “বাংলা অক্ষর, কিন্তু অঙ্ক নয়” লিখতে lookahead-এর কৌশল লাগত। আর একাধিক code point-এর ইমোজি (👍🏽 = হাত + রঙের চিহ্ন) একটি ঘর দিয়ে ধরা যেত না। ES2024-এর v flag (unicodeSets) দুটোই মিটিয়েছে: class-এর ভেতরে -- আর &&, আর \p{RGI_Emoji}-এর মতো “লেখার property”. u-এর জায়গায় নতুন flag কেন? কারণ v-তে কিছু চিহ্নের ((, |, - ইত্যাদি) class-এর ভেতরে escape বাধ্যতামূলক — পুরোনো u নকশায় এই নিয়ম আনলে সেগুলো ভেঙে যেত।

মূল উপলব্ধি: ভাষার নকশাকাররা পুরোনো অর্থ বদলান না, নতুন বাক্যগঠন যোগ করেন। ফলে “সঠিক” উপায়টি প্রায়ই নতুন, লম্বা আর কম পরিচিত — আর পুরোনো সংক্ষিপ্ত রূপটি বাংলার মতো লিপির জন্য নীরব ফাঁদ হয়ে থাকে।

প্র ০৫“Some people, when confronted with a problem, think ‘I know, I’ll use regular expressions.’ Now they have two problems.” — উক্তিটি কার, কোন প্রেক্ষাপটে, আর এটি কি regex-বিরোধী?

উক্তিটি Jamie Zawinski-র। তিনি ছিলেন Netscape Navigator-এর প্রথম দিকের প্রোগ্রামারদের একজন আর Lucid Emacs (পরে XEmacs) এডিটরের অন্যতম প্রধান ডেভেলপার। ১৯৯৭ সালের আগস্টে Usenet-এর comp.emacs.xemacs আলোচনা-গোষ্ঠীতে এক পোস্টে তিনি কথাটি লেখেন। সেই সময় Perl-এর প্রভাবে সবকিছুতে regex ব্যবহারের একটি ঝোঁক ছিল — এমনকি যেখানে সরল কোড বা একটি সঠিক parser ভালো কাজ করত। পরে Jeffrey Friedl (“Mastering Regular Expressions” বইয়ের লেখক) উক্তিটির উৎস খুঁজে দেখান যে একই ছাঁচের কৌতুক আগেও অন্য টুল নিয়ে প্রচলিত ছিল; Zawinski-র রূপটিই বিখ্যাত হয়েছে।

উক্তিটি regex-বিরোধী নয়, ভুল জায়গায় regex-বিরোধী। “দ্বিতীয় সমস্যা” বলতে বোঝায়: (১) একটি নকশা যা কেউ পড়তে পারে না, তাই ছয় মাস পরে কেউ বদলাতে সাহস পায় না; (২) সীমার কাছের ইনপুটে চুপচাপ ভুল ফল; (৩) কখনো কখনো পারফরম্যান্সের বিপর্যয় (প্রশ্ন ০৩)। HTML, JSON, CSV, ইমেইল ঠিকানা, প্রোগ্রামিং ভাষার কোড — এদের নেস্টেড বা জটিল গঠন আছে, যা regex-এর “একবার বাঁ থেকে ডানে হাঁটা”-র মডেলে আঁটে না। সেখানে regex দিয়ে শুরু করলে প্রতিটি নতুন ব্যতিক্রম নকশাকে আরও জটিল করে, যতক্ষণ না সেটি অচল হয়।

অন্যদিকে লগ ফাইলে একটি আকৃতি খোঁজা, ফোন নম্বরের format যাচাই, SMS থেকে TrxID তোলা, কোড এডিটরে “খোঁজো আর বদলাও” — এসব কাজে regex অপ্রতিদ্বন্দ্বী। একটি ভালো মাপকাঠি: নকশাটি কি এক লাইনে মুখে ব্যাখ্যা করা যায়? “০১, তারপর ৩–৯, তারপর ৮টি অঙ্ক” — যায়। “RFC 5322-এর সব ব্যতিক্রমসহ ইমেইল” — যায় না; সেখানে অন্য টুল খুঁজুন।

AI-এর যুগে কথাটি নতুন অর্থ পেয়েছে। আগে জটিল regex লিখতে সময় লাগত, তাই মানুষ দুবার ভাবত। এখন AI সেকেন্ডে দুশো অক্ষরের নকশা দেয় — লেখার খরচ শূন্য, কিন্তু বোঝা আর রক্ষণাবেক্ষণের খরচ আগের মতোই। তাই প্রশ্নটি এখন “লিখতে পারব কি?” নয়, “আমি এবং আমার দল কি এটি পড়তে, পরীক্ষা করতে আর বদলাতে পারব?”

মূল উপলব্ধি: যেকোনো শক্তিশালী টুলের সবচেয়ে বড় বিপদ হলো ভুল জায়গায় ব্যবহার। Regex-কে ছোট, পরীক্ষিত আর ব্যাখ্যাযোগ্য রাখুন; গঠনওয়ালা ডেটার জন্য parser ব্যবহার করুন।

অনুশীলন

বেশিরভাগ কাজ ব্রাউজারের console-এ এক লাইন লিখে দেখা আর ভাবা — কোড লিখতে হবে শুধু শেষটিতে।

  1. আগে অনুমান, পরে যাচাই: console-এ চালানোর আগে খাতায় লিখুন প্রতিটির ফল: (ক) "a1b22c333".match(/\d+/g) (খ) "a1b22".match(/\d+/)[0] (গ) "2026".search(/0/) (ঘ) "a,b;;c".split(/[,;]+/)

    (ক) [ '1', '22', '333' ] — g-সহ match সব মিলের লেখা দেয়; \d+ greedy, তাই পাশাপাশি অঙ্কগুলো একসাথে। (খ) '1' — g ছাড়া শুধু প্রথম মিল। (গ) 1 — প্রথম “0” ঘর ১-এ। (ঘ) [ 'a', 'b', 'c' ] — [,;]+ পরপর দুটি ;-কে একটি বিভাজক ধরে, তাই মাঝে খালি string আসে না।

  2. নিরীক্ষা: console-এ চালান /\bআম\b/.test("আম") আর /(?<![\p{L}\p{M}])আম(?![\p{L}\p{M}])/u.test("আমার"). দুটির ফল কী, আর কেন?

    দুটিই false, কিন্তু কারণ সম্পূর্ণ আলাদা। প্রথমটি ভুল কারণে false: \b খোঁজে \w আর অ-\w-এর সীমানা; বাংলা অক্ষর \w নয়, তাই “আম” লেখায় কোনো সীমানাই নেই — শব্দটি হুবহু থাকা সত্ত্বেও মিল হয় না। দ্বিতীয়টি সঠিক কারণে false: “আম”-এর পরে আছে “া”, যা \p{M} — negative lookahead বলে “পরে কোনো অক্ষর বা চিহ্ন থাকা চলবে না”, তাই “আমার”-এর ভেতরের “আম” বাদ পড়ে। একই নকশা "আম খাই"-এ true দেয়।

  3. বাগ খুঁজুন: একটি AI এই কোড দিয়েছে: const re = /^01[3-9]\d{8}$/g; তারপর ["01712345678", "01812345678", "01912345678"].filter(p => re.test(p)). তিনটি নম্বরই বৈধ। ফল কী আসবে, কেন, আর কীভাবে সারাবেন?

    ফল [ '01712345678', '01912345678' ] — মাঝের নম্বরটি হারিয়ে গেছে (Node.js আর Edge-এ যাচাই করা)। প্রথম test() সফল হয়ে lastIndex-কে 11-এ রাখে। দ্বিতীয় নম্বরে খোঁজা শুরু হয় ঘর ১১ থেকে, যেখানে লেখা শেষ — ব্যর্থ, আর lastIndex ফিরে যায় 0-তে। তৃতীয়টি আবার সফল। সমাধান: g flag মুছে দিন — হ্যাঁ/না যাচাইয়ে এর কোনো কাজ নেই। লক্ষ করুন, ^…$ থাকা সত্ত্বেও বাগটি হয়েছে; anchor আর lastIndex সম্পূর্ণ আলাদা ব্যাপার।

  4. ছোট কোড লিখুন: toIsoDate(s) নামে একটি function লিখুন, যা “15/03/2026” বা বাংলা অঙ্কে “১৫/০৩/২০২৬” নিয়ে “2026-03-15” ফেরত দেয়, আর format না মিললে null. পরীক্ষা করুন “15-03-2026” আর “1/3/2026” দিয়েও।

    একটি সমাধান:

    const BN = "০১২৩৪৫৬৭৮৯";
    const toLatin = s => s.replace(/[০-৯]/g, d => BN.indexOf(d));
    const DATE = /^(?<d>\d{2})\/(?<m>\d{2})\/(?<y>\d{4})$/;
    
    function toIsoDate(s) {
      const m = DATE.exec(toLatin(s.trim()));
      if (!m) return null;
      const { d, m: mo, y } = m.groups;
      return `${y}-${mo}-${d}`;
    }

    প্রথম দুটি দেয় "2026-03-15", শেষ দুটি null — হাইফেন নকশায় নেই, আর “1/3” এক অঙ্কের। আগে বাংলা অঙ্ক রূপান্তর, তারপর regex — এই ক্রমটিই মূল কৌশল। মনে রাখুন, এটি শুধু format যাচাই: “31/02/2026”-ও পাস করবে। তারিখটি আসলে আছে কি না দেখতে পর্ব ২০-এর Date বা Temporal দিয়ে যাচাই করুন। m: mo লেখা হয়েছে কারণ m নামটি আগেই মিলের জন্য ব্যবহৃত (পর্ব ১৭-এর destructuring-এ নাম বদল)।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমএকটি সার্চ বক্সে ব্যবহারকারীর লেখা দিয়ে নিরাপদে regex বানিয়ে পাতার লেখায় মিলগুলো চিহ্নিত করার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. 📥 সার্চ বক্স থেকে লেখাটি নেওয়া
  2. 📏 দৈর্ঘ্যের সীমা যাচাই — খুব লম্বা হলে বাতিল
  3. 🛡️ সব বিশেষ অক্ষর escape করা
  4. 🧩 new RegExp(escaped, "giu") দিয়ে regex বানানো
  5. 🔍 replace দিয়ে প্রতিটি মিল চিহ্নিত করা

JavaScript-এ /^01[3-9]\d{8}$/.test("01212345678")-এর ফল কী?

  • true
  • false
  • SyntaxError
  • null

const re = /a/g; re.test("a"); re.test("a"); — দ্বিতীয় test()-এর ফল কী, আর কেন?

  • true, কারণ একই লেখা
  • TypeError, কারণ একই regex দুবার ব্যবহার করা যায় না
  • undefined, কারণ lastIndex শেষে পৌঁছেছে
  • false, কারণ g flag-এর lastIndex প্রথম মিলের শেষে সরে গেছে

"<b>এক</b><b>দুই</b>".match(/<.+>/)[0] কী দেয়?

  • পুরো লেখা: <b>এক</b><b>দুই</b>
  • <b>
  • [ '<b>', '</b>', '<b>', '</b>' ]
  • এক

JavaScript-এ “ঢাকায় থাকি”-র মতো বাংলা লেখা থেকে পুরো শব্দগুলো (কার-চিহ্নসহ) বের করতে কোন নকশা সঠিক?

  • /\w+/g
  • /[a-zA-Z]+/g
  • /\p{L}+/gu
  • /\p{Script=Bengali}+/gu

"15/03/2026".replace(/(\d{2})\/(\d{2})\/(\d{4})/, "$3-$2-$1")-এর ফল কী?

  • "$3-$2-$1"
  • "15-03-2026"
  • "2026-03-15"
  • "15/03/2026"

লম্বা ও মিল-না-হওয়া ইনপুটে catastrophic backtracking-এর ঝুঁকি কোন নকশায় সবচেয়ে বেশি?

  • /^a+$/
  • /^(a+)+$/
  • /^\d{4}$/
  • /^\d+-\d+$/

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. regex = লেখার আকৃতির বর্ণনা। হুবহু লেখা খুঁজতে includes; HTML, JSON-এর মতো গঠনওয়ালা ডেটায় parser.
  2. যাচাইয়ে সবসময় ^…$, আর g flag নয় — g-এর lastIndex test()-কে পালা করে true/false করায়।
  3. সব মিল চাইলে matchAll (group আর index-সহ); বদলাতে replace — $1, $<name> বা একটি function দিয়ে।
  4. বাংলায় \w, \b, \d অকেজো: u flag আর \p{Script=Bengali}, বাংলা অঙ্কে [০-৯]; \p{L} কার-চিহ্ন ধরে না।
  5. নেস্টেড quantifier এড়ান, ইনপুটের দৈর্ঘ্য সীমিত করুন, বাইরের লেখা escape করুন — আর প্রতিটি নকশার পাশে পরীক্ষার তালিকা রাখুন।

পরের পর্বে: মডিউল ৫ শুরু — asynchronous JavaScript. একটি thread নিয়েও JavaScript কীভাবে একসাথে টাইমার, নেটওয়ার্ক আর ক্লিক সামলায়? event loop, task আর microtask — আর কেন একটি ধীর regex পুরো পাতা জমিয়ে দিতে পারে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান