[{"data":1,"prerenderedAt":4893},["ShallowReactive",2],{"page-\u002Fbash\u002F10-string-manipulation":3},{"id":4,"title":5,"body":6,"description":4886,"extension":4887,"meta":4888,"navigation":46,"path":4889,"seo":4890,"stem":4891,"__hash__":4892},"content\u002Fbash\u002F10-string-manipulation.md","Bash 10 — String Manipulation Internals: Parameter Expansion & Regex Engine",{"type":7,"value":8,"toc":4870},"minimark",[9,14,19,678,682,1063,1071,1240,1251,1982,1986,2449,2456,2837,2841,3264,3268,3639,3643,4385,4389,4673,4677,4681,4753,4866],[10,11,13],"h1",{"id":12},"_10-string-manipulation-internals-parameter-expansion-regex-engine","10 — String Manipulation Internals: Parameter Expansion & Regex Engine",[15,16,18],"h2",{"id":17},"parameter-expansion-the-complete-operator-table","Parameter Expansion: The Complete Operator Table",[20,21,23],"code-wrapper",{"language":22},"bash",[24,25,29],"pre",{"className":26,"code":27,"language":22,"meta":28,"style":28},"language-bash shiki shiki-themes github-light github-dark","# ── The full parameter expansion arsenal (all in-process, no subprocess) ──\n\nstr=\"hello world\"\n\n# ── Length ──\necho \"${#str}\"            # 11 — string length\n\n# ── Substring: ${var:offset:length} ──\necho \"${str:0:5}\"         # hello — offset 0, 5 chars\necho \"${str:6}\"           # world — offset 6 to end\necho \"${str: -5}\"         # world — last 5 (SPACE before - required!)\necho \"${str:0-5}\"         # world — alternative (0-5 is arithmetic offset, no space needed)\necho \"${str:1:3}\"         # ell — offset 1, 3 chars\n\n# ── Replace: ${var\u002Fpattern\u002Freplacement} (glob pattern, NOT regex) ──\necho \"${str\u002Fworld\u002FWORLD}\"  # hello WORLD — first match only\necho \"${str\u002F\u002Fl\u002FL}\"         # heLLo worLd — ALL matches (\u002F\u002F)\necho \"${str\u002F#hello\u002FHELLO}\" # HELLO world — match at START only (#)\necho \"${str\u002F%world\u002FWORLD}\" # hello WORLD — match at END only (%)\necho \"${str\u002F\u002F[^l]\u002F_}\"     # _____l___l_ — replace non-l with _ ([^l] is glob \"not l\")\n\n# ── Delete (trim prefix\u002Fsuffix): ${var#pattern} (shortest) \u002F ${var##pattern} (longest) ──\npath=\"\u002Fusr\u002Flocal\u002Fbin\u002Fbash\"\necho \"${path#*\u002F}\"         # usr\u002Flocal\u002Fbin\u002Fbash — remove SHORTEST prefix *\u002F\necho \"${path##*\u002F}\"        # bash — remove LONGEST prefix *\u002F (basename)\necho \"${path%\u002F*}\"         # \u002Fusr\u002Flocal\u002Fbin — remove SHORTEST suffix \u002F* (dirname)\necho \"${path%%\u002F*}\"        # (empty) — remove LONGEST suffix \u002F* (removes everything starting with \u002F)\n\n# ── Case conversion (Bash 4+) ──\nstr=\"Hello World\"\necho \"${str^}\"            # Hello World — first char uppercase (already is)\necho \"${str^^}\"           # HELLO WORLD — all uppercase\necho \"${str,}\"            # hello World — first char lowercase\necho \"${str,,}\"           # hello world — all lowercase\necho \"${str~~}\"           # hELLO wORLD — toggle each char\necho \"${str^^[aeiou]}\"   # hEllO wOrld — uppercase only vowels\necho \"${str,,[^aeiou ]}\"  # hEllO wOrld — lowercase only non-vowels (and non-spaces)\n\n# ── Default values ──\nunset var\necho \"${var:-default}\"    # default — if unset or empty (does NOT assign)\necho \"${var-default}\"     # default — if unset only (empty passes through)\necho \"${var:=default}\"   # default — if unset\u002Fempty AND assigns to var\necho \"${var:?msg}\"        # error: msg — if unset\u002Fempty, print msg and EXIT\necho \"${var:+set}\"        # set — if non-empty, else empty\n","",[30,31,32,41,48,63,68,74,95,100,106,131,150,169,188,212,217,223,248,273,298,321,345,350,356,367,384,401,418,435,440,446,456,471,486,501,516,531,546,561,566,572,581,602,617,636,656],"code",{"__ignoreMap":28},[33,34,37],"span",{"class":35,"line":36},"line",1,[33,38,40],{"class":39},"sdCPZ","# ── The full parameter expansion arsenal (all in-process, no subprocess) ──\n",[33,42,44],{"class":35,"line":43},2,[33,45,47],{"emptyLinePlaceholder":46},true,"\n",[33,49,51,55,59],{"class":35,"line":50},3,[33,52,54],{"class":53},"ssxIu","str",[33,56,58],{"class":57},"svdQ7","=",[33,60,62],{"class":61},"sJ6F3","\"hello world\"\n",[33,64,66],{"class":35,"line":65},4,[33,67,47],{"emptyLinePlaceholder":46},[33,69,71],{"class":35,"line":70},5,[33,72,73],{"class":39},"# ── Length ──\n",[33,75,77,81,84,87,89,92],{"class":35,"line":76},6,[33,78,80],{"class":79},"snvgF","echo",[33,82,83],{"class":61}," \"${",[33,85,86],{"class":57},"#",[33,88,54],{"class":53},[33,90,91],{"class":61},"}\"",[33,93,94],{"class":39},"            # 11 — string length\n",[33,96,98],{"class":35,"line":97},7,[33,99,47],{"emptyLinePlaceholder":46},[33,101,103],{"class":35,"line":102},8,[33,104,105],{"class":39},"# ── Substring: ${var:offset:length} ──\n",[33,107,109,111,113,115,118,121,123,126,128],{"class":35,"line":108},9,[33,110,80],{"class":79},[33,112,83],{"class":61},[33,114,54],{"class":53},[33,116,117],{"class":57},":",[33,119,120],{"class":53},"0",[33,122,117],{"class":57},[33,124,125],{"class":53},"5",[33,127,91],{"class":61},[33,129,130],{"class":39},"         # hello — offset 0, 5 chars\n",[33,132,134,136,138,140,142,145,147],{"class":35,"line":133},10,[33,135,80],{"class":79},[33,137,83],{"class":61},[33,139,54],{"class":53},[33,141,117],{"class":57},[33,143,144],{"class":53},"6",[33,146,91],{"class":61},[33,148,149],{"class":39},"           # world — offset 6 to end\n",[33,151,153,155,157,159,161,164,166],{"class":35,"line":152},11,[33,154,80],{"class":79},[33,156,83],{"class":61},[33,158,54],{"class":53},[33,160,117],{"class":57},[33,162,163],{"class":53}," -5",[33,165,91],{"class":61},[33,167,168],{"class":39},"         # world — last 5 (SPACE before - required!)\n",[33,170,172,174,176,178,180,183,185],{"class":35,"line":171},12,[33,173,80],{"class":79},[33,175,83],{"class":61},[33,177,54],{"class":53},[33,179,117],{"class":57},[33,181,182],{"class":53},"0-5",[33,184,91],{"class":61},[33,186,187],{"class":39},"         # world — alternative (0-5 is arithmetic offset, no space needed)\n",[33,189,191,193,195,197,199,202,204,207,209],{"class":35,"line":190},13,[33,192,80],{"class":79},[33,194,83],{"class":61},[33,196,54],{"class":53},[33,198,117],{"class":57},[33,200,201],{"class":53},"1",[33,203,117],{"class":57},[33,205,206],{"class":53},"3",[33,208,91],{"class":61},[33,210,211],{"class":39},"         # ell — offset 1, 3 chars\n",[33,213,215],{"class":35,"line":214},14,[33,216,47],{"emptyLinePlaceholder":46},[33,218,220],{"class":35,"line":219},15,[33,221,222],{"class":39},"# ── Replace: ${var\u002Fpattern\u002Freplacement} (glob pattern, NOT regex) ──\n",[33,224,226,228,230,232,235,238,240,243,245],{"class":35,"line":225},16,[33,227,80],{"class":79},[33,229,83],{"class":61},[33,231,54],{"class":53},[33,233,234],{"class":57},"\u002F",[33,236,237],{"class":53},"world",[33,239,234],{"class":57},[33,241,242],{"class":53},"WORLD",[33,244,91],{"class":61},[33,246,247],{"class":39},"  # hello WORLD — first match only\n",[33,249,251,253,255,257,260,263,265,268,270],{"class":35,"line":250},17,[33,252,80],{"class":79},[33,254,83],{"class":61},[33,256,54],{"class":53},[33,258,259],{"class":57},"\u002F\u002F",[33,261,262],{"class":53},"l",[33,264,234],{"class":57},[33,266,267],{"class":53},"L",[33,269,91],{"class":61},[33,271,272],{"class":39},"         # heLLo worLd — ALL matches (\u002F\u002F)\n",[33,274,276,278,280,282,285,288,290,293,295],{"class":35,"line":275},18,[33,277,80],{"class":79},[33,279,83],{"class":61},[33,281,54],{"class":53},[33,283,284],{"class":57},"\u002F#",[33,286,287],{"class":53},"hello",[33,289,234],{"class":57},[33,291,292],{"class":53},"HELLO",[33,294,91],{"class":61},[33,296,297],{"class":39}," # HELLO world — match at START only (#)\n",[33,299,301,303,305,307,310,312,314,316,318],{"class":35,"line":300},19,[33,302,80],{"class":79},[33,304,83],{"class":61},[33,306,54],{"class":53},[33,308,309],{"class":57},"\u002F%",[33,311,237],{"class":53},[33,313,234],{"class":57},[33,315,242],{"class":53},[33,317,91],{"class":61},[33,319,320],{"class":39}," # hello WORLD — match at END only (%)\n",[33,322,324,326,328,330,332,335,337,340,342],{"class":35,"line":323},20,[33,325,80],{"class":79},[33,327,83],{"class":61},[33,329,54],{"class":53},[33,331,259],{"class":57},[33,333,334],{"class":61},"[^l]",[33,336,234],{"class":57},[33,338,339],{"class":53},"_",[33,341,91],{"class":61},[33,343,344],{"class":39},"     # _____l___l_ — replace non-l with _ ([^l] is glob \"not l\")\n",[33,346,348],{"class":35,"line":347},21,[33,349,47],{"emptyLinePlaceholder":46},[33,351,353],{"class":35,"line":352},22,[33,354,355],{"class":39},"# ── Delete (trim prefix\u002Fsuffix): ${var#pattern} (shortest) \u002F ${var##pattern} (longest) ──\n",[33,357,359,362,364],{"class":35,"line":358},23,[33,360,361],{"class":53},"path",[33,363,58],{"class":57},[33,365,366],{"class":61},"\"\u002Fusr\u002Flocal\u002Fbin\u002Fbash\"\n",[33,368,370,372,374,376,379,381],{"class":35,"line":369},24,[33,371,80],{"class":79},[33,373,83],{"class":61},[33,375,361],{"class":53},[33,377,378],{"class":57},"#*\u002F",[33,380,91],{"class":61},[33,382,383],{"class":39},"         # usr\u002Flocal\u002Fbin\u002Fbash — remove SHORTEST prefix *\u002F\n",[33,385,387,389,391,393,396,398],{"class":35,"line":386},25,[33,388,80],{"class":79},[33,390,83],{"class":61},[33,392,361],{"class":53},[33,394,395],{"class":57},"##*\u002F",[33,397,91],{"class":61},[33,399,400],{"class":39},"        # bash — remove LONGEST prefix *\u002F (basename)\n",[33,402,404,406,408,410,413,415],{"class":35,"line":403},26,[33,405,80],{"class":79},[33,407,83],{"class":61},[33,409,361],{"class":53},[33,411,412],{"class":57},"%\u002F*",[33,414,91],{"class":61},[33,416,417],{"class":39},"         # \u002Fusr\u002Flocal\u002Fbin — remove SHORTEST suffix \u002F* (dirname)\n",[33,419,421,423,425,427,430,432],{"class":35,"line":420},27,[33,422,80],{"class":79},[33,424,83],{"class":61},[33,426,361],{"class":53},[33,428,429],{"class":57},"%%\u002F*",[33,431,91],{"class":61},[33,433,434],{"class":39},"        # (empty) — remove LONGEST suffix \u002F* (removes everything starting with \u002F)\n",[33,436,438],{"class":35,"line":437},28,[33,439,47],{"emptyLinePlaceholder":46},[33,441,443],{"class":35,"line":442},29,[33,444,445],{"class":39},"# ── Case conversion (Bash 4+) ──\n",[33,447,449,451,453],{"class":35,"line":448},30,[33,450,54],{"class":53},[33,452,58],{"class":57},[33,454,455],{"class":61},"\"Hello World\"\n",[33,457,459,461,463,465,468],{"class":35,"line":458},31,[33,460,80],{"class":79},[33,462,83],{"class":61},[33,464,54],{"class":53},[33,466,467],{"class":61},"^}\"",[33,469,470],{"class":39},"            # Hello World — first char uppercase (already is)\n",[33,472,474,476,478,480,483],{"class":35,"line":473},32,[33,475,80],{"class":79},[33,477,83],{"class":61},[33,479,54],{"class":53},[33,481,482],{"class":61},"^^}\"",[33,484,485],{"class":39},"           # HELLO WORLD — all uppercase\n",[33,487,489,491,493,495,498],{"class":35,"line":488},33,[33,490,80],{"class":79},[33,492,83],{"class":61},[33,494,54],{"class":53},[33,496,497],{"class":61},",}\"",[33,499,500],{"class":39},"            # hello World — first char lowercase\n",[33,502,504,506,508,510,513],{"class":35,"line":503},34,[33,505,80],{"class":79},[33,507,83],{"class":61},[33,509,54],{"class":53},[33,511,512],{"class":61},",,}\"",[33,514,515],{"class":39},"           # hello world — all lowercase\n",[33,517,519,521,523,525,528],{"class":35,"line":518},35,[33,520,80],{"class":79},[33,522,83],{"class":61},[33,524,54],{"class":53},[33,526,527],{"class":61},"~~}\"",[33,529,530],{"class":39},"           # hELLO wORLD — toggle each char\n",[33,532,534,536,538,540,543],{"class":35,"line":533},36,[33,535,80],{"class":79},[33,537,83],{"class":61},[33,539,54],{"class":53},[33,541,542],{"class":61},"^^[aeiou]}\"",[33,544,545],{"class":39},"   # hEllO wOrld — uppercase only vowels\n",[33,547,549,551,553,555,558],{"class":35,"line":548},37,[33,550,80],{"class":79},[33,552,83],{"class":61},[33,554,54],{"class":53},[33,556,557],{"class":61},",,[^aeiou ]}\"",[33,559,560],{"class":39},"  # hEllO wOrld — lowercase only non-vowels (and non-spaces)\n",[33,562,564],{"class":35,"line":563},38,[33,565,47],{"emptyLinePlaceholder":46},[33,567,569],{"class":35,"line":568},39,[33,570,571],{"class":39},"# ── Default values ──\n",[33,573,575,578],{"class":35,"line":574},40,[33,576,577],{"class":79},"unset",[33,579,580],{"class":61}," var\n",[33,582,584,586,588,591,594,597,599],{"class":35,"line":583},41,[33,585,80],{"class":79},[33,587,83],{"class":61},[33,589,590],{"class":53},"var",[33,592,593],{"class":57},":-",[33,595,596],{"class":53},"default",[33,598,91],{"class":61},[33,600,601],{"class":39},"    # default — if unset or empty (does NOT assign)\n",[33,603,605,607,609,612,614],{"class":35,"line":604},42,[33,606,80],{"class":79},[33,608,83],{"class":61},[33,610,611],{"class":53},"var-default",[33,613,91],{"class":61},[33,615,616],{"class":39},"     # default — if unset only (empty passes through)\n",[33,618,620,622,624,626,629,631,633],{"class":35,"line":619},43,[33,621,80],{"class":79},[33,623,83],{"class":61},[33,625,590],{"class":53},[33,627,628],{"class":57},":=",[33,630,596],{"class":53},[33,632,91],{"class":61},[33,634,635],{"class":39},"   # default — if unset\u002Fempty AND assigns to var\n",[33,637,639,641,643,645,648,651,653],{"class":35,"line":638},44,[33,640,80],{"class":79},[33,642,83],{"class":61},[33,644,590],{"class":53},[33,646,647],{"class":57},":?",[33,649,650],{"class":53},"msg",[33,652,91],{"class":61},[33,654,655],{"class":39},"        # error: msg — if unset\u002Fempty, print msg and EXIT\n",[33,657,659,661,663,665,667,670,673,675],{"class":35,"line":658},45,[33,660,80],{"class":79},[33,662,83],{"class":61},[33,664,590],{"class":53},[33,666,117],{"class":57},[33,668,669],{"class":61},"+",[33,671,672],{"class":53},"set",[33,674,91],{"class":61},[33,676,677],{"class":39},"        # set — if non-empty, else empty\n",[15,679,681],{"id":680},"path-manipulation-parameter-expansion-vs-external-commands","Path Manipulation: Parameter Expansion vs External Commands",[20,683,684],{"language":22},[24,685,687],{"className":26,"code":686,"language":22,"meta":28,"style":28},"path=\"\u002Fusr\u002Flocal\u002Fbin\u002Fbash.tar.gz\"\n\n# ── Basename (filename) ──\n# ❌ SLOW: forks basename subprocess\nbasename \"$path\"              # bash.tar.gz\n# ✅ FAST: parameter expansion (in-process)\necho \"${path##*\u002F}\"            # bash.tar.gz\n\n# ── Dirname (directory) ──\n# ❌ SLOW: forks dirname subprocess\ndirname \"$path\"               # \u002Fusr\u002Flocal\u002Fbin\n# ✅ FAST: parameter expansion\necho \"${path%\u002F*}\"             # \u002Fusr\u002Flocal\u002Fbin\n\n# ── Extension extraction ──\necho \"${path##*.}\"            # gz — last extension (longest prefix up to .)\necho \"${path#*.}\"             # tar.gz — first extension onward (shortest prefix up to .)\n\n# ── Remove extension ──\necho \"${path%.*}\"             # \u002Fusr\u002Flocal\u002Fbin\u002Fbash.tar — remove LAST extension (shortest suffix from .)\necho \"${path%%.*}\"            # \u002Fusr\u002Flocal\u002Fbin\u002Fbash — remove ALL extensions (longest suffix from .)\n\n# ── Replace extension ──\necho \"${path%.gz}.bz2\"        # \u002Fusr\u002Flocal\u002Fbin\u002Fbash.tar.bz2 — replace .gz with .bz2\n\n# ── Filename without any extension ──\nbase=\"${path##*\u002F}\"            # bash.tar.gz\necho \"${base%%.*}\"            # bash — filename without any extension\n\n# ── The # vs ## (shortest vs longest) distinction ──\nfile=\"archive.tar.gz\"\necho \"${file#*.}\"             # tar.gz — shortest prefix up to first .\necho \"${file##*.}\"            # gz — longest prefix up to last .\n\n# ── The % vs %% (shortest vs longest) distinction ──\necho \"${file%.*}\"             # archive.tar — shortest suffix from last .\necho \"${file%%.*}\"            # archive — longest suffix from first .\n",[30,688,689,698,702,707,712,730,735,750,754,759,764,778,783,798,802,807,824,840,844,849,871,891,895,900,921,925,930,948,967,971,976,986,1001,1016,1020,1025,1044],{"__ignoreMap":28},[33,690,691,693,695],{"class":35,"line":36},[33,692,361],{"class":53},[33,694,58],{"class":57},[33,696,697],{"class":61},"\"\u002Fusr\u002Flocal\u002Fbin\u002Fbash.tar.gz\"\n",[33,699,700],{"class":35,"line":43},[33,701,47],{"emptyLinePlaceholder":46},[33,703,704],{"class":35,"line":50},[33,705,706],{"class":39},"# ── Basename (filename) ──\n",[33,708,709],{"class":35,"line":65},[33,710,711],{"class":39},"# ❌ SLOW: forks basename subprocess\n",[33,713,714,718,721,724,727],{"class":35,"line":70},[33,715,717],{"class":716},"sIsaT","basename",[33,719,720],{"class":61}," \"",[33,722,723],{"class":53},"$path",[33,725,726],{"class":61},"\"",[33,728,729],{"class":39},"              # bash.tar.gz\n",[33,731,732],{"class":35,"line":76},[33,733,734],{"class":39},"# ✅ FAST: parameter expansion (in-process)\n",[33,736,737,739,741,743,745,747],{"class":35,"line":97},[33,738,80],{"class":79},[33,740,83],{"class":61},[33,742,361],{"class":53},[33,744,395],{"class":57},[33,746,91],{"class":61},[33,748,749],{"class":39},"            # bash.tar.gz\n",[33,751,752],{"class":35,"line":102},[33,753,47],{"emptyLinePlaceholder":46},[33,755,756],{"class":35,"line":108},[33,757,758],{"class":39},"# ── Dirname (directory) ──\n",[33,760,761],{"class":35,"line":133},[33,762,763],{"class":39},"# ❌ SLOW: forks dirname subprocess\n",[33,765,766,769,771,773,775],{"class":35,"line":152},[33,767,768],{"class":716},"dirname",[33,770,720],{"class":61},[33,772,723],{"class":53},[33,774,726],{"class":61},[33,776,777],{"class":39},"               # \u002Fusr\u002Flocal\u002Fbin\n",[33,779,780],{"class":35,"line":171},[33,781,782],{"class":39},"# ✅ FAST: parameter expansion\n",[33,784,785,787,789,791,793,795],{"class":35,"line":190},[33,786,80],{"class":79},[33,788,83],{"class":61},[33,790,361],{"class":53},[33,792,412],{"class":57},[33,794,91],{"class":61},[33,796,797],{"class":39},"             # \u002Fusr\u002Flocal\u002Fbin\n",[33,799,800],{"class":35,"line":214},[33,801,47],{"emptyLinePlaceholder":46},[33,803,804],{"class":35,"line":219},[33,805,806],{"class":39},"# ── Extension extraction ──\n",[33,808,809,811,813,815,818,821],{"class":35,"line":225},[33,810,80],{"class":79},[33,812,83],{"class":61},[33,814,361],{"class":53},[33,816,817],{"class":57},"##*",[33,819,820],{"class":61},".}\"",[33,822,823],{"class":39},"            # gz — last extension (longest prefix up to .)\n",[33,825,826,828,830,832,835,837],{"class":35,"line":250},[33,827,80],{"class":79},[33,829,83],{"class":61},[33,831,361],{"class":53},[33,833,834],{"class":57},"#*",[33,836,820],{"class":61},[33,838,839],{"class":39},"             # tar.gz — first extension onward (shortest prefix up to .)\n",[33,841,842],{"class":35,"line":275},[33,843,47],{"emptyLinePlaceholder":46},[33,845,846],{"class":35,"line":300},[33,847,848],{"class":39},"# ── Remove extension ──\n",[33,850,851,853,855,857,860,863,866,868],{"class":35,"line":323},[33,852,80],{"class":79},[33,854,83],{"class":61},[33,856,361],{"class":53},[33,858,859],{"class":57},"%",[33,861,862],{"class":61},".",[33,864,865],{"class":57},"*",[33,867,91],{"class":61},[33,869,870],{"class":39},"             # \u002Fusr\u002Flocal\u002Fbin\u002Fbash.tar — remove LAST extension (shortest suffix from .)\n",[33,872,873,875,877,879,882,884,886,888],{"class":35,"line":347},[33,874,80],{"class":79},[33,876,83],{"class":61},[33,878,361],{"class":53},[33,880,881],{"class":57},"%%",[33,883,862],{"class":61},[33,885,865],{"class":57},[33,887,91],{"class":61},[33,889,890],{"class":39},"            # \u002Fusr\u002Flocal\u002Fbin\u002Fbash — remove ALL extensions (longest suffix from .)\n",[33,892,893],{"class":35,"line":352},[33,894,47],{"emptyLinePlaceholder":46},[33,896,897],{"class":35,"line":358},[33,898,899],{"class":39},"# ── Replace extension ──\n",[33,901,902,904,906,908,910,912,915,918],{"class":35,"line":369},[33,903,80],{"class":79},[33,905,83],{"class":61},[33,907,361],{"class":53},[33,909,859],{"class":57},[33,911,862],{"class":61},[33,913,914],{"class":53},"gz",[33,916,917],{"class":61},"}.bz2\"",[33,919,920],{"class":39},"        # \u002Fusr\u002Flocal\u002Fbin\u002Fbash.tar.bz2 — replace .gz with .bz2\n",[33,922,923],{"class":35,"line":386},[33,924,47],{"emptyLinePlaceholder":46},[33,926,927],{"class":35,"line":403},[33,928,929],{"class":39},"# ── Filename without any extension ──\n",[33,931,932,935,937,940,942,944,946],{"class":35,"line":420},[33,933,934],{"class":53},"base",[33,936,58],{"class":57},[33,938,939],{"class":61},"\"${",[33,941,361],{"class":53},[33,943,395],{"class":57},[33,945,91],{"class":61},[33,947,749],{"class":39},[33,949,950,952,954,956,958,960,962,964],{"class":35,"line":437},[33,951,80],{"class":79},[33,953,83],{"class":61},[33,955,934],{"class":53},[33,957,881],{"class":57},[33,959,862],{"class":61},[33,961,865],{"class":57},[33,963,91],{"class":61},[33,965,966],{"class":39},"            # bash — filename without any extension\n",[33,968,969],{"class":35,"line":442},[33,970,47],{"emptyLinePlaceholder":46},[33,972,973],{"class":35,"line":448},[33,974,975],{"class":39},"# ── The # vs ## (shortest vs longest) distinction ──\n",[33,977,978,981,983],{"class":35,"line":458},[33,979,980],{"class":53},"file",[33,982,58],{"class":57},[33,984,985],{"class":61},"\"archive.tar.gz\"\n",[33,987,988,990,992,994,996,998],{"class":35,"line":473},[33,989,80],{"class":79},[33,991,83],{"class":61},[33,993,980],{"class":53},[33,995,834],{"class":57},[33,997,820],{"class":61},[33,999,1000],{"class":39},"             # tar.gz — shortest prefix up to first .\n",[33,1002,1003,1005,1007,1009,1011,1013],{"class":35,"line":488},[33,1004,80],{"class":79},[33,1006,83],{"class":61},[33,1008,980],{"class":53},[33,1010,817],{"class":57},[33,1012,820],{"class":61},[33,1014,1015],{"class":39},"            # gz — longest prefix up to last .\n",[33,1017,1018],{"class":35,"line":503},[33,1019,47],{"emptyLinePlaceholder":46},[33,1021,1022],{"class":35,"line":518},[33,1023,1024],{"class":39},"# ── The % vs %% (shortest vs longest) distinction ──\n",[33,1026,1027,1029,1031,1033,1035,1037,1039,1041],{"class":35,"line":533},[33,1028,80],{"class":79},[33,1030,83],{"class":61},[33,1032,980],{"class":53},[33,1034,859],{"class":57},[33,1036,862],{"class":61},[33,1038,865],{"class":57},[33,1040,91],{"class":61},[33,1042,1043],{"class":39},"             # archive.tar — shortest suffix from last .\n",[33,1045,1046,1048,1050,1052,1054,1056,1058,1060],{"class":35,"line":548},[33,1047,80],{"class":79},[33,1049,83],{"class":61},[33,1051,980],{"class":53},[33,1053,881],{"class":57},[33,1055,862],{"class":61},[33,1057,865],{"class":57},[33,1059,91],{"class":61},[33,1061,1062],{"class":39},"            # archive — longest suffix from first .\n",[15,1064,1066,1067,1070],{"id":1065},"anti-pattern-using-sed-for-simple-string-ops","Anti-Pattern: Using ",[30,1068,1069],{},"sed"," for Simple String Ops",[20,1072,1073],{"language":22},[24,1074,1076],{"className":26,"code":1075,"language":22,"meta":28,"style":28},"# ❌ SLOW — forks sed subprocess for simple replacement\nresult=$(echo \"$str\" | sed 's\u002Fold\u002Fnew\u002Fg')\n\n# ✅ FAST — parameter expansion (in-process, no subprocess)\nresult=\"${str\u002F\u002Fold\u002Fnew}\"\n\n# Benchmark (100,000 iterations):\n#   sed:   ~12s (100k subprocess forks)\n#   ${\u002F\u002F}: ~0.3s (in-process) — 40x faster\n\n# ── More examples ──\n# ❌ echo \"$str\" | sed 's\u002F^\u002Fprefix: \u002F'\n# ✅ str=\"prefix: $str\"\n\n# ❌ echo \"$str\" | sed 's\u002F  *\u002F \u002Fg'  (collapse spaces)\n# ✅ str=\"${str\u002F\u002F  \u002F }\"  (but this only replaces double spaces once — for full collapse, use a loop or tr)\n\n# ❌ echo \"$str\" | tr 'a-z' 'A-Z'\n# ✅ str=\"${str^^}\"  (Bash 4+)\n\n# ❌ echo \"$str\" | sed 's\u002F^\\([0-9]*\\).*\u002F\\1\u002F'  (extract leading digits)\n# ✅ [[ $str =~ ^([0-9]*) ]]; echo \"${BASH_REMATCH[1]}\"  (regex + capture)\n\n# When to still use sed: complex regex, multi-line, in-place file editing\n# When to use parameter expansion: simple replace, trim, case, substring (single string)\n",[30,1077,1078,1083,1114,1118,1123,1146,1150,1155,1160,1165,1169,1174,1179,1184,1188,1193,1198,1202,1207,1212,1216,1221,1226,1230,1235],{"__ignoreMap":28},[33,1079,1080],{"class":35,"line":36},[33,1081,1082],{"class":39},"# ❌ SLOW — forks sed subprocess for simple replacement\n",[33,1084,1085,1088,1090,1093,1095,1097,1100,1102,1105,1108,1111],{"class":35,"line":43},[33,1086,1087],{"class":53},"result",[33,1089,58],{"class":57},[33,1091,1092],{"class":53},"$(",[33,1094,80],{"class":79},[33,1096,720],{"class":61},[33,1098,1099],{"class":53},"$str",[33,1101,726],{"class":61},[33,1103,1104],{"class":57}," |",[33,1106,1107],{"class":716}," sed",[33,1109,1110],{"class":61}," 's\u002Fold\u002Fnew\u002Fg'",[33,1112,1113],{"class":53},")\n",[33,1115,1116],{"class":35,"line":50},[33,1117,47],{"emptyLinePlaceholder":46},[33,1119,1120],{"class":35,"line":65},[33,1121,1122],{"class":39},"# ✅ FAST — parameter expansion (in-process, no subprocess)\n",[33,1124,1125,1127,1129,1131,1133,1135,1138,1140,1143],{"class":35,"line":70},[33,1126,1087],{"class":53},[33,1128,58],{"class":57},[33,1130,939],{"class":61},[33,1132,54],{"class":53},[33,1134,259],{"class":57},[33,1136,1137],{"class":53},"old",[33,1139,234],{"class":57},[33,1141,1142],{"class":53},"new",[33,1144,1145],{"class":61},"}\"\n",[33,1147,1148],{"class":35,"line":76},[33,1149,47],{"emptyLinePlaceholder":46},[33,1151,1152],{"class":35,"line":97},[33,1153,1154],{"class":39},"# Benchmark (100,000 iterations):\n",[33,1156,1157],{"class":35,"line":102},[33,1158,1159],{"class":39},"#   sed:   ~12s (100k subprocess forks)\n",[33,1161,1162],{"class":35,"line":108},[33,1163,1164],{"class":39},"#   ${\u002F\u002F}: ~0.3s (in-process) — 40x faster\n",[33,1166,1167],{"class":35,"line":133},[33,1168,47],{"emptyLinePlaceholder":46},[33,1170,1171],{"class":35,"line":152},[33,1172,1173],{"class":39},"# ── More examples ──\n",[33,1175,1176],{"class":35,"line":171},[33,1177,1178],{"class":39},"# ❌ echo \"$str\" | sed 's\u002F^\u002Fprefix: \u002F'\n",[33,1180,1181],{"class":35,"line":190},[33,1182,1183],{"class":39},"# ✅ str=\"prefix: $str\"\n",[33,1185,1186],{"class":35,"line":214},[33,1187,47],{"emptyLinePlaceholder":46},[33,1189,1190],{"class":35,"line":219},[33,1191,1192],{"class":39},"# ❌ echo \"$str\" | sed 's\u002F  *\u002F \u002Fg'  (collapse spaces)\n",[33,1194,1195],{"class":35,"line":225},[33,1196,1197],{"class":39},"# ✅ str=\"${str\u002F\u002F  \u002F }\"  (but this only replaces double spaces once — for full collapse, use a loop or tr)\n",[33,1199,1200],{"class":35,"line":250},[33,1201,47],{"emptyLinePlaceholder":46},[33,1203,1204],{"class":35,"line":275},[33,1205,1206],{"class":39},"# ❌ echo \"$str\" | tr 'a-z' 'A-Z'\n",[33,1208,1209],{"class":35,"line":300},[33,1210,1211],{"class":39},"# ✅ str=\"${str^^}\"  (Bash 4+)\n",[33,1213,1214],{"class":35,"line":323},[33,1215,47],{"emptyLinePlaceholder":46},[33,1217,1218],{"class":35,"line":347},[33,1219,1220],{"class":39},"# ❌ echo \"$str\" | sed 's\u002F^\\([0-9]*\\).*\u002F\\1\u002F'  (extract leading digits)\n",[33,1222,1223],{"class":35,"line":352},[33,1224,1225],{"class":39},"# ✅ [[ $str =~ ^([0-9]*) ]]; echo \"${BASH_REMATCH[1]}\"  (regex + capture)\n",[33,1227,1228],{"class":35,"line":358},[33,1229,47],{"emptyLinePlaceholder":46},[33,1231,1232],{"class":35,"line":369},[33,1233,1234],{"class":39},"# When to still use sed: complex regex, multi-line, in-place file editing\n",[33,1236,1237],{"class":35,"line":386},[33,1238,1239],{"class":39},"# When to use parameter expansion: simple replace, trim, case, substring (single string)\n",[15,1241,1243,1244,1247,1248],{"id":1242},"regex-matching-and-bash_rematch","Regex Matching: ",[30,1245,1246],{},"[[ =~ ]]"," and ",[30,1249,1250],{},"BASH_REMATCH",[20,1252,1253],{"language":22},[24,1254,1256],{"className":26,"code":1255,"language":22,"meta":28,"style":28},"# ── [[ $str =~ regex ]] — extended regex (ERE), NOT PCRE ──\n# ERE: . * + ? ^ $ [] [^] () {} | (no \\d, \\w, \\s — use character classes)\n\nstr=\"user@example.com\"\nre='^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'\n\nif [[ $str =~ $re ]]; then\n    echo \"valid email\"\nfi\n# ⚠️ Store regex in a variable and reference it unquoted:\n#   re='...'; [[ $str =~ $re ]]\n# If you inline: [[ $str =~ ^[a-z]+$ ]] — works but hard to read\n# If you quote: [[ $str =~ \"$re\" ]] — becomes a LITERAL string match (not regex!)\n\n# ── Capture groups: BASH_REMATCH array ──\ndate=\"2024-01-15\"\nif [[ $date =~ ^([0-9]{4})-([0-9]{2})-([0-9]{2})$ ]]; then\n    echo \"year:  ${BASH_REMATCH[0]}\"   # 2024-01-15 — full match (group 0)\n    echo \"year:  ${BASH_REMATCH[1]}\"   # 2024 — first capture group\n    echo \"month: ${BASH_REMATCH[2]}\"   # 01 — second capture group\n    echo \"day:   ${BASH_REMATCH[3]}\"   # 15 — third capture group\nfi\n\n# ── ⚠️ BASH_REMATCH is overwritten by each [[ =~ ]] ──\n# Capture immediately after the match:\nif [[ $line =~ ^([0-9]+)\\s+(.*)$ ]]; then\n    num=\"${BASH_REMATCH[1]}\"    # capture NOW — don't reuse after another =~\n    text=\"${BASH_REMATCH[2]}\"\nfi\n\n# ── Regex features available (ERE) ──\n[[ \"abc\" =~ a.c ]]          # true — . matches any char\n[[ \"aaab\" =~ a+b ]]         # true — + one or more\n[[ \"ab\" =~ a?b ]]           # true — ? zero or one\n[[ \"abc\" =~ ^a ]]           # true — ^ start anchor\n[[ \"abc\" =~ c$ ]]           # true — $ end anchor\n[[ \"abc\" =~ [a-z]+ ]]       # true — character class\n[[ \"abc\" =~ [^0-9]+ ]]     # true — negated class\n[[ \"abc\" =~ (a|b) ]]       # true — alternation with grouping\n[[ \"aaa\" =~ a{3} ]]        # true — exact count\n[[ \"aaa\" =~ a{2,4} ]]     # true — range count\n[[ \"aaa\" =~ a{2,} ]]      # true — minimum count\n\n# ── Regex features NOT available (use grep -P or awk for these) ──\n[[ \"123\" =~ \\d+ ]]         # false — \\d is PCRE, not ERE (use [0-9])\n[[ \"abc\" =~ \\w+ ]]         # false — \\w is PCRE (use [a-zA-Z0-9_])\n[[ \"  a\" =~ \\s+a ]]        # false — \\s is PCRE (use [[:space:]] or [ \\t])\n[[ \"abc\" =~ (?=a) ]]       # false — lookaheads are PCRE only\n[[ \"abc\" =~ (?:a) ]]       # false — non-capturing groups are PCRE only\n\n# ── POSIX character classes (work in ERE) ──\n[[ \"123\" =~ [[:digit:]]+ ]]    # true — digits\n[[ \"abc\" =~ [[:alpha:]]+ ]]    # true — letters\n[[ \"a b\" =~ [[:space:]] ]]     # true — whitespace\n[[ \"a1!\" =~ [[:punct:]] ]]    # true — punctuation\n[[ \"abc\" =~ [[:alnum:]]+ ]]   # true — alphanumeric\n[[ \"x\" =~ [[:upper:]] ]]      # false — uppercase only\n[[ \"X\" =~ [[:upper:]] ]]      # true\n",[30,1257,1258,1263,1268,1272,1281,1291,1295,1312,1320,1325,1330,1335,1340,1345,1349,1354,1364,1421,1436,1450,1465,1480,1484,1488,1493,1498,1540,1556,1570,1574,1578,1583,1600,1615,1636,1650,1664,1678,1692,1712,1732,1746,1760,1764,1769,1787,1804,1823,1844,1863,1868,1874,1889,1904,1920,1936,1951,1967],{"__ignoreMap":28},[33,1259,1260],{"class":35,"line":36},[33,1261,1262],{"class":39},"# ── [[ $str =~ regex ]] — extended regex (ERE), NOT PCRE ──\n",[33,1264,1265],{"class":35,"line":43},[33,1266,1267],{"class":39},"# ERE: . * + ? ^ $ [] [^] () {} | (no \\d, \\w, \\s — use character classes)\n",[33,1269,1270],{"class":35,"line":50},[33,1271,47],{"emptyLinePlaceholder":46},[33,1273,1274,1276,1278],{"class":35,"line":65},[33,1275,54],{"class":53},[33,1277,58],{"class":57},[33,1279,1280],{"class":61},"\"user@example.com\"\n",[33,1282,1283,1286,1288],{"class":35,"line":70},[33,1284,1285],{"class":53},"re",[33,1287,58],{"class":57},[33,1289,1290],{"class":61},"'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'\n",[33,1292,1293],{"class":35,"line":76},[33,1294,47],{"emptyLinePlaceholder":46},[33,1296,1297,1300,1303,1306,1309],{"class":35,"line":97},[33,1298,1299],{"class":57},"if",[33,1301,1302],{"class":53}," [[ $str ",[33,1304,1305],{"class":57},"=~",[33,1307,1308],{"class":53}," $re ]]; ",[33,1310,1311],{"class":57},"then\n",[33,1313,1314,1317],{"class":35,"line":102},[33,1315,1316],{"class":79},"    echo",[33,1318,1319],{"class":61}," \"valid email\"\n",[33,1321,1322],{"class":35,"line":108},[33,1323,1324],{"class":57},"fi\n",[33,1326,1327],{"class":35,"line":133},[33,1328,1329],{"class":39},"# ⚠️ Store regex in a variable and reference it unquoted:\n",[33,1331,1332],{"class":35,"line":152},[33,1333,1334],{"class":39},"#   re='...'; [[ $str =~ $re ]]\n",[33,1336,1337],{"class":35,"line":171},[33,1338,1339],{"class":39},"# If you inline: [[ $str =~ ^[a-z]+$ ]] — works but hard to read\n",[33,1341,1342],{"class":35,"line":190},[33,1343,1344],{"class":39},"# If you quote: [[ $str =~ \"$re\" ]] — becomes a LITERAL string match (not regex!)\n",[33,1346,1347],{"class":35,"line":214},[33,1348,47],{"emptyLinePlaceholder":46},[33,1350,1351],{"class":35,"line":219},[33,1352,1353],{"class":39},"# ── Capture groups: BASH_REMATCH array ──\n",[33,1355,1356,1359,1361],{"class":35,"line":225},[33,1357,1358],{"class":53},"date",[33,1360,58],{"class":57},[33,1362,1363],{"class":61},"\"2024-01-15\"\n",[33,1365,1366,1368,1371,1373,1376,1378,1381,1384,1387,1390,1393,1395,1397,1399,1401,1404,1406,1408,1410,1412,1414,1416,1419],{"class":35,"line":250},[33,1367,1299],{"class":57},[33,1369,1370],{"class":53}," [[ $date ",[33,1372,1305],{"class":57},[33,1374,1375],{"class":53}," ^([",[33,1377,120],{"class":79},[33,1379,1380],{"class":57},"-",[33,1382,1383],{"class":79},"9",[33,1385,1386],{"class":53},"]{",[33,1388,1389],{"class":79},"4",[33,1391,1392],{"class":53},"})-([",[33,1394,120],{"class":79},[33,1396,1380],{"class":57},[33,1398,1383],{"class":79},[33,1400,1386],{"class":53},[33,1402,1403],{"class":79},"2",[33,1405,1392],{"class":53},[33,1407,120],{"class":79},[33,1409,1380],{"class":57},[33,1411,1383],{"class":79},[33,1413,1386],{"class":53},[33,1415,1403],{"class":79},[33,1417,1418],{"class":53},"})$ ]]; ",[33,1420,1311],{"class":57},[33,1422,1423,1425,1428,1430,1433],{"class":35,"line":275},[33,1424,1316],{"class":79},[33,1426,1427],{"class":61}," \"year:  ${",[33,1429,1250],{"class":53},[33,1431,1432],{"class":61},"[0]}\"",[33,1434,1435],{"class":39},"   # 2024-01-15 — full match (group 0)\n",[33,1437,1438,1440,1442,1444,1447],{"class":35,"line":300},[33,1439,1316],{"class":79},[33,1441,1427],{"class":61},[33,1443,1250],{"class":53},[33,1445,1446],{"class":61},"[1]}\"",[33,1448,1449],{"class":39},"   # 2024 — first capture group\n",[33,1451,1452,1454,1457,1459,1462],{"class":35,"line":323},[33,1453,1316],{"class":79},[33,1455,1456],{"class":61}," \"month: ${",[33,1458,1250],{"class":53},[33,1460,1461],{"class":61},"[2]}\"",[33,1463,1464],{"class":39},"   # 01 — second capture group\n",[33,1466,1467,1469,1472,1474,1477],{"class":35,"line":347},[33,1468,1316],{"class":79},[33,1470,1471],{"class":61}," \"day:   ${",[33,1473,1250],{"class":53},[33,1475,1476],{"class":61},"[3]}\"",[33,1478,1479],{"class":39},"   # 15 — third capture group\n",[33,1481,1482],{"class":35,"line":352},[33,1483,1324],{"class":57},[33,1485,1486],{"class":35,"line":358},[33,1487,47],{"emptyLinePlaceholder":46},[33,1489,1490],{"class":35,"line":369},[33,1491,1492],{"class":39},"# ── ⚠️ BASH_REMATCH is overwritten by each [[ =~ ]] ──\n",[33,1494,1495],{"class":35,"line":386},[33,1496,1497],{"class":39},"# Capture immediately after the match:\n",[33,1499,1500,1502,1505,1507,1509,1511,1513,1515,1518,1520,1523,1526,1528,1531,1533,1535,1538],{"class":35,"line":403},[33,1501,1299],{"class":57},[33,1503,1504],{"class":53}," [[ $line ",[33,1506,1305],{"class":57},[33,1508,1375],{"class":53},[33,1510,120],{"class":79},[33,1512,1380],{"class":57},[33,1514,1383],{"class":79},[33,1516,1517],{"class":53},"]",[33,1519,669],{"class":57},[33,1521,1522],{"class":53},")",[33,1524,1525],{"class":79},"\\s",[33,1527,669],{"class":57},[33,1529,1530],{"class":53},"(",[33,1532,862],{"class":79},[33,1534,865],{"class":61},[33,1536,1537],{"class":53},")$ ]]; ",[33,1539,1311],{"class":57},[33,1541,1542,1545,1547,1549,1551,1553],{"class":35,"line":420},[33,1543,1544],{"class":53},"    num",[33,1546,58],{"class":57},[33,1548,939],{"class":61},[33,1550,1250],{"class":53},[33,1552,1446],{"class":61},[33,1554,1555],{"class":39},"    # capture NOW — don't reuse after another =~\n",[33,1557,1558,1561,1563,1565,1567],{"class":35,"line":437},[33,1559,1560],{"class":53},"    text",[33,1562,58],{"class":57},[33,1564,939],{"class":61},[33,1566,1250],{"class":53},[33,1568,1569],{"class":61},"[2]}\"\n",[33,1571,1572],{"class":35,"line":442},[33,1573,1324],{"class":57},[33,1575,1576],{"class":35,"line":448},[33,1577,47],{"emptyLinePlaceholder":46},[33,1579,1580],{"class":35,"line":458},[33,1581,1582],{"class":39},"# ── Regex features available (ERE) ──\n",[33,1584,1585,1588,1591,1594,1597],{"class":35,"line":473},[33,1586,1587],{"class":53},"[[ ",[33,1589,1590],{"class":61},"\"abc\"",[33,1592,1593],{"class":57}," =~",[33,1595,1596],{"class":53}," a.c ]]          ",[33,1598,1599],{"class":39},"# true — . matches any char\n",[33,1601,1602,1604,1607,1609,1612],{"class":35,"line":488},[33,1603,1587],{"class":53},[33,1605,1606],{"class":61},"\"aaab\"",[33,1608,1593],{"class":57},[33,1610,1611],{"class":53}," a+b ]]         ",[33,1613,1614],{"class":39},"# true — + one or more\n",[33,1616,1617,1619,1622,1624,1627,1630,1633],{"class":35,"line":503},[33,1618,1587],{"class":53},[33,1620,1621],{"class":61},"\"ab\"",[33,1623,1593],{"class":57},[33,1625,1626],{"class":53}," a",[33,1628,1629],{"class":57},"?",[33,1631,1632],{"class":53},"b ]]           ",[33,1634,1635],{"class":39},"# true — ? zero or one\n",[33,1637,1638,1640,1642,1644,1647],{"class":35,"line":518},[33,1639,1587],{"class":53},[33,1641,1590],{"class":61},[33,1643,1593],{"class":57},[33,1645,1646],{"class":53}," ^a ]]           ",[33,1648,1649],{"class":39},"# true — ^ start anchor\n",[33,1651,1652,1654,1656,1658,1661],{"class":35,"line":533},[33,1653,1587],{"class":53},[33,1655,1590],{"class":61},[33,1657,1593],{"class":57},[33,1659,1660],{"class":53}," c$ ]]           ",[33,1662,1663],{"class":39},"# true — $ end anchor\n",[33,1665,1666,1668,1670,1672,1675],{"class":35,"line":548},[33,1667,1587],{"class":53},[33,1669,1590],{"class":61},[33,1671,1593],{"class":57},[33,1673,1674],{"class":53}," [a-z]+ ]]       ",[33,1676,1677],{"class":39},"# true — character class\n",[33,1679,1680,1682,1684,1686,1689],{"class":35,"line":563},[33,1681,1587],{"class":53},[33,1683,1590],{"class":61},[33,1685,1593],{"class":57},[33,1687,1688],{"class":53}," [^0-9]+ ]]     ",[33,1690,1691],{"class":39},"# true — negated class\n",[33,1693,1694,1696,1698,1700,1703,1706,1709],{"class":35,"line":568},[33,1695,1587],{"class":53},[33,1697,1590],{"class":61},[33,1699,1593],{"class":57},[33,1701,1702],{"class":53}," (a",[33,1704,1705],{"class":57},"|",[33,1707,1708],{"class":53},"b) ]]       ",[33,1710,1711],{"class":39},"# true — alternation with grouping\n",[33,1713,1714,1716,1719,1721,1724,1726,1729],{"class":35,"line":574},[33,1715,1587],{"class":53},[33,1717,1718],{"class":61},"\"aaa\"",[33,1720,1593],{"class":57},[33,1722,1723],{"class":53}," a{",[33,1725,206],{"class":79},[33,1727,1728],{"class":53},"} ]]        ",[33,1730,1731],{"class":39},"# true — exact count\n",[33,1733,1734,1736,1738,1740,1743],{"class":35,"line":583},[33,1735,1587],{"class":53},[33,1737,1718],{"class":61},[33,1739,1593],{"class":57},[33,1741,1742],{"class":53}," a{2,4} ]]     ",[33,1744,1745],{"class":39},"# true — range count\n",[33,1747,1748,1750,1752,1754,1757],{"class":35,"line":604},[33,1749,1587],{"class":53},[33,1751,1718],{"class":61},[33,1753,1593],{"class":57},[33,1755,1756],{"class":53}," a{2,} ]]      ",[33,1758,1759],{"class":39},"# true — minimum count\n",[33,1761,1762],{"class":35,"line":619},[33,1763,47],{"emptyLinePlaceholder":46},[33,1765,1766],{"class":35,"line":638},[33,1767,1768],{"class":39},"# ── Regex features NOT available (use grep -P or awk for these) ──\n",[33,1770,1771,1773,1776,1778,1781,1784],{"class":35,"line":658},[33,1772,1587],{"class":53},[33,1774,1775],{"class":61},"\"123\"",[33,1777,1593],{"class":57},[33,1779,1780],{"class":79}," \\d",[33,1782,1783],{"class":53},"+ ]]         ",[33,1785,1786],{"class":39},"# false — \\d is PCRE, not ERE (use [0-9])\n",[33,1788,1790,1792,1794,1796,1799,1801],{"class":35,"line":1789},46,[33,1791,1587],{"class":53},[33,1793,1590],{"class":61},[33,1795,1593],{"class":57},[33,1797,1798],{"class":79}," \\w",[33,1800,1783],{"class":53},[33,1802,1803],{"class":39},"# false — \\w is PCRE (use [a-zA-Z0-9_])\n",[33,1805,1807,1809,1812,1814,1817,1820],{"class":35,"line":1806},47,[33,1808,1587],{"class":53},[33,1810,1811],{"class":61},"\"  a\"",[33,1813,1593],{"class":57},[33,1815,1816],{"class":79}," \\s",[33,1818,1819],{"class":53},"+a ]]        ",[33,1821,1822],{"class":39},"# false — \\s is PCRE (use [[:space:]] or [ \\t])\n",[33,1824,1826,1828,1830,1832,1835,1838,1841],{"class":35,"line":1825},48,[33,1827,1587],{"class":53},[33,1829,1590],{"class":61},[33,1831,1593],{"class":57},[33,1833,1834],{"class":53}," (",[33,1836,1837],{"class":57},"?=",[33,1839,1840],{"class":53},"a) ]]       ",[33,1842,1843],{"class":39},"# false — lookaheads are PCRE only\n",[33,1845,1847,1849,1851,1853,1855,1858,1860],{"class":35,"line":1846},49,[33,1848,1587],{"class":53},[33,1850,1590],{"class":61},[33,1852,1593],{"class":57},[33,1854,1834],{"class":53},[33,1856,1857],{"class":57},"?:",[33,1859,1840],{"class":53},[33,1861,1862],{"class":39},"# false — non-capturing groups are PCRE only\n",[33,1864,1866],{"class":35,"line":1865},50,[33,1867,47],{"emptyLinePlaceholder":46},[33,1869,1871],{"class":35,"line":1870},51,[33,1872,1873],{"class":39},"# ── POSIX character classes (work in ERE) ──\n",[33,1875,1877,1879,1881,1883,1886],{"class":35,"line":1876},52,[33,1878,1587],{"class":53},[33,1880,1775],{"class":61},[33,1882,1593],{"class":57},[33,1884,1885],{"class":53}," [[:digit:]]+ ]]    ",[33,1887,1888],{"class":39},"# true — digits\n",[33,1890,1892,1894,1896,1898,1901],{"class":35,"line":1891},53,[33,1893,1587],{"class":53},[33,1895,1590],{"class":61},[33,1897,1593],{"class":57},[33,1899,1900],{"class":53}," [[:alpha:]]+ ]]    ",[33,1902,1903],{"class":39},"# true — letters\n",[33,1905,1907,1909,1912,1914,1917],{"class":35,"line":1906},54,[33,1908,1587],{"class":53},[33,1910,1911],{"class":61},"\"a b\"",[33,1913,1593],{"class":57},[33,1915,1916],{"class":53}," [[:space:]] ]]     ",[33,1918,1919],{"class":39},"# true — whitespace\n",[33,1921,1923,1925,1928,1930,1933],{"class":35,"line":1922},55,[33,1924,1587],{"class":53},[33,1926,1927],{"class":61},"\"a1!\"",[33,1929,1593],{"class":57},[33,1931,1932],{"class":53}," [[:punct:]] ]]    ",[33,1934,1935],{"class":39},"# true — punctuation\n",[33,1937,1939,1941,1943,1945,1948],{"class":35,"line":1938},56,[33,1940,1587],{"class":53},[33,1942,1590],{"class":61},[33,1944,1593],{"class":57},[33,1946,1947],{"class":53}," [[:alnum:]]+ ]]   ",[33,1949,1950],{"class":39},"# true — alphanumeric\n",[33,1952,1954,1956,1959,1961,1964],{"class":35,"line":1953},57,[33,1955,1587],{"class":53},[33,1957,1958],{"class":61},"\"x\"",[33,1960,1593],{"class":57},[33,1962,1963],{"class":53}," [[:upper:]] ]]      ",[33,1965,1966],{"class":39},"# false — uppercase only\n",[33,1968,1970,1972,1975,1977,1979],{"class":35,"line":1969},58,[33,1971,1587],{"class":53},[33,1973,1974],{"class":61},"\"X\"",[33,1976,1593],{"class":57},[33,1978,1963],{"class":53},[33,1980,1981],{"class":39},"# true\n",[15,1983,1985],{"id":1984},"production-pattern-url-parser","Production Pattern: URL Parser",[20,1987,1988],{"language":22},[24,1989,1991],{"className":26,"code":1990,"language":22,"meta":28,"style":28},"#!\u002Fusr\u002Fbin\u002Fenv bash\n# ── Parse a URL into components using regex ──\n\nparse_url() {\n    local url=$1\n    local -n _result=$2\n\n    # RFC 3986 simplified: scheme:\u002F\u002F[user[:pass]@]host[:port]\u002Fpath[?query][#fragment]\n    local re='^([a-zA-Z][a-zA-Z0-9+.-]*):\u002F\u002F'   # scheme\n    re+='([^@:\u002F]+(:[^@\u002F:]+)?@)?'               # userinfo (optional)\n    re+='([^:\u002F?#]+)'                            # host\n    re+='(:([0-9]+))?'                          # port (optional)\n    re+='(\u002F[^?#]*)?'                            # path (optional)\n    re+='(\\?([^#]*))?'                          # query (optional)\n    re+='(#(.*))?'                             # fragment (optional)\n    re+='$'\n\n    if [[ $url =~ $re ]]; then\n        _result[scheme]=\"${BASH_REMATCH[1]}\"\n        _result[userinfo]=\"${BASH_REMATCH[3]:-}\"\n        _result[host]=\"${BASH_REMATCH[6]}\"\n        _result[port]=\"${BASH_REMATCH[8]:-}\"\n        _result[path]=\"${BASH_REMATCH[9]:-\u002F}\"\n        _result[query]=\"${BASH_REMATCH[11]:-}\"\n        _result[fragment]=\"${BASH_REMATCH[13]:-}\"\n        return 0\n    else\n        return 1\n    fi\n}\n\n# ── Usage ──\ndeclare -A parts\nparse_url \"https:\u002F\u002Fuser:pass@example.com:8080\u002Fapi\u002Fv1?foo=bar#section\" parts\n\nfor key in scheme host port path query fragment; do\n    printf '%-10s %s\\n' \"$key\" \"${parts[$key]}\"\ndone\n# scheme     https\n# host       example.com\n# port       8080\n# path       \u002Fapi\u002Fv1\n# query      foo=bar\n# fragment   section\n",[30,1992,1993,1998,2003,2007,2015,2029,2044,2048,2053,2068,2082,2094,2106,2118,2130,2142,2151,2155,2169,2183,2201,2215,2233,2252,2270,2288,2296,2301,2308,2313,2318,2322,2327,2338,2347,2351,2386,2414,2419,2424,2429,2434,2439,2444],{"__ignoreMap":28},[33,1994,1995],{"class":35,"line":36},[33,1996,1997],{"class":39},"#!\u002Fusr\u002Fbin\u002Fenv bash\n",[33,1999,2000],{"class":35,"line":43},[33,2001,2002],{"class":39},"# ── Parse a URL into components using regex ──\n",[33,2004,2005],{"class":35,"line":50},[33,2006,47],{"emptyLinePlaceholder":46},[33,2008,2009,2012],{"class":35,"line":65},[33,2010,2011],{"class":716},"parse_url",[33,2013,2014],{"class":53},"() {\n",[33,2016,2017,2020,2023,2025],{"class":35,"line":70},[33,2018,2019],{"class":57},"    local",[33,2021,2022],{"class":53}," url",[33,2024,58],{"class":57},[33,2026,2028],{"class":2027},"sCrzJ","$1\n",[33,2030,2031,2033,2036,2039,2041],{"class":35,"line":76},[33,2032,2019],{"class":57},[33,2034,2035],{"class":79}," -n",[33,2037,2038],{"class":53}," _result",[33,2040,58],{"class":57},[33,2042,2043],{"class":2027},"$2\n",[33,2045,2046],{"class":35,"line":97},[33,2047,47],{"emptyLinePlaceholder":46},[33,2049,2050],{"class":35,"line":102},[33,2051,2052],{"class":39},"    # RFC 3986 simplified: scheme:\u002F\u002F[user[:pass]@]host[:port]\u002Fpath[?query][#fragment]\n",[33,2054,2055,2057,2060,2062,2065],{"class":35,"line":108},[33,2056,2019],{"class":57},[33,2058,2059],{"class":53}," re",[33,2061,58],{"class":57},[33,2063,2064],{"class":61},"'^([a-zA-Z][a-zA-Z0-9+.-]*):\u002F\u002F'",[33,2066,2067],{"class":39},"   # scheme\n",[33,2069,2070,2073,2076,2079],{"class":35,"line":133},[33,2071,2072],{"class":53},"    re",[33,2074,2075],{"class":57},"+=",[33,2077,2078],{"class":61},"'([^@:\u002F]+(:[^@\u002F:]+)?@)?'",[33,2080,2081],{"class":39},"               # userinfo (optional)\n",[33,2083,2084,2086,2088,2091],{"class":35,"line":152},[33,2085,2072],{"class":53},[33,2087,2075],{"class":57},[33,2089,2090],{"class":61},"'([^:\u002F?#]+)'",[33,2092,2093],{"class":39},"                            # host\n",[33,2095,2096,2098,2100,2103],{"class":35,"line":171},[33,2097,2072],{"class":53},[33,2099,2075],{"class":57},[33,2101,2102],{"class":61},"'(:([0-9]+))?'",[33,2104,2105],{"class":39},"                          # port (optional)\n",[33,2107,2108,2110,2112,2115],{"class":35,"line":190},[33,2109,2072],{"class":53},[33,2111,2075],{"class":57},[33,2113,2114],{"class":61},"'(\u002F[^?#]*)?'",[33,2116,2117],{"class":39},"                            # path (optional)\n",[33,2119,2120,2122,2124,2127],{"class":35,"line":214},[33,2121,2072],{"class":53},[33,2123,2075],{"class":57},[33,2125,2126],{"class":61},"'(\\?([^#]*))?'",[33,2128,2129],{"class":39},"                          # query (optional)\n",[33,2131,2132,2134,2136,2139],{"class":35,"line":219},[33,2133,2072],{"class":53},[33,2135,2075],{"class":57},[33,2137,2138],{"class":61},"'(#(.*))?'",[33,2140,2141],{"class":39},"                             # fragment (optional)\n",[33,2143,2144,2146,2148],{"class":35,"line":225},[33,2145,2072],{"class":53},[33,2147,2075],{"class":57},[33,2149,2150],{"class":61},"'$'\n",[33,2152,2153],{"class":35,"line":250},[33,2154,47],{"emptyLinePlaceholder":46},[33,2156,2157,2160,2163,2165,2167],{"class":35,"line":275},[33,2158,2159],{"class":57},"    if",[33,2161,2162],{"class":53}," [[ $url ",[33,2164,1305],{"class":57},[33,2166,1308],{"class":53},[33,2168,1311],{"class":57},[33,2170,2171,2174,2176,2178,2180],{"class":35,"line":300},[33,2172,2173],{"class":53},"        _result[scheme]",[33,2175,58],{"class":57},[33,2177,939],{"class":61},[33,2179,1250],{"class":53},[33,2181,2182],{"class":61},"[1]}\"\n",[33,2184,2185,2188,2190,2192,2194,2197,2199],{"class":35,"line":323},[33,2186,2187],{"class":53},"        _result[userinfo]",[33,2189,58],{"class":57},[33,2191,939],{"class":61},[33,2193,1250],{"class":53},[33,2195,2196],{"class":61},"[3]",[33,2198,593],{"class":57},[33,2200,1145],{"class":61},[33,2202,2203,2206,2208,2210,2212],{"class":35,"line":347},[33,2204,2205],{"class":53},"        _result[host]",[33,2207,58],{"class":57},[33,2209,939],{"class":61},[33,2211,1250],{"class":53},[33,2213,2214],{"class":61},"[6]}\"\n",[33,2216,2217,2220,2222,2224,2226,2229,2231],{"class":35,"line":352},[33,2218,2219],{"class":53},"        _result[port]",[33,2221,58],{"class":57},[33,2223,939],{"class":61},[33,2225,1250],{"class":53},[33,2227,2228],{"class":61},"[8]",[33,2230,593],{"class":57},[33,2232,1145],{"class":61},[33,2234,2235,2238,2240,2242,2244,2247,2250],{"class":35,"line":358},[33,2236,2237],{"class":53},"        _result[path]",[33,2239,58],{"class":57},[33,2241,939],{"class":61},[33,2243,1250],{"class":53},[33,2245,2246],{"class":61},"[9]",[33,2248,2249],{"class":57},":-\u002F",[33,2251,1145],{"class":61},[33,2253,2254,2257,2259,2261,2263,2266,2268],{"class":35,"line":369},[33,2255,2256],{"class":53},"        _result[query]",[33,2258,58],{"class":57},[33,2260,939],{"class":61},[33,2262,1250],{"class":53},[33,2264,2265],{"class":61},"[11]",[33,2267,593],{"class":57},[33,2269,1145],{"class":61},[33,2271,2272,2275,2277,2279,2281,2284,2286],{"class":35,"line":386},[33,2273,2274],{"class":53},"        _result[fragment]",[33,2276,58],{"class":57},[33,2278,939],{"class":61},[33,2280,1250],{"class":53},[33,2282,2283],{"class":61},"[13]",[33,2285,593],{"class":57},[33,2287,1145],{"class":61},[33,2289,2290,2293],{"class":35,"line":403},[33,2291,2292],{"class":57},"        return",[33,2294,2295],{"class":79}," 0\n",[33,2297,2298],{"class":35,"line":420},[33,2299,2300],{"class":57},"    else\n",[33,2302,2303,2305],{"class":35,"line":437},[33,2304,2292],{"class":57},[33,2306,2307],{"class":79}," 1\n",[33,2309,2310],{"class":35,"line":442},[33,2311,2312],{"class":57},"    fi\n",[33,2314,2315],{"class":35,"line":448},[33,2316,2317],{"class":53},"}\n",[33,2319,2320],{"class":35,"line":458},[33,2321,47],{"emptyLinePlaceholder":46},[33,2323,2324],{"class":35,"line":473},[33,2325,2326],{"class":39},"# ── Usage ──\n",[33,2328,2329,2332,2335],{"class":35,"line":488},[33,2330,2331],{"class":57},"declare",[33,2333,2334],{"class":79}," -A",[33,2336,2337],{"class":53}," parts\n",[33,2339,2340,2342,2345],{"class":35,"line":503},[33,2341,2011],{"class":716},[33,2343,2344],{"class":61}," \"https:\u002F\u002Fuser:pass@example.com:8080\u002Fapi\u002Fv1?foo=bar#section\"",[33,2346,2337],{"class":61},[33,2348,2349],{"class":35,"line":518},[33,2350,47],{"emptyLinePlaceholder":46},[33,2352,2353,2356,2359,2362,2365,2368,2371,2374,2377,2380,2383],{"class":35,"line":533},[33,2354,2355],{"class":57},"for",[33,2357,2358],{"class":53}," key ",[33,2360,2361],{"class":57},"in",[33,2363,2364],{"class":61}," scheme",[33,2366,2367],{"class":61}," host",[33,2369,2370],{"class":61}," port",[33,2372,2373],{"class":61}," path",[33,2375,2376],{"class":61}," query",[33,2378,2379],{"class":61}," fragment",[33,2381,2382],{"class":53},"; ",[33,2384,2385],{"class":57},"do\n",[33,2387,2388,2391,2394,2396,2399,2401,2403,2406,2409,2411],{"class":35,"line":548},[33,2389,2390],{"class":79},"    printf",[33,2392,2393],{"class":61}," '%-10s %s\\n'",[33,2395,720],{"class":61},[33,2397,2398],{"class":53},"$key",[33,2400,726],{"class":61},[33,2402,83],{"class":61},[33,2404,2405],{"class":53},"parts",[33,2407,2408],{"class":61},"[",[33,2410,2398],{"class":53},[33,2412,2413],{"class":61},"]}\"\n",[33,2415,2416],{"class":35,"line":563},[33,2417,2418],{"class":57},"done\n",[33,2420,2421],{"class":35,"line":568},[33,2422,2423],{"class":39},"# scheme     https\n",[33,2425,2426],{"class":35,"line":574},[33,2427,2428],{"class":39},"# host       example.com\n",[33,2430,2431],{"class":35,"line":583},[33,2432,2433],{"class":39},"# port       8080\n",[33,2435,2436],{"class":35,"line":604},[33,2437,2438],{"class":39},"# path       \u002Fapi\u002Fv1\n",[33,2440,2441],{"class":35,"line":619},[33,2442,2443],{"class":39},"# query      foo=bar\n",[33,2445,2446],{"class":35,"line":638},[33,2447,2448],{"class":39},"# fragment   section\n",[15,2450,2452,2455],{"id":2451},"printf-formatting-engine",[30,2453,2454],{},"printf",": Formatting Engine",[20,2457,2458],{"language":22},[24,2459,2461],{"className":26,"code":2460,"language":22,"meta":28,"style":28},"# ── printf is C's printf — format specifiers ──\nprintf '%s\\n' \"hello\"               # hello (string + newline)\nprintf '%s is %d\\n' \"Alice\" 30      # Alice is 30 (string + integer)\nprintf '%-20s %5d\\n' \"left\" 42      # \"left                42\" (left-align 20, right-align 5)\nprintf '%.2f\\n' 3.14159             # 3.14 (2 decimal places)\nprintf '%x\\n' 255                    # ff (hexadecimal)\nprintf '%o\\n' 8                     # 10 (octal)\nprintf '%05d\\n' 42                  # 00042 (zero-pad to 5)\nprintf '%5d\\n' 42                   #    42 (space-pad to 5)\nprintf '%-5d|\\n' 42                 # 42   | (left-align)\nprintf '%b\\n' 'tab\\there'            # tab    here (interpret backslash escapes)\nprintf '%q\\n' 'hello world'          # hello\\ world (shell-quoted — safe for re-eval)\n\n# ── printf -v: write to a variable (NO subprocess!) ──\nprintf -v padded '%05d' 42           # padded=\"00042\" — no $(printf ...) fork\nprintf -v json '{\"name\":\"%s\",\"age\":%d}' \"Alice\" 30  # json='{\"name\":\"Alice\",\"age\":30}'\nprintf -v header 'Host: %s\\r\\nUser-Agent: %s\\r\\n' \"example.com\" \"myapp\u002F1.0\"\n# header=\"Host: example.com\\r\\nUser-Agent: myapp\u002F1.0\\r\\n\"\n\n# ── printf reuses the format string for extra args ──\nprintf '%s\\n' a b c                  # a\\n b\\n c\\n (format reused for each arg)\n# This is how you print each element of an array on its own line:\narr=(\"one\" \"two\" \"three\")\nprintf '%s\\n' \"${arr[@]}\"            # one\\ntwo\\nthree\\n\n\n# ── printf doesn't add a trailing newline by default ──\nprintf '%s' \"no newline\"             # no newline (no \\n — same line)\nprintf '%s\\n' \"with newline\"         # with newline\\n\n\n# ── ⚠️ printf with a format string containing % in the data ──\n# ❌ Dangerous: if the data contains %, printf interprets it as a format specifier\nprintf \"$user_input\"     # if user_input is \"%s %s\" — reads from args\u002Fstack!\n# ✅ Safe: always use a format string\nprintf '%s' \"$user_input\"  # data is the ARG, format is '%s' — safe\n",[30,2462,2463,2468,2481,2497,2513,2526,2539,2552,2564,2576,2588,2601,2614,2618,2623,2641,2660,2678,2683,2687,2692,2709,2714,2734,2755,2759,2764,2777,2789,2793,2798,2803,2817,2822],{"__ignoreMap":28},[33,2464,2465],{"class":35,"line":36},[33,2466,2467],{"class":39},"# ── printf is C's printf — format specifiers ──\n",[33,2469,2470,2472,2475,2478],{"class":35,"line":43},[33,2471,2454],{"class":79},[33,2473,2474],{"class":61}," '%s\\n'",[33,2476,2477],{"class":61}," \"hello\"",[33,2479,2480],{"class":39},"               # hello (string + newline)\n",[33,2482,2483,2485,2488,2491,2494],{"class":35,"line":50},[33,2484,2454],{"class":79},[33,2486,2487],{"class":61}," '%s is %d\\n'",[33,2489,2490],{"class":61}," \"Alice\"",[33,2492,2493],{"class":79}," 30",[33,2495,2496],{"class":39},"      # Alice is 30 (string + integer)\n",[33,2498,2499,2501,2504,2507,2510],{"class":35,"line":65},[33,2500,2454],{"class":79},[33,2502,2503],{"class":61}," '%-20s %5d\\n'",[33,2505,2506],{"class":61}," \"left\"",[33,2508,2509],{"class":79}," 42",[33,2511,2512],{"class":39},"      # \"left                42\" (left-align 20, right-align 5)\n",[33,2514,2515,2517,2520,2523],{"class":35,"line":70},[33,2516,2454],{"class":79},[33,2518,2519],{"class":61}," '%.2f\\n'",[33,2521,2522],{"class":79}," 3.14159",[33,2524,2525],{"class":39},"             # 3.14 (2 decimal places)\n",[33,2527,2528,2530,2533,2536],{"class":35,"line":76},[33,2529,2454],{"class":79},[33,2531,2532],{"class":61}," '%x\\n'",[33,2534,2535],{"class":79}," 255",[33,2537,2538],{"class":39},"                    # ff (hexadecimal)\n",[33,2540,2541,2543,2546,2549],{"class":35,"line":97},[33,2542,2454],{"class":79},[33,2544,2545],{"class":61}," '%o\\n'",[33,2547,2548],{"class":79}," 8",[33,2550,2551],{"class":39},"                     # 10 (octal)\n",[33,2553,2554,2556,2559,2561],{"class":35,"line":102},[33,2555,2454],{"class":79},[33,2557,2558],{"class":61}," '%05d\\n'",[33,2560,2509],{"class":79},[33,2562,2563],{"class":39},"                  # 00042 (zero-pad to 5)\n",[33,2565,2566,2568,2571,2573],{"class":35,"line":108},[33,2567,2454],{"class":79},[33,2569,2570],{"class":61}," '%5d\\n'",[33,2572,2509],{"class":79},[33,2574,2575],{"class":39},"                   #    42 (space-pad to 5)\n",[33,2577,2578,2580,2583,2585],{"class":35,"line":133},[33,2579,2454],{"class":79},[33,2581,2582],{"class":61}," '%-5d|\\n'",[33,2584,2509],{"class":79},[33,2586,2587],{"class":39},"                 # 42   | (left-align)\n",[33,2589,2590,2592,2595,2598],{"class":35,"line":152},[33,2591,2454],{"class":79},[33,2593,2594],{"class":61}," '%b\\n'",[33,2596,2597],{"class":61}," 'tab\\there'",[33,2599,2600],{"class":39},"            # tab    here (interpret backslash escapes)\n",[33,2602,2603,2605,2608,2611],{"class":35,"line":171},[33,2604,2454],{"class":79},[33,2606,2607],{"class":61}," '%q\\n'",[33,2609,2610],{"class":61}," 'hello world'",[33,2612,2613],{"class":39},"          # hello\\ world (shell-quoted — safe for re-eval)\n",[33,2615,2616],{"class":35,"line":190},[33,2617,47],{"emptyLinePlaceholder":46},[33,2619,2620],{"class":35,"line":214},[33,2621,2622],{"class":39},"# ── printf -v: write to a variable (NO subprocess!) ──\n",[33,2624,2625,2627,2630,2633,2636,2638],{"class":35,"line":219},[33,2626,2454],{"class":79},[33,2628,2629],{"class":79}," -v",[33,2631,2632],{"class":61}," padded",[33,2634,2635],{"class":61}," '%05d'",[33,2637,2509],{"class":79},[33,2639,2640],{"class":39},"           # padded=\"00042\" — no $(printf ...) fork\n",[33,2642,2643,2645,2647,2650,2653,2655,2657],{"class":35,"line":225},[33,2644,2454],{"class":79},[33,2646,2629],{"class":79},[33,2648,2649],{"class":61}," json",[33,2651,2652],{"class":61}," '{\"name\":\"%s\",\"age\":%d}'",[33,2654,2490],{"class":61},[33,2656,2493],{"class":79},[33,2658,2659],{"class":39},"  # json='{\"name\":\"Alice\",\"age\":30}'\n",[33,2661,2662,2664,2666,2669,2672,2675],{"class":35,"line":250},[33,2663,2454],{"class":79},[33,2665,2629],{"class":79},[33,2667,2668],{"class":61}," header",[33,2670,2671],{"class":61}," 'Host: %s\\r\\nUser-Agent: %s\\r\\n'",[33,2673,2674],{"class":61}," \"example.com\"",[33,2676,2677],{"class":61}," \"myapp\u002F1.0\"\n",[33,2679,2680],{"class":35,"line":275},[33,2681,2682],{"class":39},"# header=\"Host: example.com\\r\\nUser-Agent: myapp\u002F1.0\\r\\n\"\n",[33,2684,2685],{"class":35,"line":300},[33,2686,47],{"emptyLinePlaceholder":46},[33,2688,2689],{"class":35,"line":323},[33,2690,2691],{"class":39},"# ── printf reuses the format string for extra args ──\n",[33,2693,2694,2696,2698,2700,2703,2706],{"class":35,"line":347},[33,2695,2454],{"class":79},[33,2697,2474],{"class":61},[33,2699,1626],{"class":61},[33,2701,2702],{"class":61}," b",[33,2704,2705],{"class":61}," c",[33,2707,2708],{"class":39},"                  # a\\n b\\n c\\n (format reused for each arg)\n",[33,2710,2711],{"class":35,"line":352},[33,2712,2713],{"class":39},"# This is how you print each element of an array on its own line:\n",[33,2715,2716,2719,2721,2723,2726,2729,2732],{"class":35,"line":358},[33,2717,2718],{"class":53},"arr",[33,2720,58],{"class":57},[33,2722,1530],{"class":53},[33,2724,2725],{"class":61},"\"one\"",[33,2727,2728],{"class":61}," \"two\"",[33,2730,2731],{"class":61}," \"three\"",[33,2733,1113],{"class":53},[33,2735,2736,2738,2740,2742,2744,2746,2749,2752],{"class":35,"line":369},[33,2737,2454],{"class":79},[33,2739,2474],{"class":61},[33,2741,83],{"class":61},[33,2743,2718],{"class":53},[33,2745,2408],{"class":61},[33,2747,2748],{"class":57},"@",[33,2750,2751],{"class":61},"]}\"",[33,2753,2754],{"class":39},"            # one\\ntwo\\nthree\\n\n",[33,2756,2757],{"class":35,"line":386},[33,2758,47],{"emptyLinePlaceholder":46},[33,2760,2761],{"class":35,"line":403},[33,2762,2763],{"class":39},"# ── printf doesn't add a trailing newline by default ──\n",[33,2765,2766,2768,2771,2774],{"class":35,"line":420},[33,2767,2454],{"class":79},[33,2769,2770],{"class":61}," '%s'",[33,2772,2773],{"class":61}," \"no newline\"",[33,2775,2776],{"class":39},"             # no newline (no \\n — same line)\n",[33,2778,2779,2781,2783,2786],{"class":35,"line":437},[33,2780,2454],{"class":79},[33,2782,2474],{"class":61},[33,2784,2785],{"class":61}," \"with newline\"",[33,2787,2788],{"class":39},"         # with newline\\n\n",[33,2790,2791],{"class":35,"line":442},[33,2792,47],{"emptyLinePlaceholder":46},[33,2794,2795],{"class":35,"line":448},[33,2796,2797],{"class":39},"# ── ⚠️ printf with a format string containing % in the data ──\n",[33,2799,2800],{"class":35,"line":458},[33,2801,2802],{"class":39},"# ❌ Dangerous: if the data contains %, printf interprets it as a format specifier\n",[33,2804,2805,2807,2809,2812,2814],{"class":35,"line":473},[33,2806,2454],{"class":79},[33,2808,720],{"class":61},[33,2810,2811],{"class":53},"$user_input",[33,2813,726],{"class":61},[33,2815,2816],{"class":39},"     # if user_input is \"%s %s\" — reads from args\u002Fstack!\n",[33,2818,2819],{"class":35,"line":488},[33,2820,2821],{"class":39},"# ✅ Safe: always use a format string\n",[33,2823,2824,2826,2828,2830,2832,2834],{"class":35,"line":503},[33,2825,2454],{"class":79},[33,2827,2770],{"class":61},[33,2829,720],{"class":61},[33,2831,2811],{"class":53},[33,2833,726],{"class":61},[33,2835,2836],{"class":39},"  # data is the ARG, format is '%s' — safe\n",[15,2838,2840],{"id":2839},"string-comparison-semantics","String Comparison Semantics",[20,2842,2843],{"language":22},[24,2844,2846],{"className":26,"code":2845,"language":22,"meta":28,"style":28},"# ── String comparison in [[ ]] is LEXICOGRAPHIC (dictionary order) ──\n[[ \"abc\" == \"abc\" ]]     # true — exact equality\n[[ \"abc\" != \"abd\" ]]     # true — inequality\n[[ \"abc\" \u003C \"abd\" ]]      # true — lexicographic (a=a, b=b, c\u003Cd)\n[[ \"10\" \u003C \"9\" ]]         # true — STRING comparison: \"1\" \u003C \"9\" (char-by-char)\n[[ \"10\" -lt \"9\" ]]       # ERROR — -lt is for integers, use ((...)) instead\n(( 10 \u003C 9 ))             # false — numeric comparison\n(( 10 > 9 ))             # true — numeric\n\n# ── ⚠️ \"10\" \u003C \"9\" is TRUE in string comparison ──\n# String comparison compares character by character: \"1\" (0x31) vs \"9\" (0x39)\n# \"1\" \u003C \"9\" in ASCII, so \"10\" \u003C \"9\" is true (the first char decides).\n# For numeric comparison, always use (( )).\n\n# ── Glob pattern matching (RIGHT side of ==\u002F!=) ──\nfile=\"report_2024.txt\"\n[[ $file == *.txt ]]         # true — right side is a glob pattern\n[[ $file == report_* ]]     # true — prefix glob\n[[ $file != *.log ]]        # true — negated glob\n# ⚠️ Only the RIGHT side of ==\u002F!= is treated as a glob. Left side is literal.\n# Quoting the right side makes it a literal:\n[[ $file == \"*.txt\" ]]       # false — literal comparison (file is not literally \"*.txt\")\n\n# ── Empty string checks ──\n[[ -z \"\" ]]       # true — zero length\n[[ -n \"x\" ]]      # true — non-zero length\n[[ -z \"$var\" ]]   # true if var is empty or unset\n[[ -n \"$var\" ]]   # true if var is non-empty\n\n# ── Case statement for pattern matching ──\ncase \"$file\" in\n    *.jpg|*.png|*.gif) echo \"image\" ;;\n    *.txt|*.md)        echo \"text\" ;;\n    *)                 echo \"other\" ;;\nesac\n# Case patterns are GLOBS, not regex.\n",[30,2847,2848,2853,2871,2888,2905,2923,2940,2959,2975,2979,2984,2989,2994,2999,3003,3008,3017,3034,3050,3065,3070,3075,3089,3093,3098,3113,3128,3147,3164,3168,3173,3188,3219,3241,3254,3259],{"__ignoreMap":28},[33,2849,2850],{"class":35,"line":36},[33,2851,2852],{"class":39},"# ── String comparison in [[ ]] is LEXICOGRAPHIC (dictionary order) ──\n",[33,2854,2855,2857,2859,2862,2865,2868],{"class":35,"line":43},[33,2856,1587],{"class":53},[33,2858,1590],{"class":61},[33,2860,2861],{"class":57}," ==",[33,2863,2864],{"class":61}," \"abc\"",[33,2866,2867],{"class":53}," ]]     ",[33,2869,2870],{"class":39},"# true — exact equality\n",[33,2872,2873,2875,2877,2880,2883,2885],{"class":35,"line":50},[33,2874,1587],{"class":53},[33,2876,1590],{"class":61},[33,2878,2879],{"class":57}," !=",[33,2881,2882],{"class":61}," \"abd\"",[33,2884,2867],{"class":53},[33,2886,2887],{"class":39},"# true — inequality\n",[33,2889,2890,2892,2894,2897,2899,2902],{"class":35,"line":65},[33,2891,1587],{"class":53},[33,2893,1590],{"class":61},[33,2895,2896],{"class":57}," \u003C",[33,2898,2882],{"class":61},[33,2900,2901],{"class":53}," ]]      ",[33,2903,2904],{"class":39},"# true — lexicographic (a=a, b=b, c\u003Cd)\n",[33,2906,2907,2909,2912,2914,2917,2920],{"class":35,"line":70},[33,2908,1587],{"class":53},[33,2910,2911],{"class":61},"\"10\"",[33,2913,2896],{"class":57},[33,2915,2916],{"class":61}," \"9\"",[33,2918,2919],{"class":53}," ]]         ",[33,2921,2922],{"class":39},"# true — STRING comparison: \"1\" \u003C \"9\" (char-by-char)\n",[33,2924,2925,2927,2929,2932,2934,2937],{"class":35,"line":76},[33,2926,1587],{"class":53},[33,2928,2911],{"class":61},[33,2930,2931],{"class":57}," -lt",[33,2933,2916],{"class":61},[33,2935,2936],{"class":53}," ]]       ",[33,2938,2939],{"class":39},"# ERROR — -lt is for integers, use ((...)) instead\n",[33,2941,2942,2945,2948,2950,2953,2956],{"class":35,"line":97},[33,2943,2944],{"class":53},"(( ",[33,2946,2947],{"class":79},"10",[33,2949,2896],{"class":57},[33,2951,2952],{"class":79}," 9",[33,2954,2955],{"class":53}," ))             ",[33,2957,2958],{"class":39},"# false — numeric comparison\n",[33,2960,2961,2963,2965,2968,2970,2972],{"class":35,"line":102},[33,2962,2944],{"class":53},[33,2964,2947],{"class":79},[33,2966,2967],{"class":57}," >",[33,2969,2952],{"class":79},[33,2971,2955],{"class":53},[33,2973,2974],{"class":39},"# true — numeric\n",[33,2976,2977],{"class":35,"line":108},[33,2978,47],{"emptyLinePlaceholder":46},[33,2980,2981],{"class":35,"line":133},[33,2982,2983],{"class":39},"# ── ⚠️ \"10\" \u003C \"9\" is TRUE in string comparison ──\n",[33,2985,2986],{"class":35,"line":152},[33,2987,2988],{"class":39},"# String comparison compares character by character: \"1\" (0x31) vs \"9\" (0x39)\n",[33,2990,2991],{"class":35,"line":171},[33,2992,2993],{"class":39},"# \"1\" \u003C \"9\" in ASCII, so \"10\" \u003C \"9\" is true (the first char decides).\n",[33,2995,2996],{"class":35,"line":190},[33,2997,2998],{"class":39},"# For numeric comparison, always use (( )).\n",[33,3000,3001],{"class":35,"line":214},[33,3002,47],{"emptyLinePlaceholder":46},[33,3004,3005],{"class":35,"line":219},[33,3006,3007],{"class":39},"# ── Glob pattern matching (RIGHT side of ==\u002F!=) ──\n",[33,3009,3010,3012,3014],{"class":35,"line":225},[33,3011,980],{"class":53},[33,3013,58],{"class":57},[33,3015,3016],{"class":61},"\"report_2024.txt\"\n",[33,3018,3019,3022,3025,3028,3031],{"class":35,"line":250},[33,3020,3021],{"class":53},"[[ $file ",[33,3023,3024],{"class":57},"==",[33,3026,3027],{"class":57}," *",[33,3029,3030],{"class":53},".txt ]]         ",[33,3032,3033],{"class":39},"# true — right side is a glob pattern\n",[33,3035,3036,3038,3040,3043,3045,3047],{"class":35,"line":275},[33,3037,3021],{"class":53},[33,3039,3024],{"class":57},[33,3041,3042],{"class":53}," report_",[33,3044,865],{"class":57},[33,3046,2867],{"class":53},[33,3048,3049],{"class":39},"# true — prefix glob\n",[33,3051,3052,3054,3057,3059,3062],{"class":35,"line":300},[33,3053,3021],{"class":53},[33,3055,3056],{"class":57},"!=",[33,3058,3027],{"class":57},[33,3060,3061],{"class":53},".log ]]        ",[33,3063,3064],{"class":39},"# true — negated glob\n",[33,3066,3067],{"class":35,"line":323},[33,3068,3069],{"class":39},"# ⚠️ Only the RIGHT side of ==\u002F!= is treated as a glob. Left side is literal.\n",[33,3071,3072],{"class":35,"line":347},[33,3073,3074],{"class":39},"# Quoting the right side makes it a literal:\n",[33,3076,3077,3079,3081,3084,3086],{"class":35,"line":352},[33,3078,3021],{"class":53},[33,3080,3024],{"class":57},[33,3082,3083],{"class":61}," \"*.txt\"",[33,3085,2936],{"class":53},[33,3087,3088],{"class":39},"# false — literal comparison (file is not literally \"*.txt\")\n",[33,3090,3091],{"class":35,"line":358},[33,3092,47],{"emptyLinePlaceholder":46},[33,3094,3095],{"class":35,"line":369},[33,3096,3097],{"class":39},"# ── Empty string checks ──\n",[33,3099,3100,3102,3105,3108,3110],{"class":35,"line":386},[33,3101,1587],{"class":53},[33,3103,3104],{"class":57},"-z",[33,3106,3107],{"class":61}," \"\"",[33,3109,2936],{"class":53},[33,3111,3112],{"class":39},"# true — zero length\n",[33,3114,3115,3117,3120,3123,3125],{"class":35,"line":403},[33,3116,1587],{"class":53},[33,3118,3119],{"class":57},"-n",[33,3121,3122],{"class":61}," \"x\"",[33,3124,2901],{"class":53},[33,3126,3127],{"class":39},"# true — non-zero length\n",[33,3129,3130,3132,3134,3136,3139,3141,3144],{"class":35,"line":420},[33,3131,1587],{"class":53},[33,3133,3104],{"class":57},[33,3135,720],{"class":61},[33,3137,3138],{"class":53},"$var",[33,3140,726],{"class":61},[33,3142,3143],{"class":53}," ]]   ",[33,3145,3146],{"class":39},"# true if var is empty or unset\n",[33,3148,3149,3151,3153,3155,3157,3159,3161],{"class":35,"line":437},[33,3150,1587],{"class":53},[33,3152,3119],{"class":57},[33,3154,720],{"class":61},[33,3156,3138],{"class":53},[33,3158,726],{"class":61},[33,3160,3143],{"class":53},[33,3162,3163],{"class":39},"# true if var is non-empty\n",[33,3165,3166],{"class":35,"line":442},[33,3167,47],{"emptyLinePlaceholder":46},[33,3169,3170],{"class":35,"line":448},[33,3171,3172],{"class":39},"# ── Case statement for pattern matching ──\n",[33,3174,3175,3178,3180,3183,3185],{"class":35,"line":458},[33,3176,3177],{"class":57},"case",[33,3179,720],{"class":61},[33,3181,3182],{"class":53},"$file",[33,3184,726],{"class":61},[33,3186,3187],{"class":57}," in\n",[33,3189,3190,3193,3197,3200,3203,3205,3208,3210,3213,3216],{"class":35,"line":473},[33,3191,3192],{"class":57},"    *",[33,3194,3196],{"class":3195},"svAP2",".jpg",[33,3198,3199],{"class":57},"|*",[33,3201,3202],{"class":3195},".png",[33,3204,3199],{"class":57},[33,3206,3207],{"class":3195},".gif",[33,3209,1522],{"class":57},[33,3211,3212],{"class":79}," echo",[33,3214,3215],{"class":61}," \"image\"",[33,3217,3218],{"class":53}," ;;\n",[33,3220,3221,3223,3226,3228,3231,3233,3236,3239],{"class":35,"line":488},[33,3222,3192],{"class":57},[33,3224,3225],{"class":3195},".txt",[33,3227,3199],{"class":57},[33,3229,3230],{"class":3195},".md",[33,3232,1522],{"class":57},[33,3234,3235],{"class":79},"        echo",[33,3237,3238],{"class":61}," \"text\"",[33,3240,3218],{"class":53},[33,3242,3243,3246,3249,3252],{"class":35,"line":503},[33,3244,3245],{"class":57},"    *)",[33,3247,3248],{"class":79},"                 echo",[33,3250,3251],{"class":61}," \"other\"",[33,3253,3218],{"class":53},[33,3255,3256],{"class":35,"line":518},[33,3257,3258],{"class":57},"esac\n",[33,3260,3261],{"class":35,"line":533},[33,3262,3263],{"class":39},"# Case patterns are GLOBS, not regex.\n",[15,3265,3267],{"id":3266},"whitespace-trimming","Whitespace Trimming",[20,3269,3270],{"language":22},[24,3271,3273],{"className":26,"code":3272,"language":22,"meta":28,"style":28},"# ── Trim leading whitespace ──\nstr=\"  hello  \"\n# Method 1: extglob (Bash 3+ with shopt -s extglob)\nshopt -s extglob\ntrimmed=\"${str##+([[:space:]])}\"    # remove longest leading whitespace\ntrimmed=\"${trimmed%%+([[:space:]])}\" # remove longest trailing whitespace\necho \"[$trimmed]\"   # [hello]\n\n# Method 2: without extglob (complex nested expansion)\ntrimmed=\"${str#\"${str%%[![:space:]]*}\"}\"   # remove leading whitespace\ntrimmed=\"${trimmed%\"${trimmed##*[![:space:]]}\"}\"  # remove trailing whitespace\necho \"[$trimmed]\"   # [hello]\n# How it works:\n# ${str%%[![:space:]]*} — remove longest suffix starting with a non-space → \"  \" (leading spaces)\n# ${str#...} — remove that leading part → \"hello  \"\n# ${trimmed##*[![:space:]]} — remove longest prefix ending with non-space → \"  \" (trailing spaces)\n# ${trimmed%...} — remove that trailing part → \"hello\"\n\n# Method 3: read (simple but only for single-line)\nread -r trimmed \u003C\u003C\u003C \"  hello  \"\necho \"[$trimmed]\"   # [hello] — read trims leading\u002Ftrailing whitespace (IFS=default)\n\n# ── Collapse multiple spaces to single ──\nstr=\"hello    world\"\n# Without extglob: loop or tr\ncollapsed=$(echo \"$str\" | tr -s ' ')   # hello world (tr subprocess)\n# With extglob:\nshopt -s extglob\ncollapsed=\"${str\u002F\u002F+([[:space:]])\u002F }\"    # hello world (in-process)\n",[30,3274,3275,3280,3289,3294,3305,3335,3360,3376,3380,3385,3427,3463,3475,3480,3485,3490,3495,3500,3504,3509,3526,3539,3543,3548,3557,3562,3595,3600,3608],{"__ignoreMap":28},[33,3276,3277],{"class":35,"line":36},[33,3278,3279],{"class":39},"# ── Trim leading whitespace ──\n",[33,3281,3282,3284,3286],{"class":35,"line":43},[33,3283,54],{"class":53},[33,3285,58],{"class":57},[33,3287,3288],{"class":61},"\"  hello  \"\n",[33,3290,3291],{"class":35,"line":50},[33,3292,3293],{"class":39},"# Method 1: extglob (Bash 3+ with shopt -s extglob)\n",[33,3295,3296,3299,3302],{"class":35,"line":65},[33,3297,3298],{"class":716},"shopt",[33,3300,3301],{"class":79}," -s",[33,3303,3304],{"class":61}," extglob\n",[33,3306,3307,3310,3312,3314,3316,3319,3322,3324,3327,3329,3332],{"class":35,"line":70},[33,3308,3309],{"class":53},"trimmed",[33,3311,58],{"class":57},[33,3313,939],{"class":61},[33,3315,54],{"class":53},[33,3317,3318],{"class":57},"##",[33,3320,3321],{"class":61},"+([[",[33,3323,117],{"class":57},[33,3325,3326],{"class":61},"space",[33,3328,117],{"class":57},[33,3330,3331],{"class":61},"]])}\"",[33,3333,3334],{"class":39},"    # remove longest leading whitespace\n",[33,3336,3337,3339,3341,3343,3345,3347,3349,3351,3353,3355,3357],{"class":35,"line":76},[33,3338,3309],{"class":53},[33,3340,58],{"class":57},[33,3342,939],{"class":61},[33,3344,3309],{"class":53},[33,3346,881],{"class":57},[33,3348,3321],{"class":61},[33,3350,117],{"class":57},[33,3352,3326],{"class":61},[33,3354,117],{"class":57},[33,3356,3331],{"class":61},[33,3358,3359],{"class":39}," # remove longest trailing whitespace\n",[33,3361,3362,3364,3367,3370,3373],{"class":35,"line":97},[33,3363,80],{"class":79},[33,3365,3366],{"class":61}," \"[",[33,3368,3369],{"class":53},"$trimmed",[33,3371,3372],{"class":61},"]\"",[33,3374,3375],{"class":39},"   # [hello]\n",[33,3377,3378],{"class":35,"line":102},[33,3379,47],{"emptyLinePlaceholder":46},[33,3381,3382],{"class":35,"line":108},[33,3383,3384],{"class":39},"# Method 2: without extglob (complex nested expansion)\n",[33,3386,3387,3389,3391,3393,3395,3397,3399,3401,3403,3405,3408,3410,3412,3414,3416,3419,3421,3424],{"class":35,"line":133},[33,3388,3309],{"class":53},[33,3390,58],{"class":57},[33,3392,939],{"class":61},[33,3394,54],{"class":53},[33,3396,86],{"class":57},[33,3398,939],{"class":61},[33,3400,54],{"class":53},[33,3402,881],{"class":57},[33,3404,2408],{"class":61},[33,3406,3407],{"class":57},"!",[33,3409,2408],{"class":61},[33,3411,117],{"class":57},[33,3413,3326],{"class":61},[33,3415,117],{"class":57},[33,3417,3418],{"class":61},"]]",[33,3420,865],{"class":57},[33,3422,3423],{"class":61},"}\"}\"",[33,3425,3426],{"class":39},"   # remove leading whitespace\n",[33,3428,3429,3431,3433,3435,3437,3439,3441,3443,3445,3447,3449,3451,3453,3455,3457,3460],{"class":35,"line":152},[33,3430,3309],{"class":53},[33,3432,58],{"class":57},[33,3434,939],{"class":61},[33,3436,3309],{"class":53},[33,3438,859],{"class":57},[33,3440,939],{"class":61},[33,3442,3309],{"class":53},[33,3444,817],{"class":57},[33,3446,2408],{"class":61},[33,3448,3407],{"class":57},[33,3450,2408],{"class":61},[33,3452,117],{"class":57},[33,3454,3326],{"class":61},[33,3456,117],{"class":57},[33,3458,3459],{"class":61},"]]}\"}\"",[33,3461,3462],{"class":39},"  # remove trailing whitespace\n",[33,3464,3465,3467,3469,3471,3473],{"class":35,"line":171},[33,3466,80],{"class":79},[33,3468,3366],{"class":61},[33,3470,3369],{"class":53},[33,3472,3372],{"class":61},[33,3474,3375],{"class":39},[33,3476,3477],{"class":35,"line":190},[33,3478,3479],{"class":39},"# How it works:\n",[33,3481,3482],{"class":35,"line":214},[33,3483,3484],{"class":39},"# ${str%%[![:space:]]*} — remove longest suffix starting with a non-space → \"  \" (leading spaces)\n",[33,3486,3487],{"class":35,"line":219},[33,3488,3489],{"class":39},"# ${str#...} — remove that leading part → \"hello  \"\n",[33,3491,3492],{"class":35,"line":225},[33,3493,3494],{"class":39},"# ${trimmed##*[![:space:]]} — remove longest prefix ending with non-space → \"  \" (trailing spaces)\n",[33,3496,3497],{"class":35,"line":250},[33,3498,3499],{"class":39},"# ${trimmed%...} — remove that trailing part → \"hello\"\n",[33,3501,3502],{"class":35,"line":275},[33,3503,47],{"emptyLinePlaceholder":46},[33,3505,3506],{"class":35,"line":300},[33,3507,3508],{"class":39},"# Method 3: read (simple but only for single-line)\n",[33,3510,3511,3514,3517,3520,3523],{"class":35,"line":323},[33,3512,3513],{"class":79},"read",[33,3515,3516],{"class":79}," -r",[33,3518,3519],{"class":61}," trimmed",[33,3521,3522],{"class":57}," \u003C\u003C\u003C",[33,3524,3525],{"class":61}," \"  hello  \"\n",[33,3527,3528,3530,3532,3534,3536],{"class":35,"line":347},[33,3529,80],{"class":79},[33,3531,3366],{"class":61},[33,3533,3369],{"class":53},[33,3535,3372],{"class":61},[33,3537,3538],{"class":39},"   # [hello] — read trims leading\u002Ftrailing whitespace (IFS=default)\n",[33,3540,3541],{"class":35,"line":352},[33,3542,47],{"emptyLinePlaceholder":46},[33,3544,3545],{"class":35,"line":358},[33,3546,3547],{"class":39},"# ── Collapse multiple spaces to single ──\n",[33,3549,3550,3552,3554],{"class":35,"line":369},[33,3551,54],{"class":53},[33,3553,58],{"class":57},[33,3555,3556],{"class":61},"\"hello    world\"\n",[33,3558,3559],{"class":35,"line":386},[33,3560,3561],{"class":39},"# Without extglob: loop or tr\n",[33,3563,3564,3567,3569,3571,3573,3575,3577,3579,3581,3584,3586,3589,3592],{"class":35,"line":403},[33,3565,3566],{"class":53},"collapsed",[33,3568,58],{"class":57},[33,3570,1092],{"class":53},[33,3572,80],{"class":79},[33,3574,720],{"class":61},[33,3576,1099],{"class":53},[33,3578,726],{"class":61},[33,3580,1104],{"class":57},[33,3582,3583],{"class":716}," tr",[33,3585,3301],{"class":79},[33,3587,3588],{"class":61}," ' '",[33,3590,3591],{"class":53},")   ",[33,3593,3594],{"class":39},"# hello world (tr subprocess)\n",[33,3596,3597],{"class":35,"line":420},[33,3598,3599],{"class":39},"# With extglob:\n",[33,3601,3602,3604,3606],{"class":35,"line":437},[33,3603,3298],{"class":716},[33,3605,3301],{"class":79},[33,3607,3304],{"class":61},[33,3609,3610,3612,3614,3616,3618,3620,3622,3624,3626,3628,3631,3633,3636],{"class":35,"line":442},[33,3611,3566],{"class":53},[33,3613,58],{"class":57},[33,3615,939],{"class":61},[33,3617,54],{"class":53},[33,3619,259],{"class":57},[33,3621,3321],{"class":61},[33,3623,117],{"class":57},[33,3625,3326],{"class":61},[33,3627,117],{"class":57},[33,3629,3630],{"class":61},"]])",[33,3632,234],{"class":57},[33,3634,3635],{"class":61}," }\"",[33,3637,3638],{"class":39},"    # hello world (in-process)\n",[15,3640,3642],{"id":3641},"tips-tricks","💡 Tips & Tricks",[20,3644,3645],{"language":22},[24,3646,3648],{"className":26,"code":3647,"language":22,"meta":28,"style":28},"# ── Split a version string into components ──\nversion=\"1.2.3-beta\"\nIFS='.-' read -r major minor patch pre \u003C\u003C\u003C \"$version\"\necho \"$major.$minor.$patch ($pre)\"   # 1.2.3 (beta)\n\n# ── Compare version strings ──\nver_lt() {\n    local a=$1 b=$2\n    local a1 a2 a3 b1 b2 b3\n    IFS='.' read -r a1 a2 a3 \u003C\u003C\u003C \"$a\"\n    IFS='.' read -r b1 b2 b3 \u003C\u003C\u003C \"$b\"\n    # Pad with zeros for numeric comparison\n    printf -v a_padded '%03d%03d%03d' \"${a1:-0}\" \"${a2:-0}\" \"${a3:-0}\"\n    printf -v b_padded '%03d%03d%03d' \"${b1:-0}\" \"${b2:-0}\" \"${b3:-0}\"\n    (( a_padded \u003C b_padded ))\n}\nver_lt \"1.2.3\" \"1.10.0\" && echo \"1.2.3 \u003C 1.10.0\"  # true — numeric, not string\n\n# ── Generate a random hex string ──\nrandom_hex=$(printf '%x' $RANDOM)   # e.g. \"1a2b\" (4 hex chars)\n# For longer: printf '%x' $RANDOM$RANDOM  (but leading zeros may be stripped)\n# Better: head -c8 \u002Fdev\u002Furandom | xxd -p\n\n# ── Center a string in a field ──\ncenter() {\n    local str=$1 width=$2\n    local len=${#str}\n    local pad=$(( (width - len) \u002F 2 ))\n    printf '%*s%s%*s' \"$pad\" \"\" \"$str\" \"$pad\" \"\"\n}\ncenter \"hello\" 20   # \"        hello        \"\n\n# ── Reverse a string ──\nreverse() {\n    local str=$1\n    local len=${#str}\n    local i result=\"\"\n    for ((i = len - 1; i >= 0; i--)); do\n        result+=\"${str:i:1}\"\n    done\n    printf '%s' \"$result\"\n}\nreverse \"hello\"   # olleh\n\n# ── Title case ──\ntitle_case() {\n    local str=$1\n    local word\n    local result=\"\"\n    for word in $str; do\n        result+=\"${word^} \"  # ^ capitalizes first char (Bash 4+)\n    done\n    printf '%s' \"${result% }\"  # strip trailing space\n}\ntitle_case \"hello world\"   # Hello World\n",[30,3649,3650,3655,3665,3702,3732,3736,3741,3748,3765,3772,3804,3834,3839,3884,3928,3939,3943,3964,3968,3973,3993,3998,4003,4007,4012,4019,4037,4054,4085,4116,4120,4132,4136,4141,4148,4158,4172,4184,4222,4244,4249,4262,4266,4275,4279,4284,4291,4301,4308,4319,4333,4350,4354,4371,4375],{"__ignoreMap":28},[33,3651,3652],{"class":35,"line":36},[33,3653,3654],{"class":39},"# ── Split a version string into components ──\n",[33,3656,3657,3660,3662],{"class":35,"line":43},[33,3658,3659],{"class":53},"version",[33,3661,58],{"class":57},[33,3663,3664],{"class":61},"\"1.2.3-beta\"\n",[33,3666,3667,3670,3672,3675,3678,3680,3683,3686,3689,3692,3694,3696,3699],{"class":35,"line":50},[33,3668,3669],{"class":53},"IFS",[33,3671,58],{"class":57},[33,3673,3674],{"class":61},"'.-'",[33,3676,3677],{"class":79}," read",[33,3679,3516],{"class":79},[33,3681,3682],{"class":61}," major",[33,3684,3685],{"class":61}," minor",[33,3687,3688],{"class":61}," patch",[33,3690,3691],{"class":61}," pre",[33,3693,3522],{"class":57},[33,3695,720],{"class":61},[33,3697,3698],{"class":53},"$version",[33,3700,3701],{"class":61},"\"\n",[33,3703,3704,3706,3708,3711,3713,3716,3718,3721,3723,3726,3729],{"class":35,"line":65},[33,3705,80],{"class":79},[33,3707,720],{"class":61},[33,3709,3710],{"class":53},"$major",[33,3712,862],{"class":61},[33,3714,3715],{"class":53},"$minor",[33,3717,862],{"class":61},[33,3719,3720],{"class":53},"$patch",[33,3722,1834],{"class":61},[33,3724,3725],{"class":53},"$pre",[33,3727,3728],{"class":61},")\"",[33,3730,3731],{"class":39},"   # 1.2.3 (beta)\n",[33,3733,3734],{"class":35,"line":70},[33,3735,47],{"emptyLinePlaceholder":46},[33,3737,3738],{"class":35,"line":76},[33,3739,3740],{"class":39},"# ── Compare version strings ──\n",[33,3742,3743,3746],{"class":35,"line":97},[33,3744,3745],{"class":716},"ver_lt",[33,3747,2014],{"class":53},[33,3749,3750,3752,3754,3756,3759,3761,3763],{"class":35,"line":102},[33,3751,2019],{"class":57},[33,3753,1626],{"class":53},[33,3755,58],{"class":57},[33,3757,3758],{"class":2027},"$1",[33,3760,2702],{"class":53},[33,3762,58],{"class":57},[33,3764,2043],{"class":2027},[33,3766,3767,3769],{"class":35,"line":108},[33,3768,2019],{"class":57},[33,3770,3771],{"class":53}," a1 a2 a3 b1 b2 b3\n",[33,3773,3774,3777,3779,3782,3784,3786,3789,3792,3795,3797,3799,3802],{"class":35,"line":133},[33,3775,3776],{"class":53},"    IFS",[33,3778,58],{"class":57},[33,3780,3781],{"class":61},"'.'",[33,3783,3677],{"class":79},[33,3785,3516],{"class":79},[33,3787,3788],{"class":61}," a1",[33,3790,3791],{"class":61}," a2",[33,3793,3794],{"class":61}," a3",[33,3796,3522],{"class":57},[33,3798,720],{"class":61},[33,3800,3801],{"class":53},"$a",[33,3803,3701],{"class":61},[33,3805,3806,3808,3810,3812,3814,3816,3819,3822,3825,3827,3829,3832],{"class":35,"line":152},[33,3807,3776],{"class":53},[33,3809,58],{"class":57},[33,3811,3781],{"class":61},[33,3813,3677],{"class":79},[33,3815,3516],{"class":79},[33,3817,3818],{"class":61}," b1",[33,3820,3821],{"class":61}," b2",[33,3823,3824],{"class":61}," b3",[33,3826,3522],{"class":57},[33,3828,720],{"class":61},[33,3830,3831],{"class":53},"$b",[33,3833,3701],{"class":61},[33,3835,3836],{"class":35,"line":171},[33,3837,3838],{"class":39},"    # Pad with zeros for numeric comparison\n",[33,3840,3841,3843,3845,3848,3851,3853,3856,3858,3860,3862,3864,3867,3869,3871,3873,3875,3878,3880,3882],{"class":35,"line":190},[33,3842,2390],{"class":79},[33,3844,2629],{"class":79},[33,3846,3847],{"class":61}," a_padded",[33,3849,3850],{"class":61}," '%03d%03d%03d'",[33,3852,83],{"class":61},[33,3854,3855],{"class":53},"a1",[33,3857,593],{"class":57},[33,3859,120],{"class":53},[33,3861,91],{"class":61},[33,3863,83],{"class":61},[33,3865,3866],{"class":53},"a2",[33,3868,593],{"class":57},[33,3870,120],{"class":53},[33,3872,91],{"class":61},[33,3874,83],{"class":61},[33,3876,3877],{"class":53},"a3",[33,3879,593],{"class":57},[33,3881,120],{"class":53},[33,3883,1145],{"class":61},[33,3885,3886,3888,3890,3893,3895,3897,3900,3902,3904,3906,3908,3911,3913,3915,3917,3919,3922,3924,3926],{"class":35,"line":214},[33,3887,2390],{"class":79},[33,3889,2629],{"class":79},[33,3891,3892],{"class":61}," b_padded",[33,3894,3850],{"class":61},[33,3896,83],{"class":61},[33,3898,3899],{"class":53},"b1",[33,3901,593],{"class":57},[33,3903,120],{"class":53},[33,3905,91],{"class":61},[33,3907,83],{"class":61},[33,3909,3910],{"class":53},"b2",[33,3912,593],{"class":57},[33,3914,120],{"class":53},[33,3916,91],{"class":61},[33,3918,83],{"class":61},[33,3920,3921],{"class":53},"b3",[33,3923,593],{"class":57},[33,3925,120],{"class":53},[33,3927,1145],{"class":61},[33,3929,3930,3933,3936],{"class":35,"line":219},[33,3931,3932],{"class":53},"    (( a_padded ",[33,3934,3935],{"class":57},"\u003C",[33,3937,3938],{"class":53}," b_padded ))\n",[33,3940,3941],{"class":35,"line":225},[33,3942,2317],{"class":53},[33,3944,3945,3947,3950,3953,3956,3958,3961],{"class":35,"line":250},[33,3946,3745],{"class":716},[33,3948,3949],{"class":61}," \"1.2.3\"",[33,3951,3952],{"class":61}," \"1.10.0\"",[33,3954,3955],{"class":53}," && ",[33,3957,80],{"class":79},[33,3959,3960],{"class":61}," \"1.2.3 \u003C 1.10.0\"",[33,3962,3963],{"class":39},"  # true — numeric, not string\n",[33,3965,3966],{"class":35,"line":275},[33,3967,47],{"emptyLinePlaceholder":46},[33,3969,3970],{"class":35,"line":300},[33,3971,3972],{"class":39},"# ── Generate a random hex string ──\n",[33,3974,3975,3978,3980,3982,3984,3987,3990],{"class":35,"line":323},[33,3976,3977],{"class":53},"random_hex",[33,3979,58],{"class":57},[33,3981,1092],{"class":53},[33,3983,2454],{"class":79},[33,3985,3986],{"class":61}," '%x'",[33,3988,3989],{"class":53}," $RANDOM)   ",[33,3991,3992],{"class":39},"# e.g. \"1a2b\" (4 hex chars)\n",[33,3994,3995],{"class":35,"line":347},[33,3996,3997],{"class":39},"# For longer: printf '%x' $RANDOM$RANDOM  (but leading zeros may be stripped)\n",[33,3999,4000],{"class":35,"line":352},[33,4001,4002],{"class":39},"# Better: head -c8 \u002Fdev\u002Furandom | xxd -p\n",[33,4004,4005],{"class":35,"line":358},[33,4006,47],{"emptyLinePlaceholder":46},[33,4008,4009],{"class":35,"line":369},[33,4010,4011],{"class":39},"# ── Center a string in a field ──\n",[33,4013,4014,4017],{"class":35,"line":386},[33,4015,4016],{"class":716},"center",[33,4018,2014],{"class":53},[33,4020,4021,4023,4026,4028,4030,4033,4035],{"class":35,"line":403},[33,4022,2019],{"class":57},[33,4024,4025],{"class":53}," str",[33,4027,58],{"class":57},[33,4029,3758],{"class":2027},[33,4031,4032],{"class":53}," width",[33,4034,58],{"class":57},[33,4036,2043],{"class":2027},[33,4038,4039,4041,4044,4046,4049,4051],{"class":35,"line":420},[33,4040,2019],{"class":57},[33,4042,4043],{"class":53}," len",[33,4045,58],{"class":57},[33,4047,4048],{"class":53},"${",[33,4050,86],{"class":57},[33,4052,4053],{"class":53},"str}\n",[33,4055,4056,4058,4061,4063,4066,4069,4072,4074,4077,4079,4082],{"class":35,"line":437},[33,4057,2019],{"class":57},[33,4059,4060],{"class":53}," pad",[33,4062,58],{"class":57},[33,4064,4065],{"class":53},"$(( (",[33,4067,4068],{"class":716},"width",[33,4070,4071],{"class":61}," -",[33,4073,4043],{"class":61},[33,4075,4076],{"class":53},") ",[33,4078,234],{"class":716},[33,4080,4081],{"class":79}," 2",[33,4083,4084],{"class":53}," ))\n",[33,4086,4087,4089,4092,4094,4097,4099,4101,4103,4105,4107,4109,4111,4113],{"class":35,"line":442},[33,4088,2390],{"class":79},[33,4090,4091],{"class":61}," '%*s%s%*s'",[33,4093,720],{"class":61},[33,4095,4096],{"class":53},"$pad",[33,4098,726],{"class":61},[33,4100,3107],{"class":61},[33,4102,720],{"class":61},[33,4104,1099],{"class":53},[33,4106,726],{"class":61},[33,4108,720],{"class":61},[33,4110,4096],{"class":53},[33,4112,726],{"class":61},[33,4114,4115],{"class":61}," \"\"\n",[33,4117,4118],{"class":35,"line":448},[33,4119,2317],{"class":53},[33,4121,4122,4124,4126,4129],{"class":35,"line":458},[33,4123,4016],{"class":716},[33,4125,2477],{"class":61},[33,4127,4128],{"class":79}," 20",[33,4130,4131],{"class":39},"   # \"        hello        \"\n",[33,4133,4134],{"class":35,"line":473},[33,4135,47],{"emptyLinePlaceholder":46},[33,4137,4138],{"class":35,"line":488},[33,4139,4140],{"class":39},"# ── Reverse a string ──\n",[33,4142,4143,4146],{"class":35,"line":503},[33,4144,4145],{"class":716},"reverse",[33,4147,2014],{"class":53},[33,4149,4150,4152,4154,4156],{"class":35,"line":518},[33,4151,2019],{"class":57},[33,4153,4025],{"class":53},[33,4155,58],{"class":57},[33,4157,2028],{"class":2027},[33,4159,4160,4162,4164,4166,4168,4170],{"class":35,"line":533},[33,4161,2019],{"class":57},[33,4163,4043],{"class":53},[33,4165,58],{"class":57},[33,4167,4048],{"class":53},[33,4169,86],{"class":57},[33,4171,4053],{"class":53},[33,4173,4174,4176,4179,4181],{"class":35,"line":548},[33,4175,2019],{"class":57},[33,4177,4178],{"class":53}," i result",[33,4180,58],{"class":57},[33,4182,4183],{"class":61},"\"\"\n",[33,4185,4186,4189,4192,4194,4197,4199,4202,4205,4208,4211,4214,4217,4220],{"class":35,"line":563},[33,4187,4188],{"class":57},"    for",[33,4190,4191],{"class":53}," ((i ",[33,4193,58],{"class":57},[33,4195,4196],{"class":53}," len ",[33,4198,1380],{"class":57},[33,4200,4201],{"class":79}," 1",[33,4203,4204],{"class":53},"; i ",[33,4206,4207],{"class":57},">=",[33,4209,4210],{"class":79}," 0",[33,4212,4213],{"class":53},"; i",[33,4215,4216],{"class":57},"--",[33,4218,4219],{"class":53},")); ",[33,4221,2385],{"class":57},[33,4223,4224,4227,4229,4231,4233,4235,4238,4240,4242],{"class":35,"line":568},[33,4225,4226],{"class":53},"        result",[33,4228,2075],{"class":57},[33,4230,939],{"class":61},[33,4232,54],{"class":53},[33,4234,117],{"class":57},[33,4236,4237],{"class":53},"i",[33,4239,117],{"class":57},[33,4241,201],{"class":53},[33,4243,1145],{"class":61},[33,4245,4246],{"class":35,"line":574},[33,4247,4248],{"class":57},"    done\n",[33,4250,4251,4253,4255,4257,4260],{"class":35,"line":583},[33,4252,2390],{"class":79},[33,4254,2770],{"class":61},[33,4256,720],{"class":61},[33,4258,4259],{"class":53},"$result",[33,4261,3701],{"class":61},[33,4263,4264],{"class":35,"line":604},[33,4265,2317],{"class":53},[33,4267,4268,4270,4272],{"class":35,"line":619},[33,4269,4145],{"class":716},[33,4271,2477],{"class":61},[33,4273,4274],{"class":39},"   # olleh\n",[33,4276,4277],{"class":35,"line":638},[33,4278,47],{"emptyLinePlaceholder":46},[33,4280,4281],{"class":35,"line":658},[33,4282,4283],{"class":39},"# ── Title case ──\n",[33,4285,4286,4289],{"class":35,"line":1789},[33,4287,4288],{"class":716},"title_case",[33,4290,2014],{"class":53},[33,4292,4293,4295,4297,4299],{"class":35,"line":1806},[33,4294,2019],{"class":57},[33,4296,4025],{"class":53},[33,4298,58],{"class":57},[33,4300,2028],{"class":2027},[33,4302,4303,4305],{"class":35,"line":1825},[33,4304,2019],{"class":57},[33,4306,4307],{"class":53}," word\n",[33,4309,4310,4312,4315,4317],{"class":35,"line":1846},[33,4311,2019],{"class":57},[33,4313,4314],{"class":53}," result",[33,4316,58],{"class":57},[33,4318,4183],{"class":61},[33,4320,4321,4323,4326,4328,4331],{"class":35,"line":1865},[33,4322,4188],{"class":57},[33,4324,4325],{"class":53}," word ",[33,4327,2361],{"class":57},[33,4329,4330],{"class":53}," $str; ",[33,4332,2385],{"class":57},[33,4334,4335,4337,4339,4341,4344,4347],{"class":35,"line":1870},[33,4336,4226],{"class":53},[33,4338,2075],{"class":57},[33,4340,939],{"class":61},[33,4342,4343],{"class":53},"word",[33,4345,4346],{"class":61},"^} \"",[33,4348,4349],{"class":39},"  # ^ capitalizes first char (Bash 4+)\n",[33,4351,4352],{"class":35,"line":1876},[33,4353,4248],{"class":57},[33,4355,4356,4358,4360,4362,4364,4366,4368],{"class":35,"line":1891},[33,4357,2390],{"class":79},[33,4359,2770],{"class":61},[33,4361,83],{"class":61},[33,4363,1087],{"class":53},[33,4365,859],{"class":57},[33,4367,3635],{"class":61},[33,4369,4370],{"class":39},"  # strip trailing space\n",[33,4372,4373],{"class":35,"line":1906},[33,4374,2317],{"class":53},[33,4376,4377,4379,4382],{"class":35,"line":1922},[33,4378,4288],{"class":716},[33,4380,4381],{"class":61}," \"hello world\"",[33,4383,4384],{"class":39},"   # Hello World\n",[15,4386,4388],{"id":4387},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[20,4390,4391],{"language":22},[24,4392,4394],{"className":26,"code":4393,"language":22,"meta":28,"style":28},"# ── `${str: -5}` needs a SPACE before - (else it's :- default expansion) ──\nstr=\"hello\"\necho \"${str: -3}\"   # llo — slice: last 3 chars (space before -)\necho \"${str:-3}\"    # hello — default expansion: if str is unset\u002Fempty, use \"3\"\n# (Since str is \"hello\" (non-empty), ${str:-3} returns str's value, not \"3\")\n# But if str were empty: echo \"${str:-3}\" → \"3\" (default), not a slice!\n\n# ── `${var\u002Fpattern\u002Frepl}` patterns are GLOBS, not regex ──\necho \"${str\u002F\u002F[0-9]\u002FN}\"   # replace digits with N (glob [0-9])\n# Cannot use regex like \\d+ — use [[ =~ ]] for regex.\n\n# ── `[[ =~ ]]` regex must NOT be quoted ──\n[[ \"123\" =~ \"^[0-9]+$\" ]]   # false — quoted regex is a LITERAL string match\n[[ \"123\" =~ ^[0-9]+$ ]]     # true — unquoted regex (but hard to read inline)\nre='^[0-9]+$'\n[[ \"123\" =~ $re ]]          # true — variable reference (BEST: readable + correct)\n\n# ── `BASH_REMATCH` is global and overwritten by each =~ ──\n[[ \"abc\" =~ (a) ]]; echo \"${BASH_REMATCH[1]}\"  # a\n[[ \"xyz\" =~ (x) ]]; echo \"${BASH_REMATCH[1]}\"  # x — previous \"a\" is GONE\n# Capture immediately after each match.\n\n# ── `printf '%s' \"$var\"` is safe; `printf \"$var\"` is dangerous ──\n# If $var contains %s, %d, etc., printf interprets them as format specifiers.\n# Always use a format string: printf '%s' \"$var\"  (or printf '%s\\n' \"$var\")\n\n# ── Case conversion is Bash 4+ only ──\necho \"${str^^}\"   # Bash 4+: HELLO. Bash 3.2 (macOS): \"bash: ${str^^}: bad substitution\"\n# Portable fallback: echo \"$str\" | tr 'a-z' 'A-Z' (but that forks a subprocess)\n\n# ── `[[ \"10\" \u003C \"9\" ]]` is TRUE (string comparison) ──\n# Always use (( )) for numeric comparison: (( 10 \u003C 9 ))  → false\n",[30,4395,4396,4401,4410,4428,4445,4450,4455,4459,4464,4487,4492,4496,4501,4517,4531,4540,4554,4558,4563,4585,4608,4613,4617,4622,4627,4632,4636,4641,4654,4659,4663,4668],{"__ignoreMap":28},[33,4397,4398],{"class":35,"line":36},[33,4399,4400],{"class":39},"# ── `${str: -5}` needs a SPACE before - (else it's :- default expansion) ──\n",[33,4402,4403,4405,4407],{"class":35,"line":43},[33,4404,54],{"class":53},[33,4406,58],{"class":57},[33,4408,4409],{"class":61},"\"hello\"\n",[33,4411,4412,4414,4416,4418,4420,4423,4425],{"class":35,"line":50},[33,4413,80],{"class":79},[33,4415,83],{"class":61},[33,4417,54],{"class":53},[33,4419,117],{"class":57},[33,4421,4422],{"class":53}," -3",[33,4424,91],{"class":61},[33,4426,4427],{"class":39},"   # llo — slice: last 3 chars (space before -)\n",[33,4429,4430,4432,4434,4436,4438,4440,4442],{"class":35,"line":65},[33,4431,80],{"class":79},[33,4433,83],{"class":61},[33,4435,54],{"class":53},[33,4437,593],{"class":57},[33,4439,206],{"class":53},[33,4441,91],{"class":61},[33,4443,4444],{"class":39},"    # hello — default expansion: if str is unset\u002Fempty, use \"3\"\n",[33,4446,4447],{"class":35,"line":70},[33,4448,4449],{"class":39},"# (Since str is \"hello\" (non-empty), ${str:-3} returns str's value, not \"3\")\n",[33,4451,4452],{"class":35,"line":76},[33,4453,4454],{"class":39},"# But if str were empty: echo \"${str:-3}\" → \"3\" (default), not a slice!\n",[33,4456,4457],{"class":35,"line":97},[33,4458,47],{"emptyLinePlaceholder":46},[33,4460,4461],{"class":35,"line":102},[33,4462,4463],{"class":39},"# ── `${var\u002Fpattern\u002Frepl}` patterns are GLOBS, not regex ──\n",[33,4465,4466,4468,4470,4472,4474,4477,4479,4482,4484],{"class":35,"line":108},[33,4467,80],{"class":79},[33,4469,83],{"class":61},[33,4471,54],{"class":53},[33,4473,259],{"class":57},[33,4475,4476],{"class":61},"[0-9]",[33,4478,234],{"class":57},[33,4480,4481],{"class":53},"N",[33,4483,91],{"class":61},[33,4485,4486],{"class":39},"   # replace digits with N (glob [0-9])\n",[33,4488,4489],{"class":35,"line":133},[33,4490,4491],{"class":39},"# Cannot use regex like \\d+ — use [[ =~ ]] for regex.\n",[33,4493,4494],{"class":35,"line":152},[33,4495,47],{"emptyLinePlaceholder":46},[33,4497,4498],{"class":35,"line":171},[33,4499,4500],{"class":39},"# ── `[[ =~ ]]` regex must NOT be quoted ──\n",[33,4502,4503,4505,4507,4509,4512,4514],{"class":35,"line":190},[33,4504,1587],{"class":53},[33,4506,1775],{"class":61},[33,4508,1593],{"class":57},[33,4510,4511],{"class":61}," \"^[0-9]+$\"",[33,4513,3143],{"class":53},[33,4515,4516],{"class":39},"# false — quoted regex is a LITERAL string match\n",[33,4518,4519,4521,4523,4525,4528],{"class":35,"line":214},[33,4520,1587],{"class":53},[33,4522,1775],{"class":61},[33,4524,1593],{"class":57},[33,4526,4527],{"class":53}," ^[0-9]+$ ]]     ",[33,4529,4530],{"class":39},"# true — unquoted regex (but hard to read inline)\n",[33,4532,4533,4535,4537],{"class":35,"line":219},[33,4534,1285],{"class":53},[33,4536,58],{"class":57},[33,4538,4539],{"class":61},"'^[0-9]+$'\n",[33,4541,4542,4544,4546,4548,4551],{"class":35,"line":225},[33,4543,1587],{"class":53},[33,4545,1775],{"class":61},[33,4547,1593],{"class":57},[33,4549,4550],{"class":53}," $re ]]          ",[33,4552,4553],{"class":39},"# true — variable reference (BEST: readable + correct)\n",[33,4555,4556],{"class":35,"line":250},[33,4557,47],{"emptyLinePlaceholder":46},[33,4559,4560],{"class":35,"line":275},[33,4561,4562],{"class":39},"# ── `BASH_REMATCH` is global and overwritten by each =~ ──\n",[33,4564,4565,4567,4569,4571,4574,4576,4578,4580,4582],{"class":35,"line":300},[33,4566,1587],{"class":53},[33,4568,1590],{"class":61},[33,4570,1593],{"class":57},[33,4572,4573],{"class":53}," (a) ]]; ",[33,4575,80],{"class":79},[33,4577,83],{"class":61},[33,4579,1250],{"class":53},[33,4581,1446],{"class":61},[33,4583,4584],{"class":39},"  # a\n",[33,4586,4587,4589,4592,4594,4597,4599,4601,4603,4605],{"class":35,"line":323},[33,4588,1587],{"class":53},[33,4590,4591],{"class":61},"\"xyz\"",[33,4593,1593],{"class":57},[33,4595,4596],{"class":53}," (x) ]]; ",[33,4598,80],{"class":79},[33,4600,83],{"class":61},[33,4602,1250],{"class":53},[33,4604,1446],{"class":61},[33,4606,4607],{"class":39},"  # x — previous \"a\" is GONE\n",[33,4609,4610],{"class":35,"line":347},[33,4611,4612],{"class":39},"# Capture immediately after each match.\n",[33,4614,4615],{"class":35,"line":352},[33,4616,47],{"emptyLinePlaceholder":46},[33,4618,4619],{"class":35,"line":358},[33,4620,4621],{"class":39},"# ── `printf '%s' \"$var\"` is safe; `printf \"$var\"` is dangerous ──\n",[33,4623,4624],{"class":35,"line":369},[33,4625,4626],{"class":39},"# If $var contains %s, %d, etc., printf interprets them as format specifiers.\n",[33,4628,4629],{"class":35,"line":386},[33,4630,4631],{"class":39},"# Always use a format string: printf '%s' \"$var\"  (or printf '%s\\n' \"$var\")\n",[33,4633,4634],{"class":35,"line":403},[33,4635,47],{"emptyLinePlaceholder":46},[33,4637,4638],{"class":35,"line":420},[33,4639,4640],{"class":39},"# ── Case conversion is Bash 4+ only ──\n",[33,4642,4643,4645,4647,4649,4651],{"class":35,"line":437},[33,4644,80],{"class":79},[33,4646,83],{"class":61},[33,4648,54],{"class":53},[33,4650,482],{"class":61},[33,4652,4653],{"class":39},"   # Bash 4+: HELLO. Bash 3.2 (macOS): \"bash: ${str^^}: bad substitution\"\n",[33,4655,4656],{"class":35,"line":442},[33,4657,4658],{"class":39},"# Portable fallback: echo \"$str\" | tr 'a-z' 'A-Z' (but that forks a subprocess)\n",[33,4660,4661],{"class":35,"line":448},[33,4662,47],{"emptyLinePlaceholder":46},[33,4664,4665],{"class":35,"line":458},[33,4666,4667],{"class":39},"# ── `[[ \"10\" \u003C \"9\" ]]` is TRUE (string comparison) ──\n",[33,4669,4670],{"class":35,"line":473},[33,4671,4672],{"class":39},"# Always use (( )) for numeric comparison: (( 10 \u003C 9 ))  → false\n",[15,4674,4676],{"id":4675},"quick-quiz","🧠 Quick Quiz",[4678,4679,4680],"p",{},"What does this print?",[20,4682,4683],{"language":22},[24,4684,4686],{"className":26,"code":4685,"language":22,"meta":28,"style":28},"file=\"archive.tar.gz\"\necho \"${file%%.*}\"\necho \"${file%.*}\"\necho \"${file##*.}\"\necho \"${file#*.}\"\n",[30,4687,4688,4696,4712,4728,4741],{"__ignoreMap":28},[33,4689,4690,4692,4694],{"class":35,"line":36},[33,4691,980],{"class":53},[33,4693,58],{"class":57},[33,4695,985],{"class":61},[33,4697,4698,4700,4702,4704,4706,4708,4710],{"class":35,"line":43},[33,4699,80],{"class":79},[33,4701,83],{"class":61},[33,4703,980],{"class":53},[33,4705,881],{"class":57},[33,4707,862],{"class":61},[33,4709,865],{"class":57},[33,4711,1145],{"class":61},[33,4713,4714,4716,4718,4720,4722,4724,4726],{"class":35,"line":50},[33,4715,80],{"class":79},[33,4717,83],{"class":61},[33,4719,980],{"class":53},[33,4721,859],{"class":57},[33,4723,862],{"class":61},[33,4725,865],{"class":57},[33,4727,1145],{"class":61},[33,4729,4730,4732,4734,4736,4738],{"class":35,"line":65},[33,4731,80],{"class":79},[33,4733,83],{"class":61},[33,4735,980],{"class":53},[33,4737,817],{"class":57},[33,4739,4740],{"class":61},".}\"\n",[33,4742,4743,4745,4747,4749,4751],{"class":35,"line":70},[33,4744,80],{"class":79},[33,4746,83],{"class":61},[33,4748,980],{"class":53},[33,4750,834],{"class":57},[33,4752,4740],{"class":61},[4754,4755,4756,4760,4768,4774],"details",{},[4757,4758,4759],"summary",{},"Answer",[24,4761,4766],{"className":4762,"code":4764,"language":4765},[4763],"language-text","archive          ← ${file%%.*}: removes LONGEST suffix from first . (removes \".tar.gz\")\narchive.tar      ← ${file%.*}: removes SHORTEST suffix from last . (removes \".gz\")\ngz               ← ${file##*.}: removes LONGEST prefix up to last . (leaves \"gz\")\ntar.gz           ← ${file#*.}: removes SHORTEST prefix up to first . (leaves \"tar.gz\")\n","text",[30,4767,4764],{"__ignoreMap":28},[4678,4769,4770,117],{},[4771,4772,4773],"strong",{},"The key insight",[4775,4776,4777,4796],"table",{},[4778,4779,4780],"thead",{},[4781,4782,4783,4787,4790,4793],"tr",{},[4784,4785,4786],"th",{},"Operator",[4784,4788,4789],{},"Scope",[4784,4791,4792],{},"Direction",[4784,4794,4795],{},"Removes",[4797,4798,4799,4818,4834,4851],"tbody",{},[4781,4800,4801,4807,4810,4813],{},[4802,4803,4804,4806],"td",{},[30,4805,86],{}," (shortest)",[4802,4808,4809],{},"prefix",[4802,4811,4812],{},"left-to-right",[4802,4814,4815,4816],{},"up to FIRST ",[30,4817,862],{},[4781,4819,4820,4825,4827,4829],{},[4802,4821,4822,4824],{},[30,4823,3318],{}," (longest)",[4802,4826,4809],{},[4802,4828,4812],{},[4802,4830,4831,4832],{},"up to LAST ",[30,4833,862],{},[4781,4835,4836,4840,4843,4846],{},[4802,4837,4838,4806],{},[30,4839,859],{},[4802,4841,4842],{},"suffix",[4802,4844,4845],{},"right-to-left",[4802,4847,4848,4849],{},"from LAST ",[30,4850,862],{},[4781,4852,4853,4857,4859,4861],{},[4802,4854,4855,4824],{},[30,4856,881],{},[4802,4858,4842],{},[4802,4860,4845],{},[4802,4862,4863,4864],{},"from FIRST ",[30,4865,862],{},[4867,4868,4869],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}html pre.shiki code .svAP2, html code.shiki .svAP2{--shiki-default:#032F62;--shiki-github-dark:#DBEDFF}",{"title":28,"searchDepth":43,"depth":43,"links":4871},[4872,4873,4874,4876,4878,4879,4881,4882,4883,4884,4885],{"id":17,"depth":43,"text":18},{"id":680,"depth":43,"text":681},{"id":1065,"depth":43,"text":4875},"Anti-Pattern: Using sed for Simple String Ops",{"id":1242,"depth":43,"text":4877},"Regex Matching: [[ =~ ]] and BASH_REMATCH",{"id":1984,"depth":43,"text":1985},{"id":2451,"depth":43,"text":4880},"printf: Formatting Engine",{"id":2839,"depth":43,"text":2840},{"id":3266,"depth":43,"text":3267},{"id":3641,"depth":43,"text":3642},{"id":4387,"depth":43,"text":4388},{"id":4675,"depth":43,"text":4676},"Deep-dive into Bash string mechanics: parameter expansion operators (substring, replace, delete, case), BASH_REMATCH capture groups, printf formatting, and zero-allocation string processing patterns. Code-first reference for senior engineers.","md",{},"\u002Fbash\u002F10-string-manipulation",{"title":5,"description":4886},"bash\u002F10-string-manipulation","sjj7CNw0a8y5INSkd0qCglCvbOsKRxnKVwrAp6TjTgg",1789924644817]