[{"data":1,"prerenderedAt":4169},["ShallowReactive",2],{"page-\u002Fbash\u002F03-operators-and-expansions":3},{"id":4,"title":5,"body":6,"description":4162,"extension":4163,"meta":4164,"navigation":65,"path":4165,"seo":4166,"stem":4167,"__hash__":4168},"content\u002Fbash\u002F03-operators-and-expansions.md","Bash 03 — Arithmetic Engine, Test Conditionals & Expansion Pipelines",{"type":7,"value":8,"toc":4141},"minimark",[9,14,27,480,492,643,649,807,817,1375,1379,1708,1716,2044,2055,2442,2446,2682,2686,3077,3081,3268,3272,3671,3675,3971,3975,3979,4041,4137],[10,11,13],"h1",{"id":12},"_03-arithmetic-engine-test-conditionals-expansion-pipelines","03 — Arithmetic Engine, Test Conditionals & Expansion Pipelines",[15,16,18,19,23,24],"h2",{"id":17},"arithmetic-evaluation-vs","Arithmetic Evaluation: ",[20,21,22],"code",{},"((...))"," vs ",[20,25,26],{},"$((...))",[28,29,31],"code-wrapper",{"language":30},"bash",[32,33,37],"pre",{"className":34,"code":35,"language":30,"meta":36,"style":36},"language-bash shiki shiki-themes github-light github-dark","# ── Two forms, two purposes ──\n# ((expr))   → evaluates expr; returns exit status (0 if result != 0, 1 if result == 0)\n# $((expr))  → evaluates expr; EXPANDS to the result (used in commands\u002Fassignments)\n\nx=10\n((x = x + 5))         # x is now 15 — assignment in arithmetic context (no $ needed)\n((x++))               # 16 — postfix increment (returns old value, increments after)\n((++x))               # 17 — prefix increment (increments first, returns new value)\n((x *= 2))            # 34 — compound assignment\n((x %= 5))            # 4  — 34 % 5 = 4\n\n# ── As a condition (returns exit status) ──\n((x > 10)) && echo \"big\"       # big — exit 0 (true) since 17 > 10\n((x \u003C 10)) || echo \"not big\"   # not big — exit 1 (false), so || fires\n\n# ── As expansion (returns the value) ──\nresult=$((x * 3))     # result is 12 — $((...)) expands to the computed value\necho \"$((17 % 5))\"    # 2 — modulo\necho \"$((17 \u002F 5))\"    # 3 — integer division (truncates toward zero)\necho \"$((-17 \u002F 5))\"   # -3 — truncates toward ZERO (not floor!), so -17\u002F5 = -3.4 → -3\necho \"$((2 ** 10))\"   # 1024 — exponentiation\necho \"$((5 \u003C\u003C 3))\"    # 40 — bitwise left shift (5 * 2^3)\necho \"$((0xFF))\"      # 255 — hex literal\necho \"$((0b1010))\"    # 10 — binary literal (Bash 2.21+... wait, actually Bash 4.1+? No, 0b is NOT supported)\n                       # ^-- 0b prefix is NOT supported in Bash arithmetic. Use $((2#1010)) instead.\necho \"$((2#1010))\"    # 10 — base#number notation (2# = binary, 8# = octal, 16# = hex)\necho \"$((8#777))\"     # 511 — octal\necho \"$((16#FF))\"     # 255 — hex (same as 0xFF)\n\n# ── Variables don't need $ inside ((...)) — but they CAN have it ──\na=5\n((a + 1))             # evaluates a+1=6, returns 0 (non-zero result)\necho \"$((a + 1))\"      # 6 — same\necho \"$(($a + 1))\"     # 6 — also works (but unnecessary, and can cause issues with array refs)\n","",[20,38,39,48,54,60,67,82,106,121,135,152,167,172,178,201,226,231,237,263,286,305,324,344,362,374,385,397,407,418,429,434,440,446,452,469],{"__ignoreMap":36},[40,41,44],"span",{"class":42,"line":43},"line",1,[40,45,47],{"class":46},"sdCPZ","# ── Two forms, two purposes ──\n",[40,49,51],{"class":42,"line":50},2,[40,52,53],{"class":46},"# ((expr))   → evaluates expr; returns exit status (0 if result != 0, 1 if result == 0)\n",[40,55,57],{"class":42,"line":56},3,[40,58,59],{"class":46},"# $((expr))  → evaluates expr; EXPANDS to the result (used in commands\u002Fassignments)\n",[40,61,63],{"class":42,"line":62},4,[40,64,66],{"emptyLinePlaceholder":65},true,"\n",[40,68,70,74,78],{"class":42,"line":69},5,[40,71,73],{"class":72},"ssxIu","x",[40,75,77],{"class":76},"svdQ7","=",[40,79,81],{"class":80},"sJ6F3","10\n",[40,83,85,88,90,93,96,100,103],{"class":42,"line":84},6,[40,86,87],{"class":72},"((x ",[40,89,77],{"class":76},[40,91,92],{"class":72}," x ",[40,94,95],{"class":76},"+",[40,97,99],{"class":98},"snvgF"," 5",[40,101,102],{"class":72},"))         ",[40,104,105],{"class":46},"# x is now 15 — assignment in arithmetic context (no $ needed)\n",[40,107,109,112,115,118],{"class":42,"line":108},7,[40,110,111],{"class":72},"((x",[40,113,114],{"class":76},"++",[40,116,117],{"class":72},"))               ",[40,119,120],{"class":46},"# 16 — postfix increment (returns old value, increments after)\n",[40,122,124,127,129,132],{"class":42,"line":123},8,[40,125,126],{"class":72},"((",[40,128,114],{"class":76},[40,130,131],{"class":72},"x))               ",[40,133,134],{"class":46},"# 17 — prefix increment (increments first, returns new value)\n",[40,136,138,140,143,146,149],{"class":42,"line":137},9,[40,139,87],{"class":72},[40,141,142],{"class":76},"*=",[40,144,145],{"class":98}," 2",[40,147,148],{"class":72},"))            ",[40,150,151],{"class":46},"# 34 — compound assignment\n",[40,153,155,157,160,162,164],{"class":42,"line":154},10,[40,156,87],{"class":72},[40,158,159],{"class":76},"%=",[40,161,99],{"class":98},[40,163,148],{"class":72},[40,165,166],{"class":46},"# 4  — 34 % 5 = 4\n",[40,168,170],{"class":42,"line":169},11,[40,171,66],{"emptyLinePlaceholder":65},[40,173,175],{"class":42,"line":174},12,[40,176,177],{"class":46},"# ── As a condition (returns exit status) ──\n",[40,179,181,183,186,189,192,195,198],{"class":42,"line":180},13,[40,182,87],{"class":72},[40,184,185],{"class":76},">",[40,187,188],{"class":98}," 10",[40,190,191],{"class":72},")) && ",[40,193,194],{"class":98},"echo",[40,196,197],{"class":80}," \"big\"",[40,199,200],{"class":46},"       # big — exit 0 (true) since 17 > 10\n",[40,202,204,206,209,211,214,217,220,223],{"class":42,"line":203},14,[40,205,87],{"class":72},[40,207,208],{"class":76},"\u003C",[40,210,188],{"class":98},[40,212,213],{"class":72},")) ",[40,215,216],{"class":76},"||",[40,218,219],{"class":98}," echo",[40,221,222],{"class":80}," \"not big\"",[40,224,225],{"class":46},"   # not big — exit 1 (false), so || fires\n",[40,227,229],{"class":42,"line":228},15,[40,230,66],{"emptyLinePlaceholder":65},[40,232,234],{"class":42,"line":233},16,[40,235,236],{"class":46},"# ── As expansion (returns the value) ──\n",[40,238,240,243,245,248,251,254,257,260],{"class":42,"line":239},17,[40,241,242],{"class":72},"result",[40,244,77],{"class":76},[40,246,247],{"class":72},"$((",[40,249,73],{"class":250},"sIsaT",[40,252,253],{"class":98}," *",[40,255,256],{"class":98}," 3",[40,258,259],{"class":72},"))     ",[40,261,262],{"class":46},"# result is 12 — $((...)) expands to the computed value\n",[40,264,266,268,271,274,277,280,283],{"class":42,"line":265},18,[40,267,194],{"class":98},[40,269,270],{"class":80}," \"$((",[40,272,273],{"class":250},"17",[40,275,276],{"class":80}," % ",[40,278,279],{"class":98},"5",[40,281,282],{"class":80},"))\"",[40,284,285],{"class":46},"    # 2 — modulo\n",[40,287,289,291,293,295,298,300,302],{"class":42,"line":288},19,[40,290,194],{"class":98},[40,292,270],{"class":80},[40,294,273],{"class":250},[40,296,297],{"class":80}," \u002F ",[40,299,279],{"class":98},[40,301,282],{"class":80},[40,303,304],{"class":46},"    # 3 — integer division (truncates toward zero)\n",[40,306,308,310,312,315,317,319,321],{"class":42,"line":307},20,[40,309,194],{"class":98},[40,311,270],{"class":80},[40,313,314],{"class":250},"-17",[40,316,297],{"class":80},[40,318,279],{"class":98},[40,320,282],{"class":80},[40,322,323],{"class":46},"   # -3 — truncates toward ZERO (not floor!), so -17\u002F5 = -3.4 → -3\n",[40,325,327,329,331,334,337,339,341],{"class":42,"line":326},21,[40,328,194],{"class":98},[40,330,270],{"class":80},[40,332,333],{"class":250},"2",[40,335,336],{"class":98}," **",[40,338,188],{"class":98},[40,340,282],{"class":80},[40,342,343],{"class":46},"   # 1024 — exponentiation\n",[40,345,347,349,351,353,356,359],{"class":42,"line":346},22,[40,348,194],{"class":98},[40,350,270],{"class":80},[40,352,279],{"class":250},[40,354,355],{"class":76}," \u003C\u003C",[40,357,358],{"class":80}," 3))",[40,360,361],{"class":250},"\"    # 40 — bitwise left shift (5 * 2^3)\n",[40,363,365,368,371],{"class":42,"line":364},23,[40,366,367],{"class":80},"echo \"$((",[40,369,370],{"class":250},"0xFF",[40,372,373],{"class":80},"))\"      # 255 — hex literal\n",[40,375,377,379,382],{"class":42,"line":376},24,[40,378,367],{"class":80},[40,380,381],{"class":250},"0b1010",[40,383,384],{"class":80},"))\"    # 10 — binary literal (Bash 2.21+... wait, actually Bash 4.1+? No, 0b is NOT supported)\n",[40,386,388,391,394],{"class":42,"line":387},25,[40,389,390],{"class":80},"                       # ^-- 0b prefix is NOT supported in Bash arithmetic. Use $((",[40,392,393],{"class":250},"2#1010",[40,395,396],{"class":80},")) instead.\n",[40,398,400,402,404],{"class":42,"line":399},26,[40,401,367],{"class":80},[40,403,393],{"class":250},[40,405,406],{"class":80},"))\"    # 10 — base#number notation (2# = binary, 8# = octal, 16# = hex)\n",[40,408,410,412,415],{"class":42,"line":409},27,[40,411,367],{"class":80},[40,413,414],{"class":250},"8#777",[40,416,417],{"class":80},"))\"     # 511 — octal\n",[40,419,421,423,426],{"class":42,"line":420},28,[40,422,367],{"class":80},[40,424,425],{"class":250},"16#FF",[40,427,428],{"class":80},"))\"     # 255 — hex (same as 0xFF)\n",[40,430,432],{"class":42,"line":431},29,[40,433,66],{"emptyLinePlaceholder":65},[40,435,437],{"class":42,"line":436},30,[40,438,439],{"class":80},"# ── Variables don't need $ inside ((...)) — but they CAN have it ──\n",[40,441,443],{"class":42,"line":442},31,[40,444,445],{"class":80},"a=5\n",[40,447,449],{"class":42,"line":448},32,[40,450,451],{"class":80},"((a + 1))             # evaluates a+1=6, returns 0 (non-zero result)\n",[40,453,455,457,460,463,466],{"class":42,"line":454},33,[40,456,367],{"class":80},[40,458,459],{"class":250},"a",[40,461,462],{"class":80}," + ",[40,464,465],{"class":98},"1",[40,467,468],{"class":80},"))\"      # 6 — same\n",[40,470,472,474,477],{"class":42,"line":471},34,[40,473,367],{"class":80},[40,475,476],{"class":72},"$a",[40,478,479],{"class":80}," + 1))\"     # 6 — also works (but unnecessary, and can cause issues with array refs)\n",[15,481,483,484,487,488,491],{"id":482},"anti-pattern-using-expr-or-let-for-arithmetic","Anti-Pattern: Using ",[20,485,486],{},"expr"," or ",[20,489,490],{},"let"," for Arithmetic",[28,493,494],{"language":30},[32,495,497],{"className":34,"code":496,"language":30,"meta":36,"style":36},"# ❌ SLOW — expr forks a subprocess per operation\nx=$(expr 5 + 3)          # forks \u002Fusr\u002Fbin\u002Fexpr — slow, error-prone with special chars\nx=$(expr \"$a\" \\* \"$b\")    # must escape * — it's a glob! And spaces required around operators.\n\n# ❌ LEGACY — let (works but ((...)) is clearer and more powerful)\nlet x=5+3                 # works, but ((x = 5 + 3)) is the modern idiom\n\n# ✅ CORRECT — ((...)) and $((...)) are in-process, no subprocess\n((x = 5 + 3))             # assignment — no subprocess\nx=$((5 + 3))              # expansion — no subprocess\n# Benchmark: 100,000 iterations\n#   x=$(expr 1 + 1):  ~12s (forking 100k subprocesses)\n#   x=$((1 + 1)):     ~0.3s (in-process) — 40x faster\n",[20,498,499,504,528,562,566,571,581,585,590,608,628,633,638],{"__ignoreMap":36},[40,500,501],{"class":42,"line":43},[40,502,503],{"class":46},"# ❌ SLOW — expr forks a subprocess per operation\n",[40,505,506,508,510,513,515,517,520,522,525],{"class":42,"line":50},[40,507,73],{"class":72},[40,509,77],{"class":76},[40,511,512],{"class":72},"$(",[40,514,486],{"class":250},[40,516,99],{"class":98},[40,518,519],{"class":80}," +",[40,521,256],{"class":98},[40,523,524],{"class":72},")          ",[40,526,527],{"class":46},"# forks \u002Fusr\u002Fbin\u002Fexpr — slow, error-prone with special chars\n",[40,529,530,532,534,536,538,541,543,546,549,551,554,556,559],{"class":42,"line":56},[40,531,73],{"class":72},[40,533,77],{"class":76},[40,535,512],{"class":72},[40,537,486],{"class":250},[40,539,540],{"class":80}," \"",[40,542,476],{"class":72},[40,544,545],{"class":80},"\"",[40,547,548],{"class":98}," \\*",[40,550,540],{"class":80},[40,552,553],{"class":72},"$b",[40,555,545],{"class":80},[40,557,558],{"class":72},")    ",[40,560,561],{"class":46},"# must escape * — it's a glob! And spaces required around operators.\n",[40,563,564],{"class":42,"line":62},[40,565,66],{"emptyLinePlaceholder":65},[40,567,568],{"class":42,"line":69},[40,569,570],{"class":46},"# ❌ LEGACY — let (works but ((...)) is clearer and more powerful)\n",[40,572,573,575,578],{"class":42,"line":84},[40,574,490],{"class":98},[40,576,577],{"class":80}," x=5+3",[40,579,580],{"class":46},"                 # works, but ((x = 5 + 3)) is the modern idiom\n",[40,582,583],{"class":42,"line":108},[40,584,66],{"emptyLinePlaceholder":65},[40,586,587],{"class":42,"line":123},[40,588,589],{"class":46},"# ✅ CORRECT — ((...)) and $((...)) are in-process, no subprocess\n",[40,591,592,594,596,598,600,602,605],{"class":42,"line":137},[40,593,87],{"class":72},[40,595,77],{"class":76},[40,597,99],{"class":98},[40,599,519],{"class":76},[40,601,256],{"class":98},[40,603,604],{"class":72},"))             ",[40,606,607],{"class":46},"# assignment — no subprocess\n",[40,609,610,612,614,616,618,620,622,625],{"class":42,"line":154},[40,611,73],{"class":72},[40,613,77],{"class":76},[40,615,247],{"class":72},[40,617,279],{"class":250},[40,619,519],{"class":80},[40,621,256],{"class":98},[40,623,624],{"class":72},"))              ",[40,626,627],{"class":46},"# expansion — no subprocess\n",[40,629,630],{"class":42,"line":169},[40,631,632],{"class":46},"# Benchmark: 100,000 iterations\n",[40,634,635],{"class":42,"line":174},[40,636,637],{"class":46},"#   x=$(expr 1 + 1):  ~12s (forking 100k subprocesses)\n",[40,639,640],{"class":42,"line":180},[40,641,642],{"class":46},"#   x=$((1 + 1)):     ~0.3s (in-process) — 40x faster\n",[15,644,646,648],{"id":645},"exit-status-quirk",[20,647,22],{}," Exit Status Quirk",[28,650,651],{"language":30},[32,652,654],{"className":34,"code":653,"language":30,"meta":36,"style":36},"# ── ((expr)) returns exit 0 if the RESULT is non-zero, exit 1 if the RESULT is zero ──\n# This is C-style \"0 is false, non-zero is true\" — but it can bite you:\n\n((0))                    # exit 1 — result is 0, which is \"false\"\n((1))                    # exit 0 — result is 1, which is \"true\"\n((x = 0))                # exit 1 — the ASSIGNMENT result is 0, so exit 1!\n#  ^-- with `set -e`, ((x = 0)) would EXIT the script (because exit status is 1)!\n#  Fix: ((x = 0)) || true  — explicitly allow zero result.\n\n# ── Increment to zero is a trap ──\nx=1\n((x--))                  # x is now 0, but the EXPRESSION returns the OLD value (1) → exit 0\n((x--))                  # x is now -1, expression returns 0 (old value) → exit 1!\n#  ^-- set -e would kill the script here because the expression evaluated to 0.\n\n# ── Safe pattern: always || true or use the assignment form ──\n((x++)) || true          # always safe — can't trigger set -e\n: $((x++))               # : is a no-op that discards the expansion — no exit status issue\n",[20,655,656,661,666,670,683,694,709,714,719,723,728,737,750,761,766,770,775,791],{"__ignoreMap":36},[40,657,658],{"class":42,"line":43},[40,659,660],{"class":46},"# ── ((expr)) returns exit 0 if the RESULT is non-zero, exit 1 if the RESULT is zero ──\n",[40,662,663],{"class":42,"line":50},[40,664,665],{"class":46},"# This is C-style \"0 is false, non-zero is true\" — but it can bite you:\n",[40,667,668],{"class":42,"line":56},[40,669,66],{"emptyLinePlaceholder":65},[40,671,672,674,677,680],{"class":42,"line":62},[40,673,126],{"class":72},[40,675,676],{"class":98},"0",[40,678,679],{"class":72},"))                    ",[40,681,682],{"class":46},"# exit 1 — result is 0, which is \"false\"\n",[40,684,685,687,689,691],{"class":42,"line":69},[40,686,126],{"class":72},[40,688,465],{"class":98},[40,690,679],{"class":72},[40,692,693],{"class":46},"# exit 0 — result is 1, which is \"true\"\n",[40,695,696,698,700,703,706],{"class":42,"line":84},[40,697,87],{"class":72},[40,699,77],{"class":76},[40,701,702],{"class":98}," 0",[40,704,705],{"class":72},"))                ",[40,707,708],{"class":46},"# exit 1 — the ASSIGNMENT result is 0, so exit 1!\n",[40,710,711],{"class":42,"line":108},[40,712,713],{"class":46},"#  ^-- with `set -e`, ((x = 0)) would EXIT the script (because exit status is 1)!\n",[40,715,716],{"class":42,"line":123},[40,717,718],{"class":46},"#  Fix: ((x = 0)) || true  — explicitly allow zero result.\n",[40,720,721],{"class":42,"line":137},[40,722,66],{"emptyLinePlaceholder":65},[40,724,725],{"class":42,"line":154},[40,726,727],{"class":46},"# ── Increment to zero is a trap ──\n",[40,729,730,732,734],{"class":42,"line":169},[40,731,73],{"class":72},[40,733,77],{"class":76},[40,735,736],{"class":80},"1\n",[40,738,739,741,744,747],{"class":42,"line":174},[40,740,111],{"class":72},[40,742,743],{"class":76},"--",[40,745,746],{"class":72},"))                  ",[40,748,749],{"class":46},"# x is now 0, but the EXPRESSION returns the OLD value (1) → exit 0\n",[40,751,752,754,756,758],{"class":42,"line":180},[40,753,111],{"class":72},[40,755,743],{"class":76},[40,757,746],{"class":72},[40,759,760],{"class":46},"# x is now -1, expression returns 0 (old value) → exit 1!\n",[40,762,763],{"class":42,"line":203},[40,764,765],{"class":46},"#  ^-- set -e would kill the script here because the expression evaluated to 0.\n",[40,767,768],{"class":42,"line":228},[40,769,66],{"emptyLinePlaceholder":65},[40,771,772],{"class":42,"line":233},[40,773,774],{"class":46},"# ── Safe pattern: always || true or use the assignment form ──\n",[40,776,777,779,781,783,785,788],{"class":42,"line":239},[40,778,111],{"class":72},[40,780,114],{"class":76},[40,782,213],{"class":72},[40,784,216],{"class":76},[40,786,787],{"class":98}," true",[40,789,790],{"class":46},"          # always safe — can't trigger set -e\n",[40,792,793,796,799,802,804],{"class":42,"line":265},[40,794,795],{"class":98},":",[40,797,798],{"class":72}," $((",[40,800,801],{"class":250},"x++",[40,803,117],{"class":72},[40,805,806],{"class":46},"# : is a no-op that discards the expansion — no exit status issue\n",[15,808,810,811,23,814],{"id":809},"test-conditionals-vs","Test Conditionals: ",[20,812,813],{},"[[... ]]",[20,815,816],{},"[... ]",[28,818,819],{"language":30},[32,820,822],{"className":34,"code":821,"language":30,"meta":36,"style":36},"# ── [ ] is POSIX test (a command, not syntax) — needs careful quoting ──\n# ── [[ ]] is Bash-extended test (syntax, not a command) — safe without quoting ──\n\n# ── String comparison ──\nstr=\"hello\"\n[[ $str == \"hello\" ]]     # true — no quoting needed (no word splitting in [[ ]])\n[[ \"$str\" == \"hello\" ]]   # also true — quoting is safe but unnecessary\n[ \"$str\" = \"hello\" ]      # true — MUST quote in [ ] (or [ $str = hello ] breaks with empty str)\n# Note: [ ] uses `=` for string equality. [[ ]] accepts both `=` and `==` (use == for clarity).\n\n# ── Pattern matching (glob, RIGHT side only) ──\nfile=\"report_2024.txt\"\n[[ $file == *.txt ]]       # true — glob pattern on right side\n[[ $file == report_* ]]   # true — prefix glob\n[[ $file == *2024* ]]     # true — substring glob\n[[ $file != *.log ]]      # true — negated glob\n\n# ── Regex matching (extended regex, RIGHT side) ──\nemail=\"user@example.com\"\nre='^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'\n[[ $email =~ $re ]]       # true — store regex in variable for readability (and to avoid quoting issues)\necho \"${BASH_REMATCH[0]}\"  # user@example.com — full match\necho \"${BASH_REMATCH[1]}\"  # (empty — no capture groups in this regex)\n\ndate=\"2024-01-15\"\n[[ $date =~ ^([0-9]{4})-([0-9]{2})-([0-9]{2})$ ]]\necho \"${BASH_REMATCH[1]}\"  # 2024 — first capture group\necho \"${BASH_REMATCH[2]}\"  # 01 — second capture group\necho \"${BASH_REMATCH[3]}\"  # 15 — third capture group\n\n# ── ⚠️ Don't quote the regex — quoting makes it a literal string ──\n[[ $date =~ \"^([0-9]{4})\" ]]  # ✗ matches literal \"^([0-9]{4})\" — not a regex\n[[ $date =~ ^([0-9]{4}) ]]   # ✓ regex match (but hard to read inline)\n[[ $date =~ $re ]]            # ✓ BEST — store in variable, reference unquoted\n\n# ── && and || inside [[ ]] (not -a \u002F -o) ──\n[[ -f \"$file\" && -r \"$file\" ]]   # true if file exists AND is readable\n[[ $x -gt 0 || $x -lt -10 ]]    # true if x > 0 OR x \u003C -10\n# In [ ], you'd need: [ -f \"$file\" -a -r \"$file\" ] (deprecated, error-prone)\n\n# ── Integer comparison ──\n# Both [[ ]] and [ ] support -eq, -ne, -lt, -le, -gt, -ge for integers\n# But (( )) is better for arithmetic comparisons:\n[[ $x -gt 5 ]]   # works\n(( x > 5 ))      # cleaner, and supports arithmetic expressions: (( x > 5 && y \u003C 10 ))\n",[20,823,824,829,834,838,843,853,870,893,915,920,924,929,939,954,971,989,1004,1008,1013,1023,1033,1047,1063,1077,1081,1091,1143,1156,1170,1184,1188,1193,1208,1232,1244,1249,1255,1287,1316,1322,1327,1333,1339,1345,1359],{"__ignoreMap":36},[40,825,826],{"class":42,"line":43},[40,827,828],{"class":46},"# ── [ ] is POSIX test (a command, not syntax) — needs careful quoting ──\n",[40,830,831],{"class":42,"line":50},[40,832,833],{"class":46},"# ── [[ ]] is Bash-extended test (syntax, not a command) — safe without quoting ──\n",[40,835,836],{"class":42,"line":56},[40,837,66],{"emptyLinePlaceholder":65},[40,839,840],{"class":42,"line":62},[40,841,842],{"class":46},"# ── String comparison ──\n",[40,844,845,848,850],{"class":42,"line":69},[40,846,847],{"class":72},"str",[40,849,77],{"class":76},[40,851,852],{"class":80},"\"hello\"\n",[40,854,855,858,861,864,867],{"class":42,"line":84},[40,856,857],{"class":72},"[[ $str ",[40,859,860],{"class":76},"==",[40,862,863],{"class":80}," \"hello\"",[40,865,866],{"class":72}," ]]     ",[40,868,869],{"class":46},"# true — no quoting needed (no word splitting in [[ ]])\n",[40,871,872,875,877,880,882,885,887,890],{"class":42,"line":108},[40,873,874],{"class":72},"[[ ",[40,876,545],{"class":80},[40,878,879],{"class":72},"$str",[40,881,545],{"class":80},[40,883,884],{"class":76}," ==",[40,886,863],{"class":80},[40,888,889],{"class":72}," ]]   ",[40,891,892],{"class":46},"# also true — quoting is safe but unnecessary\n",[40,894,895,898,900,902,904,907,909,912],{"class":42,"line":123},[40,896,897],{"class":72},"[ ",[40,899,545],{"class":80},[40,901,879],{"class":72},[40,903,545],{"class":80},[40,905,906],{"class":76}," =",[40,908,863],{"class":80},[40,910,911],{"class":72}," ]      ",[40,913,914],{"class":46},"# true — MUST quote in [ ] (or [ $str = hello ] breaks with empty str)\n",[40,916,917],{"class":42,"line":137},[40,918,919],{"class":46},"# Note: [ ] uses `=` for string equality. [[ ]] accepts both `=` and `==` (use == for clarity).\n",[40,921,922],{"class":42,"line":154},[40,923,66],{"emptyLinePlaceholder":65},[40,925,926],{"class":42,"line":169},[40,927,928],{"class":46},"# ── Pattern matching (glob, RIGHT side only) ──\n",[40,930,931,934,936],{"class":42,"line":174},[40,932,933],{"class":72},"file",[40,935,77],{"class":76},[40,937,938],{"class":80},"\"report_2024.txt\"\n",[40,940,941,944,946,948,951],{"class":42,"line":180},[40,942,943],{"class":72},"[[ $file ",[40,945,860],{"class":76},[40,947,253],{"class":76},[40,949,950],{"class":72},".txt ]]       ",[40,952,953],{"class":46},"# true — glob pattern on right side\n",[40,955,956,958,960,963,966,968],{"class":42,"line":203},[40,957,943],{"class":72},[40,959,860],{"class":76},[40,961,962],{"class":72}," report_",[40,964,965],{"class":76},"*",[40,967,889],{"class":72},[40,969,970],{"class":46},"# true — prefix glob\n",[40,972,973,975,977,979,982,984,986],{"class":42,"line":228},[40,974,943],{"class":72},[40,976,860],{"class":76},[40,978,253],{"class":76},[40,980,981],{"class":72},"2024",[40,983,965],{"class":76},[40,985,866],{"class":72},[40,987,988],{"class":46},"# true — substring glob\n",[40,990,991,993,996,998,1001],{"class":42,"line":233},[40,992,943],{"class":72},[40,994,995],{"class":76},"!=",[40,997,253],{"class":76},[40,999,1000],{"class":72},".log ]]      ",[40,1002,1003],{"class":46},"# true — negated glob\n",[40,1005,1006],{"class":42,"line":239},[40,1007,66],{"emptyLinePlaceholder":65},[40,1009,1010],{"class":42,"line":265},[40,1011,1012],{"class":46},"# ── Regex matching (extended regex, RIGHT side) ──\n",[40,1014,1015,1018,1020],{"class":42,"line":288},[40,1016,1017],{"class":72},"email",[40,1019,77],{"class":76},[40,1021,1022],{"class":80},"\"user@example.com\"\n",[40,1024,1025,1028,1030],{"class":42,"line":307},[40,1026,1027],{"class":72},"re",[40,1029,77],{"class":76},[40,1031,1032],{"class":80},"'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'\n",[40,1034,1035,1038,1041,1044],{"class":42,"line":326},[40,1036,1037],{"class":72},"[[ $email ",[40,1039,1040],{"class":76},"=~",[40,1042,1043],{"class":72}," $re ]]       ",[40,1045,1046],{"class":46},"# true — store regex in variable for readability (and to avoid quoting issues)\n",[40,1048,1049,1051,1054,1057,1060],{"class":42,"line":346},[40,1050,194],{"class":98},[40,1052,1053],{"class":80}," \"${",[40,1055,1056],{"class":72},"BASH_REMATCH",[40,1058,1059],{"class":80},"[0]}\"",[40,1061,1062],{"class":46},"  # user@example.com — full match\n",[40,1064,1065,1067,1069,1071,1074],{"class":42,"line":364},[40,1066,194],{"class":98},[40,1068,1053],{"class":80},[40,1070,1056],{"class":72},[40,1072,1073],{"class":80},"[1]}\"",[40,1075,1076],{"class":46},"  # (empty — no capture groups in this regex)\n",[40,1078,1079],{"class":42,"line":376},[40,1080,66],{"emptyLinePlaceholder":65},[40,1082,1083,1086,1088],{"class":42,"line":387},[40,1084,1085],{"class":72},"date",[40,1087,77],{"class":76},[40,1089,1090],{"class":80},"\"2024-01-15\"\n",[40,1092,1093,1096,1098,1101,1103,1106,1109,1112,1115,1118,1120,1122,1124,1126,1128,1130,1132,1134,1136,1138,1140],{"class":42,"line":399},[40,1094,1095],{"class":72},"[[ $date ",[40,1097,1040],{"class":76},[40,1099,1100],{"class":72}," ^([",[40,1102,676],{"class":98},[40,1104,1105],{"class":76},"-",[40,1107,1108],{"class":98},"9",[40,1110,1111],{"class":72},"]{",[40,1113,1114],{"class":98},"4",[40,1116,1117],{"class":72},"})-([",[40,1119,676],{"class":98},[40,1121,1105],{"class":76},[40,1123,1108],{"class":98},[40,1125,1111],{"class":72},[40,1127,333],{"class":98},[40,1129,1117],{"class":72},[40,1131,676],{"class":98},[40,1133,1105],{"class":76},[40,1135,1108],{"class":98},[40,1137,1111],{"class":72},[40,1139,333],{"class":98},[40,1141,1142],{"class":72},"})$ ]]\n",[40,1144,1145,1147,1149,1151,1153],{"class":42,"line":409},[40,1146,194],{"class":98},[40,1148,1053],{"class":80},[40,1150,1056],{"class":72},[40,1152,1073],{"class":80},[40,1154,1155],{"class":46},"  # 2024 — first capture group\n",[40,1157,1158,1160,1162,1164,1167],{"class":42,"line":420},[40,1159,194],{"class":98},[40,1161,1053],{"class":80},[40,1163,1056],{"class":72},[40,1165,1166],{"class":80},"[2]}\"",[40,1168,1169],{"class":46},"  # 01 — second capture group\n",[40,1171,1172,1174,1176,1178,1181],{"class":42,"line":431},[40,1173,194],{"class":98},[40,1175,1053],{"class":80},[40,1177,1056],{"class":72},[40,1179,1180],{"class":80},"[3]}\"",[40,1182,1183],{"class":46},"  # 15 — third capture group\n",[40,1185,1186],{"class":42,"line":436},[40,1187,66],{"emptyLinePlaceholder":65},[40,1189,1190],{"class":42,"line":442},[40,1191,1192],{"class":46},"# ── ⚠️ Don't quote the regex — quoting makes it a literal string ──\n",[40,1194,1195,1197,1199,1202,1205],{"class":42,"line":448},[40,1196,1095],{"class":72},[40,1198,1040],{"class":76},[40,1200,1201],{"class":80}," \"^([0-9]{4})\"",[40,1203,1204],{"class":72}," ]]  ",[40,1206,1207],{"class":46},"# ✗ matches literal \"^([0-9]{4})\" — not a regex\n",[40,1209,1210,1212,1214,1216,1218,1220,1222,1224,1226,1229],{"class":42,"line":454},[40,1211,1095],{"class":72},[40,1213,1040],{"class":76},[40,1215,1100],{"class":72},[40,1217,676],{"class":98},[40,1219,1105],{"class":76},[40,1221,1108],{"class":98},[40,1223,1111],{"class":72},[40,1225,1114],{"class":98},[40,1227,1228],{"class":72},"}) ]]   ",[40,1230,1231],{"class":46},"# ✓ regex match (but hard to read inline)\n",[40,1233,1234,1236,1238,1241],{"class":42,"line":471},[40,1235,1095],{"class":72},[40,1237,1040],{"class":76},[40,1239,1240],{"class":72}," $re ]]            ",[40,1242,1243],{"class":46},"# ✓ BEST — store in variable, reference unquoted\n",[40,1245,1247],{"class":42,"line":1246},35,[40,1248,66],{"emptyLinePlaceholder":65},[40,1250,1252],{"class":42,"line":1251},36,[40,1253,1254],{"class":46},"# ── && and || inside [[ ]] (not -a \u002F -o) ──\n",[40,1256,1258,1260,1263,1265,1268,1270,1273,1276,1278,1280,1282,1284],{"class":42,"line":1257},37,[40,1259,874],{"class":72},[40,1261,1262],{"class":76},"-f",[40,1264,540],{"class":80},[40,1266,1267],{"class":72},"$file",[40,1269,545],{"class":80},[40,1271,1272],{"class":76}," &&",[40,1274,1275],{"class":76}," -r",[40,1277,540],{"class":80},[40,1279,1267],{"class":72},[40,1281,545],{"class":80},[40,1283,889],{"class":72},[40,1285,1286],{"class":46},"# true if file exists AND is readable\n",[40,1288,1290,1293,1296,1298,1301,1304,1307,1310,1313],{"class":42,"line":1289},38,[40,1291,1292],{"class":72},"[[ $x ",[40,1294,1295],{"class":76},"-gt",[40,1297,702],{"class":98},[40,1299,1300],{"class":76}," ||",[40,1302,1303],{"class":72}," $x ",[40,1305,1306],{"class":76},"-lt",[40,1308,1309],{"class":98}," -10",[40,1311,1312],{"class":72}," ]]    ",[40,1314,1315],{"class":46},"# true if x > 0 OR x \u003C -10\n",[40,1317,1319],{"class":42,"line":1318},39,[40,1320,1321],{"class":46},"# In [ ], you'd need: [ -f \"$file\" -a -r \"$file\" ] (deprecated, error-prone)\n",[40,1323,1325],{"class":42,"line":1324},40,[40,1326,66],{"emptyLinePlaceholder":65},[40,1328,1330],{"class":42,"line":1329},41,[40,1331,1332],{"class":46},"# ── Integer comparison ──\n",[40,1334,1336],{"class":42,"line":1335},42,[40,1337,1338],{"class":46},"# Both [[ ]] and [ ] support -eq, -ne, -lt, -le, -gt, -ge for integers\n",[40,1340,1342],{"class":42,"line":1341},43,[40,1343,1344],{"class":46},"# But (( )) is better for arithmetic comparisons:\n",[40,1346,1348,1350,1352,1354,1356],{"class":42,"line":1347},44,[40,1349,1292],{"class":72},[40,1351,1295],{"class":76},[40,1353,99],{"class":98},[40,1355,889],{"class":72},[40,1357,1358],{"class":46},"# works\n",[40,1360,1362,1365,1367,1369,1372],{"class":42,"line":1361},45,[40,1363,1364],{"class":72},"(( x ",[40,1366,185],{"class":76},[40,1368,99],{"class":98},[40,1370,1371],{"class":72}," ))      ",[40,1373,1374],{"class":46},"# cleaner, and supports arithmetic expressions: (( x > 5 && y \u003C 10 ))\n",[15,1376,1378],{"id":1377},"file-test-operators-complete-reference","File Test Operators (Complete Reference)",[28,1380,1381],{"language":30},[32,1382,1384],{"className":34,"code":1383,"language":30,"meta":36,"style":36},"# ── Existence and type ──\n[[ -e $f ]]    # exists (any type: file, dir, symlink, socket, device)\n[[ -f $f ]]    # exists AND is a regular file (follows symlinks)\n[[ -d $f ]]    # exists AND is a directory\n[[ -L $f ]]    # is a symbolic link (does NOT follow — tests the link itself)\n[[ -S $f ]]    # is a socket\n[[ -p $f ]]    # is a named pipe (FIFO)\n[[ -b $f ]]    # is a block device\n[[ -c $f ]]    # is a character device\n\n# ── Permissions (tests the EFFECTIVE user's permissions) ──\n[[ -r $f ]]    # readable\n[[ -w $f ]]    # writable\n[[ -x $f ]]    # executable \u002F searchable (for dirs)\n[[ -u $f ]]    # has setuid bit\n[[ -g $f ]]    # has setgid bit\n[[ -k $f ]]    # has sticky bit\n\n# ── Content ──\n[[ -s $f ]]    # exists AND size > 0 (not empty)\n\n# ── Comparison ──\n[[ $f1 -nt $f2 ]]  # f1 is NEWER than f2 (by mtime — or exists when f2 doesn't)\n[[ $f1 -ot $f2 ]]  # f1 is OLDER than f2\n[[ $f1 -ef $f2 ]]  # f1 and f2 are the SAME file (same inode — hard links or same path)\n\n# ── Edge: -e follows symlinks, -L doesn't ──\nln -s \u002Fnonexistent broken_link\n[[ -e broken_link ]]  # false — target doesn't exist\n[[ -L broken_link ]]  # true  — the link itself exists\n# To check if a symlink points to a valid target:\n[[ -L $link && -e $link ]]  # is a symlink AND target exists\n",[20,1385,1386,1391,1404,1415,1427,1439,1451,1463,1475,1487,1491,1496,1508,1520,1532,1544,1556,1568,1572,1577,1589,1593,1598,1612,1624,1636,1640,1645,1659,1671,1682,1687],{"__ignoreMap":36},[40,1387,1388],{"class":42,"line":43},[40,1389,1390],{"class":46},"# ── Existence and type ──\n",[40,1392,1393,1395,1398,1401],{"class":42,"line":50},[40,1394,874],{"class":72},[40,1396,1397],{"class":76},"-e",[40,1399,1400],{"class":72}," $f ]]    ",[40,1402,1403],{"class":46},"# exists (any type: file, dir, symlink, socket, device)\n",[40,1405,1406,1408,1410,1412],{"class":42,"line":56},[40,1407,874],{"class":72},[40,1409,1262],{"class":76},[40,1411,1400],{"class":72},[40,1413,1414],{"class":46},"# exists AND is a regular file (follows symlinks)\n",[40,1416,1417,1419,1422,1424],{"class":42,"line":62},[40,1418,874],{"class":72},[40,1420,1421],{"class":76},"-d",[40,1423,1400],{"class":72},[40,1425,1426],{"class":46},"# exists AND is a directory\n",[40,1428,1429,1431,1434,1436],{"class":42,"line":69},[40,1430,874],{"class":72},[40,1432,1433],{"class":76},"-L",[40,1435,1400],{"class":72},[40,1437,1438],{"class":46},"# is a symbolic link (does NOT follow — tests the link itself)\n",[40,1440,1441,1443,1446,1448],{"class":42,"line":84},[40,1442,874],{"class":72},[40,1444,1445],{"class":76},"-S",[40,1447,1400],{"class":72},[40,1449,1450],{"class":46},"# is a socket\n",[40,1452,1453,1455,1458,1460],{"class":42,"line":108},[40,1454,874],{"class":72},[40,1456,1457],{"class":76},"-p",[40,1459,1400],{"class":72},[40,1461,1462],{"class":46},"# is a named pipe (FIFO)\n",[40,1464,1465,1467,1470,1472],{"class":42,"line":123},[40,1466,874],{"class":72},[40,1468,1469],{"class":76},"-b",[40,1471,1400],{"class":72},[40,1473,1474],{"class":46},"# is a block device\n",[40,1476,1477,1479,1482,1484],{"class":42,"line":137},[40,1478,874],{"class":72},[40,1480,1481],{"class":76},"-c",[40,1483,1400],{"class":72},[40,1485,1486],{"class":46},"# is a character device\n",[40,1488,1489],{"class":42,"line":154},[40,1490,66],{"emptyLinePlaceholder":65},[40,1492,1493],{"class":42,"line":169},[40,1494,1495],{"class":46},"# ── Permissions (tests the EFFECTIVE user's permissions) ──\n",[40,1497,1498,1500,1503,1505],{"class":42,"line":174},[40,1499,874],{"class":72},[40,1501,1502],{"class":76},"-r",[40,1504,1400],{"class":72},[40,1506,1507],{"class":46},"# readable\n",[40,1509,1510,1512,1515,1517],{"class":42,"line":180},[40,1511,874],{"class":72},[40,1513,1514],{"class":76},"-w",[40,1516,1400],{"class":72},[40,1518,1519],{"class":46},"# writable\n",[40,1521,1522,1524,1527,1529],{"class":42,"line":203},[40,1523,874],{"class":72},[40,1525,1526],{"class":76},"-x",[40,1528,1400],{"class":72},[40,1530,1531],{"class":46},"# executable \u002F searchable (for dirs)\n",[40,1533,1534,1536,1539,1541],{"class":42,"line":228},[40,1535,874],{"class":72},[40,1537,1538],{"class":76},"-u",[40,1540,1400],{"class":72},[40,1542,1543],{"class":46},"# has setuid bit\n",[40,1545,1546,1548,1551,1553],{"class":42,"line":233},[40,1547,874],{"class":72},[40,1549,1550],{"class":76},"-g",[40,1552,1400],{"class":72},[40,1554,1555],{"class":46},"# has setgid bit\n",[40,1557,1558,1560,1563,1565],{"class":42,"line":239},[40,1559,874],{"class":72},[40,1561,1562],{"class":76},"-k",[40,1564,1400],{"class":72},[40,1566,1567],{"class":46},"# has sticky bit\n",[40,1569,1570],{"class":42,"line":265},[40,1571,66],{"emptyLinePlaceholder":65},[40,1573,1574],{"class":42,"line":288},[40,1575,1576],{"class":46},"# ── Content ──\n",[40,1578,1579,1581,1584,1586],{"class":42,"line":307},[40,1580,874],{"class":72},[40,1582,1583],{"class":76},"-s",[40,1585,1400],{"class":72},[40,1587,1588],{"class":46},"# exists AND size > 0 (not empty)\n",[40,1590,1591],{"class":42,"line":326},[40,1592,66],{"emptyLinePlaceholder":65},[40,1594,1595],{"class":42,"line":346},[40,1596,1597],{"class":46},"# ── Comparison ──\n",[40,1599,1600,1603,1606,1609],{"class":42,"line":364},[40,1601,1602],{"class":72},"[[ $f1 ",[40,1604,1605],{"class":76},"-nt",[40,1607,1608],{"class":72}," $f2 ]]  ",[40,1610,1611],{"class":46},"# f1 is NEWER than f2 (by mtime — or exists when f2 doesn't)\n",[40,1613,1614,1616,1619,1621],{"class":42,"line":376},[40,1615,1602],{"class":72},[40,1617,1618],{"class":76},"-ot",[40,1620,1608],{"class":72},[40,1622,1623],{"class":46},"# f1 is OLDER than f2\n",[40,1625,1626,1628,1631,1633],{"class":42,"line":387},[40,1627,1602],{"class":72},[40,1629,1630],{"class":76},"-ef",[40,1632,1608],{"class":72},[40,1634,1635],{"class":46},"# f1 and f2 are the SAME file (same inode — hard links or same path)\n",[40,1637,1638],{"class":42,"line":399},[40,1639,66],{"emptyLinePlaceholder":65},[40,1641,1642],{"class":42,"line":409},[40,1643,1644],{"class":46},"# ── Edge: -e follows symlinks, -L doesn't ──\n",[40,1646,1647,1650,1653,1656],{"class":42,"line":420},[40,1648,1649],{"class":250},"ln",[40,1651,1652],{"class":98}," -s",[40,1654,1655],{"class":80}," \u002Fnonexistent",[40,1657,1658],{"class":80}," broken_link\n",[40,1660,1661,1663,1665,1668],{"class":42,"line":431},[40,1662,874],{"class":72},[40,1664,1397],{"class":76},[40,1666,1667],{"class":72}," broken_link ]]  ",[40,1669,1670],{"class":46},"# false — target doesn't exist\n",[40,1672,1673,1675,1677,1679],{"class":42,"line":436},[40,1674,874],{"class":72},[40,1676,1433],{"class":76},[40,1678,1667],{"class":72},[40,1680,1681],{"class":46},"# true  — the link itself exists\n",[40,1683,1684],{"class":42,"line":442},[40,1685,1686],{"class":46},"# To check if a symlink points to a valid target:\n",[40,1688,1689,1691,1693,1696,1699,1702,1705],{"class":42,"line":448},[40,1690,874],{"class":72},[40,1692,1433],{"class":76},[40,1694,1695],{"class":72}," $link ",[40,1697,1698],{"class":76},"&&",[40,1700,1701],{"class":76}," -e",[40,1703,1704],{"class":72}," $link ]]  ",[40,1706,1707],{"class":46},"# is a symlink AND target exists\n",[15,1709,1711,1712,1715],{"id":1710},"command-substitution-internals","Command Substitution: ",[20,1713,1714],{},"$(...)"," Internals",[28,1717,1718],{"language":30},[32,1719,1721],{"className":34,"code":1720,"language":30,"meta":36,"style":36},"# ── $(...) runs a command in a subshell, captures stdout, strips trailing newlines ──\ndate_output=$(date +%Y-%m-%d)   # \"2024-01-15\" — trailing newline stripped\nmultiline=$(echo -e \"a\\nb\\nc\")  # \"a\\nb\\nc\" — internal newlines preserved, trailing stripped\n\n# ── Nested command substitution (backticks can't nest cleanly) ──\n# ❌ LEGACY — backticks (hard to nest, hard to read)\nfiles=`ls \\`pwd\\``\n# ✅ MODERN — $(...) nests cleanly\nfiles=$(ls $(pwd))\ninner=$(echo \"outer: $(echo \"inner: $(date +%H:%M)\")\")  # \"outer: inner: 14:30\"\n\n# ── Trailing newline stripping is significant ──\noutput=$(printf 'line1\\nline2\\n\\n\\n')  # trailing \\n\\n\\n are ALL stripped → \"line1\\nline2\"\n# To preserve: add a sentinel and strip it:\noutput=$(printf 'line1\\nline2\\n\\n\\n'; printf 'X')  # \"line1\\nline2\\n\\n\\nX\"\noutput=\"${output%X}\"  # \"line1\\nline2\\n\\n\\n\" — preserved!\n\n# ── Command substitution runs in a subshell — variables don't leak ──\nx=before\noutput=$(x=after; echo \"$x\")  # output is \"after\"\necho \"$x\"                      # before — the subshell's x=after didn't leak\n\n# ── `set -e` is inherited by subshells in Bash 4.4+ (inherit_errexit) ──\nset -e\n# Without inherit_errexit: x=$(false); echo \"reached\"  → \"reached\" (false's failure is masked!)\n# With inherit_errexit:   x=$(false); echo \"reached\"  → script exits (false propagates)\nshopt -s inherit_errexit  # enable this (Bash 4.4+) — makes $(...) respect set -e\n",[20,1722,1723,1728,1748,1770,1774,1779,1784,1809,1814,1832,1861,1865,1870,1890,1895,1920,1943,1947,1952,1961,1991,2004,2008,2013,2021,2026,2031],{"__ignoreMap":36},[40,1724,1725],{"class":42,"line":43},[40,1726,1727],{"class":46},"# ── $(...) runs a command in a subshell, captures stdout, strips trailing newlines ──\n",[40,1729,1730,1733,1735,1737,1739,1742,1745],{"class":42,"line":50},[40,1731,1732],{"class":72},"date_output",[40,1734,77],{"class":76},[40,1736,512],{"class":72},[40,1738,1085],{"class":250},[40,1740,1741],{"class":80}," +%Y-%m-%d",[40,1743,1744],{"class":72},")   ",[40,1746,1747],{"class":46},"# \"2024-01-15\" — trailing newline stripped\n",[40,1749,1750,1753,1755,1757,1759,1761,1764,1767],{"class":42,"line":56},[40,1751,1752],{"class":72},"multiline",[40,1754,77],{"class":76},[40,1756,512],{"class":72},[40,1758,194],{"class":98},[40,1760,1701],{"class":98},[40,1762,1763],{"class":80}," \"a\\nb\\nc\"",[40,1765,1766],{"class":72},")  ",[40,1768,1769],{"class":46},"# \"a\\nb\\nc\" — internal newlines preserved, trailing stripped\n",[40,1771,1772],{"class":42,"line":62},[40,1773,66],{"emptyLinePlaceholder":65},[40,1775,1776],{"class":42,"line":69},[40,1777,1778],{"class":46},"# ── Nested command substitution (backticks can't nest cleanly) ──\n",[40,1780,1781],{"class":42,"line":84},[40,1782,1783],{"class":46},"# ❌ LEGACY — backticks (hard to nest, hard to read)\n",[40,1785,1786,1789,1791,1794,1797,1800,1803,1806],{"class":42,"line":108},[40,1787,1788],{"class":72},"files",[40,1790,77],{"class":76},[40,1792,1793],{"class":80},"`",[40,1795,1796],{"class":250},"ls",[40,1798,1799],{"class":98}," \\`",[40,1801,1802],{"class":80},"pwd",[40,1804,1805],{"class":98},"\\`",[40,1807,1808],{"class":80},"`\n",[40,1810,1811],{"class":42,"line":123},[40,1812,1813],{"class":46},"# ✅ MODERN — $(...) nests cleanly\n",[40,1815,1816,1818,1820,1822,1824,1827,1829],{"class":42,"line":137},[40,1817,1788],{"class":72},[40,1819,77],{"class":76},[40,1821,512],{"class":72},[40,1823,1796],{"class":250},[40,1825,1826],{"class":72}," $(",[40,1828,1802],{"class":98},[40,1830,1831],{"class":72},"))\n",[40,1833,1834,1837,1839,1841,1843,1846,1848,1851,1853,1856,1858],{"class":42,"line":154},[40,1835,1836],{"class":72},"inner",[40,1838,77],{"class":76},[40,1840,512],{"class":72},[40,1842,194],{"class":98},[40,1844,1845],{"class":80}," \"outer: $(",[40,1847,194],{"class":98},[40,1849,1850],{"class":80}," \"inner: $(",[40,1852,1085],{"class":250},[40,1854,1855],{"class":80}," +%H:%M)\")\"",[40,1857,1766],{"class":72},[40,1859,1860],{"class":46},"# \"outer: inner: 14:30\"\n",[40,1862,1863],{"class":42,"line":169},[40,1864,66],{"emptyLinePlaceholder":65},[40,1866,1867],{"class":42,"line":174},[40,1868,1869],{"class":46},"# ── Trailing newline stripping is significant ──\n",[40,1871,1872,1875,1877,1879,1882,1885,1887],{"class":42,"line":180},[40,1873,1874],{"class":72},"output",[40,1876,77],{"class":76},[40,1878,512],{"class":72},[40,1880,1881],{"class":98},"printf",[40,1883,1884],{"class":80}," 'line1\\nline2\\n\\n\\n'",[40,1886,1766],{"class":72},[40,1888,1889],{"class":46},"# trailing \\n\\n\\n are ALL stripped → \"line1\\nline2\"\n",[40,1891,1892],{"class":42,"line":203},[40,1893,1894],{"class":46},"# To preserve: add a sentinel and strip it:\n",[40,1896,1897,1899,1901,1903,1905,1907,1910,1912,1915,1917],{"class":42,"line":228},[40,1898,1874],{"class":72},[40,1900,77],{"class":76},[40,1902,512],{"class":72},[40,1904,1881],{"class":98},[40,1906,1884],{"class":80},[40,1908,1909],{"class":72},"; ",[40,1911,1881],{"class":98},[40,1913,1914],{"class":80}," 'X'",[40,1916,1766],{"class":72},[40,1918,1919],{"class":46},"# \"line1\\nline2\\n\\n\\nX\"\n",[40,1921,1922,1924,1926,1929,1931,1934,1937,1940],{"class":42,"line":233},[40,1923,1874],{"class":72},[40,1925,77],{"class":76},[40,1927,1928],{"class":80},"\"${",[40,1930,1874],{"class":72},[40,1932,1933],{"class":76},"%",[40,1935,1936],{"class":72},"X",[40,1938,1939],{"class":80},"}\"",[40,1941,1942],{"class":46},"  # \"line1\\nline2\\n\\n\\n\" — preserved!\n",[40,1944,1945],{"class":42,"line":239},[40,1946,66],{"emptyLinePlaceholder":65},[40,1948,1949],{"class":42,"line":265},[40,1950,1951],{"class":46},"# ── Command substitution runs in a subshell — variables don't leak ──\n",[40,1953,1954,1956,1958],{"class":42,"line":288},[40,1955,73],{"class":72},[40,1957,77],{"class":76},[40,1959,1960],{"class":80},"before\n",[40,1962,1963,1965,1967,1970,1972,1975,1977,1979,1981,1984,1986,1988],{"class":42,"line":307},[40,1964,1874],{"class":72},[40,1966,77],{"class":76},[40,1968,1969],{"class":72},"$(x",[40,1971,77],{"class":76},[40,1973,1974],{"class":80},"after",[40,1976,1909],{"class":72},[40,1978,194],{"class":98},[40,1980,540],{"class":80},[40,1982,1983],{"class":72},"$x",[40,1985,545],{"class":80},[40,1987,1766],{"class":72},[40,1989,1990],{"class":46},"# output is \"after\"\n",[40,1992,1993,1995,1997,1999,2001],{"class":42,"line":326},[40,1994,194],{"class":98},[40,1996,540],{"class":80},[40,1998,1983],{"class":72},[40,2000,545],{"class":80},[40,2002,2003],{"class":46},"                      # before — the subshell's x=after didn't leak\n",[40,2005,2006],{"class":42,"line":346},[40,2007,66],{"emptyLinePlaceholder":65},[40,2009,2010],{"class":42,"line":364},[40,2011,2012],{"class":46},"# ── `set -e` is inherited by subshells in Bash 4.4+ (inherit_errexit) ──\n",[40,2014,2015,2018],{"class":42,"line":376},[40,2016,2017],{"class":98},"set",[40,2019,2020],{"class":98}," -e\n",[40,2022,2023],{"class":42,"line":387},[40,2024,2025],{"class":46},"# Without inherit_errexit: x=$(false); echo \"reached\"  → \"reached\" (false's failure is masked!)\n",[40,2027,2028],{"class":42,"line":399},[40,2029,2030],{"class":46},"# With inherit_errexit:   x=$(false); echo \"reached\"  → script exits (false propagates)\n",[40,2032,2033,2036,2038,2041],{"class":42,"line":409},[40,2034,2035],{"class":250},"shopt",[40,2037,1652],{"class":98},[40,2039,2040],{"class":80}," inherit_errexit",[40,2042,2043],{"class":46},"  # enable this (Bash 4.4+) — makes $(...) respect set -e\n",[15,2045,2047,2048,2051,2052],{"id":2046},"process-substitution-and","Process Substitution: ",[20,2049,2050],{},"\u003C(...)"," and ",[20,2053,2054],{},">(...)",[28,2056,2057],{"language":30},[32,2058,2060],{"className":34,"code":2059,"language":30,"meta":36,"style":36},"# ── \u003C(cmd) exposes cmd's output as a file descriptor (\u002Fdev\u002Ffd\u002F63 or a named pipe) ──\n# The cmd runs in a subshell; its stdout connects to a fd that looks like a file.\n\n# ── Diff two command outputs without temp files ──\ndiff \u003C(sort file1.txt) \u003C(sort file2.txt)\n# Equivalent without process substitution:\n# sort file1.txt > \u002Ftmp\u002Fa; sort file2.txt > \u002Ftmp\u002Fb; diff \u002Ftmp\u002Fa \u002Ftmp\u002Fb; rm \u002Ftmp\u002Fa \u002Ftmp\u002Fb\n\n# ── Feed a while loop without the subshell trap ──\n# ❌ PIPE TRAP: while loop runs in a subshell — variables don't persist\necho \"1 2 3\" | while read -r num; do total=$((total + num)); done\necho \"$total\"  # empty — subshell variable lost\n\n# ✅ PROCESS SUBSTITUTION: while loop runs in the MAIN shell\ntotal=0\nwhile read -r num; do\n    total=$((total + num))\ndone \u003C \u003C(echo \"1 2 3\")\necho \"$total\"  # 6 — persists (no subshell for the while body)\n\n# ── Compare files from different servers ──\ndiff \u003C(ssh server1 cat \u002Fetc\u002Fhosts) \u003C(ssh server2 cat \u002Fetc\u002Fhosts)\n\n# ── >(...) writes to a command's stdin (less common) ──\n# Tee output to both a file and a pipeline:\nmake 2> >(grep -i error >&2) > build.log\n# stderr goes to grep (which sends matches to stderr), stdout goes to build.log\n\n# ── Multiple process substitutions ──\npaste \u003C(cut -d, -f1 data.csv) \u003C(cut -d, -f3 data.csv)  # side-by-side from different columns\n\n# ── Edge: process substitution fds are ephemeral ──\n# The \u002Fdev\u002Ffd\u002F63 path is valid only during the command's execution.\n# Storing it in a variable and using it later FAILS:\nfd=$(\u003C(echo hello))   # fd=\"\u002Fdev\u002Ffd\u002F63\" — but the fd is already closed!\ncat \"$fd\"              # cat: \u002Fdev\u002Ffd\u002F63: No such file or directory\n",[20,2061,2062,2067,2072,2076,2081,2102,2107,2112,2116,2121,2126,2172,2186,2190,2195,2204,2220,2237,2252,2265,2269,2274,2293,2297,2302,2307,2339,2344,2348,2353,2386,2390,2395,2400,2405,2427],{"__ignoreMap":36},[40,2063,2064],{"class":42,"line":43},[40,2065,2066],{"class":46},"# ── \u003C(cmd) exposes cmd's output as a file descriptor (\u002Fdev\u002Ffd\u002F63 or a named pipe) ──\n",[40,2068,2069],{"class":42,"line":50},[40,2070,2071],{"class":46},"# The cmd runs in a subshell; its stdout connects to a fd that looks like a file.\n",[40,2073,2074],{"class":42,"line":56},[40,2075,66],{"emptyLinePlaceholder":65},[40,2077,2078],{"class":42,"line":62},[40,2079,2080],{"class":46},"# ── Diff two command outputs without temp files ──\n",[40,2082,2083,2086,2089,2092,2095,2097,2099],{"class":42,"line":69},[40,2084,2085],{"class":250},"diff",[40,2087,2088],{"class":80}," \u003C(",[40,2090,2091],{"class":250},"sort",[40,2093,2094],{"class":80}," file1.txt)",[40,2096,2088],{"class":80},[40,2098,2091],{"class":250},[40,2100,2101],{"class":80}," file2.txt)\n",[40,2103,2104],{"class":42,"line":84},[40,2105,2106],{"class":46},"# Equivalent without process substitution:\n",[40,2108,2109],{"class":42,"line":108},[40,2110,2111],{"class":46},"# sort file1.txt > \u002Ftmp\u002Fa; sort file2.txt > \u002Ftmp\u002Fb; diff \u002Ftmp\u002Fa \u002Ftmp\u002Fb; rm \u002Ftmp\u002Fa \u002Ftmp\u002Fb\n",[40,2113,2114],{"class":42,"line":123},[40,2115,66],{"emptyLinePlaceholder":65},[40,2117,2118],{"class":42,"line":137},[40,2119,2120],{"class":46},"# ── Feed a while loop without the subshell trap ──\n",[40,2122,2123],{"class":42,"line":154},[40,2124,2125],{"class":46},"# ❌ PIPE TRAP: while loop runs in a subshell — variables don't persist\n",[40,2127,2128,2130,2133,2136,2139,2142,2144,2147,2149,2152,2155,2157,2159,2162,2164,2166,2169],{"class":42,"line":169},[40,2129,194],{"class":98},[40,2131,2132],{"class":80}," \"1 2 3\"",[40,2134,2135],{"class":76}," |",[40,2137,2138],{"class":76}," while",[40,2140,2141],{"class":98}," read",[40,2143,1275],{"class":98},[40,2145,2146],{"class":80}," num",[40,2148,1909],{"class":72},[40,2150,2151],{"class":76},"do",[40,2153,2154],{"class":72}," total",[40,2156,77],{"class":76},[40,2158,247],{"class":72},[40,2160,2161],{"class":250},"total",[40,2163,519],{"class":80},[40,2165,2146],{"class":80},[40,2167,2168],{"class":72},")); ",[40,2170,2171],{"class":76},"done\n",[40,2173,2174,2176,2178,2181,2183],{"class":42,"line":174},[40,2175,194],{"class":98},[40,2177,540],{"class":80},[40,2179,2180],{"class":72},"$total",[40,2182,545],{"class":80},[40,2184,2185],{"class":46},"  # empty — subshell variable lost\n",[40,2187,2188],{"class":42,"line":180},[40,2189,66],{"emptyLinePlaceholder":65},[40,2191,2192],{"class":42,"line":203},[40,2193,2194],{"class":46},"# ✅ PROCESS SUBSTITUTION: while loop runs in the MAIN shell\n",[40,2196,2197,2199,2201],{"class":42,"line":228},[40,2198,2161],{"class":72},[40,2200,77],{"class":76},[40,2202,2203],{"class":80},"0\n",[40,2205,2206,2209,2211,2213,2215,2217],{"class":42,"line":233},[40,2207,2208],{"class":76},"while",[40,2210,2141],{"class":98},[40,2212,1275],{"class":98},[40,2214,2146],{"class":80},[40,2216,1909],{"class":72},[40,2218,2219],{"class":76},"do\n",[40,2221,2222,2225,2227,2229,2231,2233,2235],{"class":42,"line":239},[40,2223,2224],{"class":72},"    total",[40,2226,77],{"class":76},[40,2228,247],{"class":72},[40,2230,2161],{"class":250},[40,2232,519],{"class":80},[40,2234,2146],{"class":80},[40,2236,1831],{"class":72},[40,2238,2239,2242,2245,2247,2249],{"class":42,"line":265},[40,2240,2241],{"class":76},"done",[40,2243,2244],{"class":76}," \u003C",[40,2246,2088],{"class":80},[40,2248,194],{"class":98},[40,2250,2251],{"class":80}," \"1 2 3\")\n",[40,2253,2254,2256,2258,2260,2262],{"class":42,"line":288},[40,2255,194],{"class":98},[40,2257,540],{"class":80},[40,2259,2180],{"class":72},[40,2261,545],{"class":80},[40,2263,2264],{"class":46},"  # 6 — persists (no subshell for the while body)\n",[40,2266,2267],{"class":42,"line":307},[40,2268,66],{"emptyLinePlaceholder":65},[40,2270,2271],{"class":42,"line":326},[40,2272,2273],{"class":46},"# ── Compare files from different servers ──\n",[40,2275,2276,2278,2280,2283,2286,2288,2290],{"class":42,"line":346},[40,2277,2085],{"class":250},[40,2279,2088],{"class":80},[40,2281,2282],{"class":250},"ssh",[40,2284,2285],{"class":80}," server1 cat \u002Fetc\u002Fhosts)",[40,2287,2088],{"class":80},[40,2289,2282],{"class":250},[40,2291,2292],{"class":80}," server2 cat \u002Fetc\u002Fhosts)\n",[40,2294,2295],{"class":42,"line":364},[40,2296,66],{"emptyLinePlaceholder":65},[40,2298,2299],{"class":42,"line":376},[40,2300,2301],{"class":46},"# ── >(...) writes to a command's stdin (less common) ──\n",[40,2303,2304],{"class":42,"line":387},[40,2305,2306],{"class":46},"# Tee output to both a file and a pipeline:\n",[40,2308,2309,2312,2315,2318,2321,2324,2327,2330,2333,2336],{"class":42,"line":399},[40,2310,2311],{"class":250},"make",[40,2313,2314],{"class":76}," 2>",[40,2316,2317],{"class":80}," >(",[40,2319,2320],{"class":250},"grep",[40,2322,2323],{"class":98}," -i",[40,2325,2326],{"class":80}," error ",[40,2328,2329],{"class":76},">&2",[40,2331,2332],{"class":80},")",[40,2334,2335],{"class":76}," >",[40,2337,2338],{"class":80}," build.log\n",[40,2340,2341],{"class":42,"line":409},[40,2342,2343],{"class":46},"# stderr goes to grep (which sends matches to stderr), stdout goes to build.log\n",[40,2345,2346],{"class":42,"line":420},[40,2347,66],{"emptyLinePlaceholder":65},[40,2349,2350],{"class":42,"line":431},[40,2351,2352],{"class":46},"# ── Multiple process substitutions ──\n",[40,2354,2355,2358,2360,2363,2366,2369,2372,2374,2376,2378,2381,2383],{"class":42,"line":436},[40,2356,2357],{"class":250},"paste",[40,2359,2088],{"class":80},[40,2361,2362],{"class":250},"cut",[40,2364,2365],{"class":98}," -d,",[40,2367,2368],{"class":98}," -f1",[40,2370,2371],{"class":80}," data.csv)",[40,2373,2088],{"class":80},[40,2375,2362],{"class":250},[40,2377,2365],{"class":98},[40,2379,2380],{"class":98}," -f3",[40,2382,2371],{"class":80},[40,2384,2385],{"class":46},"  # side-by-side from different columns\n",[40,2387,2388],{"class":42,"line":442},[40,2389,66],{"emptyLinePlaceholder":65},[40,2391,2392],{"class":42,"line":448},[40,2393,2394],{"class":46},"# ── Edge: process substitution fds are ephemeral ──\n",[40,2396,2397],{"class":42,"line":454},[40,2398,2399],{"class":46},"# The \u002Fdev\u002Ffd\u002F63 path is valid only during the command's execution.\n",[40,2401,2402],{"class":42,"line":471},[40,2403,2404],{"class":46},"# Storing it in a variable and using it later FAILS:\n",[40,2406,2407,2410,2412,2414,2417,2419,2422,2424],{"class":42,"line":1246},[40,2408,2409],{"class":72},"fd",[40,2411,77],{"class":76},[40,2413,512],{"class":72},[40,2415,2416],{"class":80},"\u003C(",[40,2418,194],{"class":98},[40,2420,2421],{"class":80}," hello)",[40,2423,1744],{"class":72},[40,2425,2426],{"class":46},"# fd=\"\u002Fdev\u002Ffd\u002F63\" — but the fd is already closed!\n",[40,2428,2429,2432,2434,2437,2439],{"class":42,"line":1251},[40,2430,2431],{"class":250},"cat",[40,2433,540],{"class":80},[40,2435,2436],{"class":72},"$fd",[40,2438,545],{"class":80},[40,2440,2441],{"class":46},"              # cat: \u002Fdev\u002Ffd\u002F63: No such file or directory\n",[15,2443,2445],{"id":2444},"brace-expansion-code-generation-before-parsing","Brace Expansion: Code Generation Before Parsing",[28,2447,2448],{"language":30},[32,2449,2451],{"className":34,"code":2450,"language":30,"meta":36,"style":36},"# ── Brace expansion happens FIRST (before variable expansion, before globbing) ──\n# It generates strings — no files need to exist (unlike globs).\n\necho {1..5}              # 1 2 3 4 5 — numeric range\necho {a..e}              # a b c d e — alpha range\necho {01..10}            # 01 02 03 ... 10 — zero-padded (Bash 4+)\necho {5..1}              # 5 4 3 2 1 — reverse range\necho {a..z..2}           # a c e g i k m o q s u w y — step 2 (Bash 4+)\necho {1..10..3}          # 1 4 7 10 — step 3 (Bash 4+)\n\n# ── List expansion ──\necho {foo,bar,baz}       # foo bar baz\necho file{1..3}.txt      # file1.txt file2.txt file3.txt — prefix\u002Fsuffix\nmkdir -p project\u002F{src,lib,test}\u002F{bin,lib}  # creates project\u002Fsrc\u002Fbin, project\u002Fsrc\u002Flib, ... (6 dirs)\n\n# ── Brace expansion is NOT variable-aware (happens before variable expansion) ──\nprefix=\"file\"\necho {$prefix,backup}   # {file,backup} — brace expansion sees literal $prefix (not its value)\necho ${prefix}{.txt,.log}  # file.txt file.log — brace expansion on the SUFFIX works (prefix already expanded)\n\n# ── Nested braces ──\necho a{b,c}d{e,f}       # abde abdf acde acdf — cartesian product\necho {{a,b},{c,d}}       # a b c d — nested lists flatten\n\n# ── Brace expansion vs glob — critical distinction ──\necho *.txt               # GLOB: only if .txt files exist → \"file1.txt file2.txt\"; else \"*.txt\"\necho file{1..3}.txt      # BRACE: always → \"file1.txt file2.txt file3.txt\" (even if they don't exist)\n# Glob depends on filesystem; brace expansion is pure string generation.\n",[20,2452,2453,2458,2463,2467,2477,2487,2497,2507,2517,2527,2531,2536,2546,2556,2570,2574,2579,2589,2605,2618,2622,2627,2637,2647,2651,2656,2668,2677],{"__ignoreMap":36},[40,2454,2455],{"class":42,"line":43},[40,2456,2457],{"class":46},"# ── Brace expansion happens FIRST (before variable expansion, before globbing) ──\n",[40,2459,2460],{"class":42,"line":50},[40,2461,2462],{"class":46},"# It generates strings — no files need to exist (unlike globs).\n",[40,2464,2465],{"class":42,"line":56},[40,2466,66],{"emptyLinePlaceholder":65},[40,2468,2469,2471,2474],{"class":42,"line":62},[40,2470,194],{"class":98},[40,2472,2473],{"class":80}," {1..5}",[40,2475,2476],{"class":46},"              # 1 2 3 4 5 — numeric range\n",[40,2478,2479,2481,2484],{"class":42,"line":69},[40,2480,194],{"class":98},[40,2482,2483],{"class":80}," {a..e}",[40,2485,2486],{"class":46},"              # a b c d e — alpha range\n",[40,2488,2489,2491,2494],{"class":42,"line":84},[40,2490,194],{"class":98},[40,2492,2493],{"class":80}," {01..10}",[40,2495,2496],{"class":46},"            # 01 02 03 ... 10 — zero-padded (Bash 4+)\n",[40,2498,2499,2501,2504],{"class":42,"line":108},[40,2500,194],{"class":98},[40,2502,2503],{"class":80}," {5..1}",[40,2505,2506],{"class":46},"              # 5 4 3 2 1 — reverse range\n",[40,2508,2509,2511,2514],{"class":42,"line":123},[40,2510,194],{"class":98},[40,2512,2513],{"class":80}," {a..z..2}",[40,2515,2516],{"class":46},"           # a c e g i k m o q s u w y — step 2 (Bash 4+)\n",[40,2518,2519,2521,2524],{"class":42,"line":137},[40,2520,194],{"class":98},[40,2522,2523],{"class":80}," {1..10..3}",[40,2525,2526],{"class":46},"          # 1 4 7 10 — step 3 (Bash 4+)\n",[40,2528,2529],{"class":42,"line":154},[40,2530,66],{"emptyLinePlaceholder":65},[40,2532,2533],{"class":42,"line":169},[40,2534,2535],{"class":46},"# ── List expansion ──\n",[40,2537,2538,2540,2543],{"class":42,"line":174},[40,2539,194],{"class":98},[40,2541,2542],{"class":80}," {foo,bar,baz}",[40,2544,2545],{"class":46},"       # foo bar baz\n",[40,2547,2548,2550,2553],{"class":42,"line":180},[40,2549,194],{"class":98},[40,2551,2552],{"class":80}," file{1..3}.txt",[40,2554,2555],{"class":46},"      # file1.txt file2.txt file3.txt — prefix\u002Fsuffix\n",[40,2557,2558,2561,2564,2567],{"class":42,"line":203},[40,2559,2560],{"class":250},"mkdir",[40,2562,2563],{"class":98}," -p",[40,2565,2566],{"class":80}," project\u002F{src,lib,test}\u002F{bin,lib}",[40,2568,2569],{"class":46},"  # creates project\u002Fsrc\u002Fbin, project\u002Fsrc\u002Flib, ... (6 dirs)\n",[40,2571,2572],{"class":42,"line":228},[40,2573,66],{"emptyLinePlaceholder":65},[40,2575,2576],{"class":42,"line":233},[40,2577,2578],{"class":46},"# ── Brace expansion is NOT variable-aware (happens before variable expansion) ──\n",[40,2580,2581,2584,2586],{"class":42,"line":239},[40,2582,2583],{"class":72},"prefix",[40,2585,77],{"class":76},[40,2587,2588],{"class":80},"\"file\"\n",[40,2590,2591,2593,2596,2599,2602],{"class":42,"line":265},[40,2592,194],{"class":98},[40,2594,2595],{"class":80}," {",[40,2597,2598],{"class":72},"$prefix",[40,2600,2601],{"class":80},",backup}",[40,2603,2604],{"class":46},"   # {file,backup} — brace expansion sees literal $prefix (not its value)\n",[40,2606,2607,2609,2612,2615],{"class":42,"line":288},[40,2608,194],{"class":98},[40,2610,2611],{"class":72}," ${prefix}",[40,2613,2614],{"class":80},"{.txt,.log}",[40,2616,2617],{"class":46},"  # file.txt file.log — brace expansion on the SUFFIX works (prefix already expanded)\n",[40,2619,2620],{"class":42,"line":307},[40,2621,66],{"emptyLinePlaceholder":65},[40,2623,2624],{"class":42,"line":326},[40,2625,2626],{"class":46},"# ── Nested braces ──\n",[40,2628,2629,2631,2634],{"class":42,"line":346},[40,2630,194],{"class":98},[40,2632,2633],{"class":80}," a{b,c}d{e,f}",[40,2635,2636],{"class":46},"       # abde abdf acde acdf — cartesian product\n",[40,2638,2639,2641,2644],{"class":42,"line":364},[40,2640,194],{"class":98},[40,2642,2643],{"class":80}," {{a,b},{c,d}}",[40,2645,2646],{"class":46},"       # a b c d — nested lists flatten\n",[40,2648,2649],{"class":42,"line":376},[40,2650,66],{"emptyLinePlaceholder":65},[40,2652,2653],{"class":42,"line":387},[40,2654,2655],{"class":46},"# ── Brace expansion vs glob — critical distinction ──\n",[40,2657,2658,2660,2662,2665],{"class":42,"line":399},[40,2659,194],{"class":98},[40,2661,253],{"class":98},[40,2663,2664],{"class":80},".txt",[40,2666,2667],{"class":46},"               # GLOB: only if .txt files exist → \"file1.txt file2.txt\"; else \"*.txt\"\n",[40,2669,2670,2672,2674],{"class":42,"line":409},[40,2671,194],{"class":98},[40,2673,2552],{"class":80},[40,2675,2676],{"class":46},"      # BRACE: always → \"file1.txt file2.txt file3.txt\" (even if they don't exist)\n",[40,2678,2679],{"class":42,"line":420},[40,2680,2681],{"class":46},"# Glob depends on filesystem; brace expansion is pure string generation.\n",[15,2683,2685],{"id":2684},"globbing-filename-expansion-mechanics","Globbing: Filename Expansion Mechanics",[28,2687,2688],{"language":30},[32,2689,2691],{"className":34,"code":2690,"language":30,"meta":36,"style":36},"# ── Glob patterns ──\n*          # matches any string (including empty)\n?          # matches any single char\n[abc]      # matches a, b, or c\n[a-z]      # matches a through z (locale-dependent!)\n[!abc]     # matches anything NOT a, b, c (POSIX) \u002F [^abc] in some shells\n**         # recursive (needs shopt -s globstar)\n\n# ── Globbing happens AFTER word splitting and variable expansion ──\npattern=\"*.txt\"\necho \"$pattern\"   # *.txt — quoted: no glob, literal string\necho $pattern     # file1.txt file2.txt — unquoted: glob expands (if matches exist)\n\n# ── nullglob: empty result if no match (instead of literal pattern) ──\nshopt -s nullglob\necho *.nonexistent   # (empty) — no match → empty, not \"*.nonexistent\"\nshopt -u nullglob\necho *.nonexistent   # *.nonexistent — no match → literal string passed through\n\n# ── failglob: error if no match (Bash 3+, strongest safety) ──\nshopt -s failglob\necho *.nonexistent   # bash: no match: *.nonexistent — script aborts (catches typos!)\n# Best for scripts: use failglob to catch glob typs early.\n\n# ── globstar: recursive ** (Bash 4+) ──\nshopt -s globstar\necho **\u002F*.py         # all .py files in current dir and all subdirs (recursively)\n# ⚠️ ** only matches recursively if it's a path component by itself:\necho **.py           # matches: dir.py (NOT recursive — ** is not a standalone component)\necho **\u002F*.py         # matches: subdir\u002Ffile.py (recursive — ** is a standalone component)\n\n# ── extglob: extended pattern matching (Bash 2.02+, needs shopt) ──\nshopt -s extglob\n# ?(pattern)  — zero or one match\n# *(pattern)  — zero or more\n# +(pattern)  — one or more\n# @(pattern)  — exactly one\n# !(pattern)  — anything that doesn't match\necho !(*.txt)        # all files NOT ending in .txt\necho @(*.jpg|*.png)  # all .jpg or .png files\necho *(.bak|.tmp)    # all files ending in .bak or .tmp (zero or more — includes files with no extension)\n\n# ── dotglob: include hidden files (dotfiles) in globs ──\nshopt -s dotglob\necho *               # includes .hidden_file (default: * excludes dotfiles)\n",[20,2692,2693,2698,2705,2713,2721,2729,2743,2751,2755,2760,2770,2784,2794,2798,2803,2812,2824,2833,2844,2848,2853,2862,2873,2878,2882,2887,2896,2913,2918,2929,2944,2948,2953,2962,2967,2972,2977,2982,2987,3005,3028,3050,3054,3059,3068],{"__ignoreMap":36},[40,2694,2695],{"class":42,"line":43},[40,2696,2697],{"class":46},"# ── Glob patterns ──\n",[40,2699,2700,2702],{"class":42,"line":50},[40,2701,965],{"class":76},[40,2703,2704],{"class":46},"          # matches any string (including empty)\n",[40,2706,2707,2710],{"class":42,"line":56},[40,2708,2709],{"class":76},"?",[40,2711,2712],{"class":46},"          # matches any single char\n",[40,2714,2715,2718],{"class":42,"line":62},[40,2716,2717],{"class":72},"[abc]      ",[40,2719,2720],{"class":46},"# matches a, b, or c\n",[40,2722,2723,2726],{"class":42,"line":69},[40,2724,2725],{"class":72},"[a-z]      ",[40,2727,2728],{"class":46},"# matches a through z (locale-dependent!)\n",[40,2730,2731,2734,2737,2740],{"class":42,"line":84},[40,2732,2733],{"class":72},"[",[40,2735,2736],{"class":76},"!",[40,2738,2739],{"class":72},"abc]     ",[40,2741,2742],{"class":46},"# matches anything NOT a, b, c (POSIX) \u002F [^abc] in some shells\n",[40,2744,2745,2748],{"class":42,"line":108},[40,2746,2747],{"class":76},"**",[40,2749,2750],{"class":46},"         # recursive (needs shopt -s globstar)\n",[40,2752,2753],{"class":42,"line":123},[40,2754,66],{"emptyLinePlaceholder":65},[40,2756,2757],{"class":42,"line":137},[40,2758,2759],{"class":46},"# ── Globbing happens AFTER word splitting and variable expansion ──\n",[40,2761,2762,2765,2767],{"class":42,"line":154},[40,2763,2764],{"class":72},"pattern",[40,2766,77],{"class":76},[40,2768,2769],{"class":80},"\"*.txt\"\n",[40,2771,2772,2774,2776,2779,2781],{"class":42,"line":169},[40,2773,194],{"class":98},[40,2775,540],{"class":80},[40,2777,2778],{"class":72},"$pattern",[40,2780,545],{"class":80},[40,2782,2783],{"class":46},"   # *.txt — quoted: no glob, literal string\n",[40,2785,2786,2788,2791],{"class":42,"line":174},[40,2787,194],{"class":98},[40,2789,2790],{"class":72}," $pattern     ",[40,2792,2793],{"class":46},"# file1.txt file2.txt — unquoted: glob expands (if matches exist)\n",[40,2795,2796],{"class":42,"line":180},[40,2797,66],{"emptyLinePlaceholder":65},[40,2799,2800],{"class":42,"line":203},[40,2801,2802],{"class":46},"# ── nullglob: empty result if no match (instead of literal pattern) ──\n",[40,2804,2805,2807,2809],{"class":42,"line":228},[40,2806,2035],{"class":250},[40,2808,1652],{"class":98},[40,2810,2811],{"class":80}," nullglob\n",[40,2813,2814,2816,2818,2821],{"class":42,"line":233},[40,2815,194],{"class":98},[40,2817,253],{"class":98},[40,2819,2820],{"class":80},".nonexistent",[40,2822,2823],{"class":46},"   # (empty) — no match → empty, not \"*.nonexistent\"\n",[40,2825,2826,2828,2831],{"class":42,"line":239},[40,2827,2035],{"class":250},[40,2829,2830],{"class":98}," -u",[40,2832,2811],{"class":80},[40,2834,2835,2837,2839,2841],{"class":42,"line":265},[40,2836,194],{"class":98},[40,2838,253],{"class":98},[40,2840,2820],{"class":80},[40,2842,2843],{"class":46},"   # *.nonexistent — no match → literal string passed through\n",[40,2845,2846],{"class":42,"line":288},[40,2847,66],{"emptyLinePlaceholder":65},[40,2849,2850],{"class":42,"line":307},[40,2851,2852],{"class":46},"# ── failglob: error if no match (Bash 3+, strongest safety) ──\n",[40,2854,2855,2857,2859],{"class":42,"line":326},[40,2856,2035],{"class":250},[40,2858,1652],{"class":98},[40,2860,2861],{"class":80}," failglob\n",[40,2863,2864,2866,2868,2870],{"class":42,"line":346},[40,2865,194],{"class":98},[40,2867,253],{"class":98},[40,2869,2820],{"class":80},[40,2871,2872],{"class":46},"   # bash: no match: *.nonexistent — script aborts (catches typos!)\n",[40,2874,2875],{"class":42,"line":364},[40,2876,2877],{"class":46},"# Best for scripts: use failglob to catch glob typs early.\n",[40,2879,2880],{"class":42,"line":376},[40,2881,66],{"emptyLinePlaceholder":65},[40,2883,2884],{"class":42,"line":387},[40,2885,2886],{"class":46},"# ── globstar: recursive ** (Bash 4+) ──\n",[40,2888,2889,2891,2893],{"class":42,"line":399},[40,2890,2035],{"class":250},[40,2892,1652],{"class":98},[40,2894,2895],{"class":80}," globstar\n",[40,2897,2898,2900,2902,2905,2907,2910],{"class":42,"line":409},[40,2899,194],{"class":98},[40,2901,336],{"class":98},[40,2903,2904],{"class":80},"\u002F",[40,2906,965],{"class":98},[40,2908,2909],{"class":80},".py",[40,2911,2912],{"class":46},"         # all .py files in current dir and all subdirs (recursively)\n",[40,2914,2915],{"class":42,"line":420},[40,2916,2917],{"class":46},"# ⚠️ ** only matches recursively if it's a path component by itself:\n",[40,2919,2920,2922,2924,2926],{"class":42,"line":431},[40,2921,194],{"class":98},[40,2923,336],{"class":98},[40,2925,2909],{"class":80},[40,2927,2928],{"class":46},"           # matches: dir.py (NOT recursive — ** is not a standalone component)\n",[40,2930,2931,2933,2935,2937,2939,2941],{"class":42,"line":436},[40,2932,194],{"class":98},[40,2934,336],{"class":98},[40,2936,2904],{"class":80},[40,2938,965],{"class":98},[40,2940,2909],{"class":80},[40,2942,2943],{"class":46},"         # matches: subdir\u002Ffile.py (recursive — ** is a standalone component)\n",[40,2945,2946],{"class":42,"line":442},[40,2947,66],{"emptyLinePlaceholder":65},[40,2949,2950],{"class":42,"line":448},[40,2951,2952],{"class":46},"# ── extglob: extended pattern matching (Bash 2.02+, needs shopt) ──\n",[40,2954,2955,2957,2959],{"class":42,"line":454},[40,2956,2035],{"class":250},[40,2958,1652],{"class":98},[40,2960,2961],{"class":80}," extglob\n",[40,2963,2964],{"class":42,"line":471},[40,2965,2966],{"class":46},"# ?(pattern)  — zero or one match\n",[40,2968,2969],{"class":42,"line":1246},[40,2970,2971],{"class":46},"# *(pattern)  — zero or more\n",[40,2973,2974],{"class":42,"line":1251},[40,2975,2976],{"class":46},"# +(pattern)  — one or more\n",[40,2978,2979],{"class":42,"line":1257},[40,2980,2981],{"class":46},"# @(pattern)  — exactly one\n",[40,2983,2984],{"class":42,"line":1289},[40,2985,2986],{"class":46},"# !(pattern)  — anything that doesn't match\n",[40,2988,2989,2991,2994,2997,2999,3002],{"class":42,"line":1318},[40,2990,194],{"class":98},[40,2992,2993],{"class":80}," !",[40,2995,2996],{"class":72},"(",[40,2998,965],{"class":76},[40,3000,3001],{"class":72},".txt)        ",[40,3003,3004],{"class":46},"# all files NOT ending in .txt\n",[40,3006,3007,3009,3012,3014,3016,3019,3022,3025],{"class":42,"line":1324},[40,3008,194],{"class":98},[40,3010,3011],{"class":80}," @",[40,3013,2996],{"class":72},[40,3015,965],{"class":76},[40,3017,3018],{"class":72},".jpg",[40,3020,3021],{"class":76},"|*",[40,3023,3024],{"class":72},".png)  ",[40,3026,3027],{"class":46},"# all .jpg or .png files\n",[40,3029,3030,3032,3034,3036,3039,3042,3045,3047],{"class":42,"line":1329},[40,3031,194],{"class":98},[40,3033,253],{"class":98},[40,3035,2996],{"class":72},[40,3037,3038],{"class":250},".bak",[40,3040,3041],{"class":76},"|",[40,3043,3044],{"class":250},".tmp",[40,3046,558],{"class":72},[40,3048,3049],{"class":46},"# all files ending in .bak or .tmp (zero or more — includes files with no extension)\n",[40,3051,3052],{"class":42,"line":1335},[40,3053,66],{"emptyLinePlaceholder":65},[40,3055,3056],{"class":42,"line":1341},[40,3057,3058],{"class":46},"# ── dotglob: include hidden files (dotfiles) in globs ──\n",[40,3060,3061,3063,3065],{"class":42,"line":1347},[40,3062,2035],{"class":250},[40,3064,1652],{"class":98},[40,3066,3067],{"class":80}," dotglob\n",[40,3069,3070,3072,3074],{"class":42,"line":1361},[40,3071,194],{"class":98},[40,3073,253],{"class":98},[40,3075,3076],{"class":46},"               # includes .hidden_file (default: * excludes dotfiles)\n",[15,3078,3080],{"id":3079},"anti-pattern-locale-dependent-range","Anti-Pattern: Locale-Dependent Range",[28,3082,3083],{"language":30},[32,3084,3086],{"className":34,"code":3085,"language":30,"meta":36,"style":36},"# ❌ NAIVE — [a-z] depends on locale (case-insensitive in some locales, includes accented chars)\nfor file in [a-z]*.txt; do   # In C locale: a-z. In en_US.UTF-8: might include A-Z!\n    echo \"$file\"\ndone\n\n# ✅ CORRECT — force C locale for deterministic byte-order sorting\nLC_ALL=C\nfor file in [a-z]*.txt; do   # guaranteed a-z only\n    echo \"$file\"\ndone\n\n# ✅ BEST — use extglob for explicit control\nshopt -s extglob\nfor file in @([a-z]*.txt); do   # @() is exact match, no locale ambiguity\n    echo \"$file\"\ndone\n\n# ── The \"includes uppercase\" trap ──\n# In en_US.UTF-8 locale, [a-z] matches: aAbBcC...zZ (because collation is case-insensitive + accent-aware)\n# In C locale, [a-z] matches: a b c ... z (ASCII byte order)\n# Always use LC_ALL=C for glob ranges in scripts.\n",[20,3087,3088,3093,3120,3132,3136,3140,3145,3155,3176,3186,3190,3194,3199,3207,3230,3240,3244,3248,3253,3258,3263],{"__ignoreMap":36},[40,3089,3090],{"class":42,"line":43},[40,3091,3092],{"class":46},"# ❌ NAIVE — [a-z] depends on locale (case-insensitive in some locales, includes accented chars)\n",[40,3094,3095,3098,3101,3104,3107,3110,3112,3115,3117],{"class":42,"line":50},[40,3096,3097],{"class":76},"for",[40,3099,3100],{"class":72}," file ",[40,3102,3103],{"class":76},"in",[40,3105,3106],{"class":80}," [a-z",[40,3108,3109],{"class":72},"]",[40,3111,965],{"class":76},[40,3113,3114],{"class":72},".txt; ",[40,3116,2151],{"class":76},[40,3118,3119],{"class":46},"   # In C locale: a-z. In en_US.UTF-8: might include A-Z!\n",[40,3121,3122,3125,3127,3129],{"class":42,"line":56},[40,3123,3124],{"class":98},"    echo",[40,3126,540],{"class":80},[40,3128,1267],{"class":72},[40,3130,3131],{"class":80},"\"\n",[40,3133,3134],{"class":42,"line":62},[40,3135,2171],{"class":76},[40,3137,3138],{"class":42,"line":69},[40,3139,66],{"emptyLinePlaceholder":65},[40,3141,3142],{"class":42,"line":84},[40,3143,3144],{"class":46},"# ✅ CORRECT — force C locale for deterministic byte-order sorting\n",[40,3146,3147,3150,3152],{"class":42,"line":108},[40,3148,3149],{"class":72},"LC_ALL",[40,3151,77],{"class":76},[40,3153,3154],{"class":80},"C\n",[40,3156,3157,3159,3161,3163,3165,3167,3169,3171,3173],{"class":42,"line":123},[40,3158,3097],{"class":76},[40,3160,3100],{"class":72},[40,3162,3103],{"class":76},[40,3164,3106],{"class":80},[40,3166,3109],{"class":72},[40,3168,965],{"class":76},[40,3170,3114],{"class":72},[40,3172,2151],{"class":76},[40,3174,3175],{"class":46},"   # guaranteed a-z only\n",[40,3177,3178,3180,3182,3184],{"class":42,"line":137},[40,3179,3124],{"class":98},[40,3181,540],{"class":80},[40,3183,1267],{"class":72},[40,3185,3131],{"class":80},[40,3187,3188],{"class":42,"line":154},[40,3189,2171],{"class":76},[40,3191,3192],{"class":42,"line":169},[40,3193,66],{"emptyLinePlaceholder":65},[40,3195,3196],{"class":42,"line":174},[40,3197,3198],{"class":46},"# ✅ BEST — use extglob for explicit control\n",[40,3200,3201,3203,3205],{"class":42,"line":180},[40,3202,2035],{"class":250},[40,3204,1652],{"class":98},[40,3206,2961],{"class":80},[40,3208,3209,3211,3213,3215,3217,3220,3222,3225,3227],{"class":42,"line":203},[40,3210,3097],{"class":76},[40,3212,3100],{"class":72},[40,3214,3103],{"class":76},[40,3216,3011],{"class":80},[40,3218,3219],{"class":72},"([a-z]",[40,3221,965],{"class":76},[40,3223,3224],{"class":72},".txt); ",[40,3226,2151],{"class":76},[40,3228,3229],{"class":46},"   # @() is exact match, no locale ambiguity\n",[40,3231,3232,3234,3236,3238],{"class":42,"line":228},[40,3233,3124],{"class":98},[40,3235,540],{"class":80},[40,3237,1267],{"class":72},[40,3239,3131],{"class":80},[40,3241,3242],{"class":42,"line":233},[40,3243,2171],{"class":76},[40,3245,3246],{"class":42,"line":239},[40,3247,66],{"emptyLinePlaceholder":65},[40,3249,3250],{"class":42,"line":265},[40,3251,3252],{"class":46},"# ── The \"includes uppercase\" trap ──\n",[40,3254,3255],{"class":42,"line":288},[40,3256,3257],{"class":46},"# In en_US.UTF-8 locale, [a-z] matches: aAbBcC...zZ (because collation is case-insensitive + accent-aware)\n",[40,3259,3260],{"class":42,"line":307},[40,3261,3262],{"class":46},"# In C locale, [a-z] matches: a b c ... z (ASCII byte order)\n",[40,3264,3265],{"class":42,"line":326},[40,3266,3267],{"class":46},"# Always use LC_ALL=C for glob ranges in scripts.\n",[15,3269,3271],{"id":3270},"tips-tricks","💡 Tips & Tricks",[28,3273,3274],{"language":30},[32,3275,3277],{"className":34,"code":3276,"language":30,"meta":36,"style":36},"# ── Arithmetic with arrays: index arithmetic ──\narr=(10 20 30 40 50)\ni=2\necho \"${arr[i + 1]}\"          # 40 — index is an arithmetic expression (no $ needed inside [])\necho \"${arr[$((i + 1))]}\"     # 40 — also works (explicit $((...)))\n\n# ── Bitwise operations for flags ──\nreadonly READ=1   WRITE=2  EXEC=4\nperms=0\n((perms |= READ | WRITE))   # 3 — set read and write bits\n((perms & READ)) && echo \"readable\"    # readable — check if bit is set\n((perms & EXEC)) || echo \"not exec\"     # not exec — bit not set\n((perms & ~READ))          # 2 — mask off read bit (doesn't modify perms)\n((perms &= ~WRITE))         # clear write bit from perms → perms is now 1\n\n# ── Random numbers ──\necho \"$RANDOM\"               # 0-32767 (pseudo-random, seeded from $$ and time)\necho \"$((RANDOM % 100))\"    # 0-99 — dice roll\necho \"$((RANDOM % 6 + 1))\"  # 1-6 — die\n# For better randomness (crypto): read \u002Fdev\u002Furandom\nod -An -tu4 -N4 \u002Fdev\u002Furandom | tr -d ' '  # 32-bit random integer\n\n# ── Sequence generation without seq (Bash 4+) ──\necho {1..100}                # 1 2 3 ... 100 — no subprocess\n# ❌ seq 1 100 — forks a subprocess\n# ✅ {1..100} — in-process brace expansion\n\n# ── Conditional assignment (ternary-like) ──\nx=5\n# Bash has no ternary, but you can use:\n((x > 0 ? y = 1 : y = 0))    # y is 1 if x > 0, else 0 — works in ((...))\n# Or: y=0; ((x > 0)) && y=1  — more readable\n",[20,3278,3279,3284,3311,3321,3335,3349,3353,3358,3385,3394,3413,3431,3450,3465,3480,3484,3489,3503,3522,3544,3549,3580,3584,3589,3599,3604,3609,3613,3618,3627,3632,3666],{"__ignoreMap":36},[40,3280,3281],{"class":42,"line":43},[40,3282,3283],{"class":46},"# ── Arithmetic with arrays: index arithmetic ──\n",[40,3285,3286,3289,3291,3293,3296,3299,3302,3305,3308],{"class":42,"line":50},[40,3287,3288],{"class":72},"arr",[40,3290,77],{"class":76},[40,3292,2996],{"class":72},[40,3294,3295],{"class":98},"10",[40,3297,3298],{"class":98}," 20",[40,3300,3301],{"class":98}," 30",[40,3303,3304],{"class":98}," 40",[40,3306,3307],{"class":98}," 50",[40,3309,3310],{"class":72},")\n",[40,3312,3313,3316,3318],{"class":42,"line":56},[40,3314,3315],{"class":72},"i",[40,3317,77],{"class":76},[40,3319,3320],{"class":80},"2\n",[40,3322,3323,3325,3327,3329,3332],{"class":42,"line":62},[40,3324,194],{"class":98},[40,3326,1053],{"class":80},[40,3328,3288],{"class":72},[40,3330,3331],{"class":80},"[i + 1]}\"",[40,3333,3334],{"class":46},"          # 40 — index is an arithmetic expression (no $ needed inside [])\n",[40,3336,3337,3339,3341,3343,3346],{"class":42,"line":69},[40,3338,194],{"class":98},[40,3340,1053],{"class":80},[40,3342,3288],{"class":72},[40,3344,3345],{"class":80},"[$((i + 1))]}\"",[40,3347,3348],{"class":46},"     # 40 — also works (explicit $((...)))\n",[40,3350,3351],{"class":42,"line":84},[40,3352,66],{"emptyLinePlaceholder":65},[40,3354,3355],{"class":42,"line":108},[40,3356,3357],{"class":46},"# ── Bitwise operations for flags ──\n",[40,3359,3360,3363,3366,3368,3370,3373,3375,3377,3380,3382],{"class":42,"line":123},[40,3361,3362],{"class":76},"readonly",[40,3364,3365],{"class":72}," READ",[40,3367,77],{"class":76},[40,3369,465],{"class":98},[40,3371,3372],{"class":72},"   WRITE",[40,3374,77],{"class":76},[40,3376,333],{"class":98},[40,3378,3379],{"class":72},"  EXEC",[40,3381,77],{"class":76},[40,3383,3384],{"class":98},"4\n",[40,3386,3387,3390,3392],{"class":42,"line":137},[40,3388,3389],{"class":72},"perms",[40,3391,77],{"class":76},[40,3393,2203],{"class":80},[40,3395,3396,3399,3402,3405,3407,3410],{"class":42,"line":154},[40,3397,3398],{"class":72},"((perms ",[40,3400,3401],{"class":76},"|=",[40,3403,3404],{"class":72}," READ ",[40,3406,3041],{"class":76},[40,3408,3409],{"class":72}," WRITE))   ",[40,3411,3412],{"class":46},"# 3 — set read and write bits\n",[40,3414,3415,3417,3420,3423,3425,3428],{"class":42,"line":169},[40,3416,3398],{"class":72},[40,3418,3419],{"class":76},"&",[40,3421,3422],{"class":72}," READ)) && ",[40,3424,194],{"class":98},[40,3426,3427],{"class":80}," \"readable\"",[40,3429,3430],{"class":46},"    # readable — check if bit is set\n",[40,3432,3433,3435,3437,3440,3442,3444,3447],{"class":42,"line":174},[40,3434,3398],{"class":72},[40,3436,3419],{"class":76},[40,3438,3439],{"class":72}," EXEC)) ",[40,3441,216],{"class":76},[40,3443,219],{"class":98},[40,3445,3446],{"class":80}," \"not exec\"",[40,3448,3449],{"class":46},"     # not exec — bit not set\n",[40,3451,3452,3454,3456,3459,3462],{"class":42,"line":180},[40,3453,3398],{"class":72},[40,3455,3419],{"class":76},[40,3457,3458],{"class":76}," ~",[40,3460,3461],{"class":72},"READ))          ",[40,3463,3464],{"class":46},"# 2 — mask off read bit (doesn't modify perms)\n",[40,3466,3467,3469,3472,3474,3477],{"class":42,"line":203},[40,3468,3398],{"class":72},[40,3470,3471],{"class":76},"&=",[40,3473,3458],{"class":76},[40,3475,3476],{"class":72},"WRITE))         ",[40,3478,3479],{"class":46},"# clear write bit from perms → perms is now 1\n",[40,3481,3482],{"class":42,"line":228},[40,3483,66],{"emptyLinePlaceholder":65},[40,3485,3486],{"class":42,"line":233},[40,3487,3488],{"class":46},"# ── Random numbers ──\n",[40,3490,3491,3493,3495,3498,3500],{"class":42,"line":239},[40,3492,194],{"class":98},[40,3494,540],{"class":80},[40,3496,3497],{"class":72},"$RANDOM",[40,3499,545],{"class":80},[40,3501,3502],{"class":46},"               # 0-32767 (pseudo-random, seeded from $$ and time)\n",[40,3504,3505,3507,3509,3512,3514,3517,3519],{"class":42,"line":265},[40,3506,194],{"class":98},[40,3508,270],{"class":80},[40,3510,3511],{"class":250},"RANDOM",[40,3513,276],{"class":80},[40,3515,3516],{"class":98},"100",[40,3518,282],{"class":80},[40,3520,3521],{"class":46},"    # 0-99 — dice roll\n",[40,3523,3524,3526,3528,3530,3532,3535,3537,3539,3541],{"class":42,"line":288},[40,3525,194],{"class":98},[40,3527,270],{"class":80},[40,3529,3511],{"class":250},[40,3531,276],{"class":80},[40,3533,3534],{"class":98},"6",[40,3536,462],{"class":80},[40,3538,465],{"class":98},[40,3540,282],{"class":80},[40,3542,3543],{"class":46},"  # 1-6 — die\n",[40,3545,3546],{"class":42,"line":307},[40,3547,3548],{"class":46},"# For better randomness (crypto): read \u002Fdev\u002Furandom\n",[40,3550,3551,3554,3557,3560,3563,3566,3568,3571,3574,3577],{"class":42,"line":326},[40,3552,3553],{"class":250},"od",[40,3555,3556],{"class":98}," -An",[40,3558,3559],{"class":98}," -tu4",[40,3561,3562],{"class":98}," -N4",[40,3564,3565],{"class":80}," \u002Fdev\u002Furandom",[40,3567,2135],{"class":76},[40,3569,3570],{"class":250}," tr",[40,3572,3573],{"class":98}," -d",[40,3575,3576],{"class":80}," ' '",[40,3578,3579],{"class":46},"  # 32-bit random integer\n",[40,3581,3582],{"class":42,"line":346},[40,3583,66],{"emptyLinePlaceholder":65},[40,3585,3586],{"class":42,"line":364},[40,3587,3588],{"class":46},"# ── Sequence generation without seq (Bash 4+) ──\n",[40,3590,3591,3593,3596],{"class":42,"line":376},[40,3592,194],{"class":98},[40,3594,3595],{"class":80}," {1..100}",[40,3597,3598],{"class":46},"                # 1 2 3 ... 100 — no subprocess\n",[40,3600,3601],{"class":42,"line":387},[40,3602,3603],{"class":46},"# ❌ seq 1 100 — forks a subprocess\n",[40,3605,3606],{"class":42,"line":399},[40,3607,3608],{"class":46},"# ✅ {1..100} — in-process brace expansion\n",[40,3610,3611],{"class":42,"line":409},[40,3612,66],{"emptyLinePlaceholder":65},[40,3614,3615],{"class":42,"line":420},[40,3616,3617],{"class":46},"# ── Conditional assignment (ternary-like) ──\n",[40,3619,3620,3622,3624],{"class":42,"line":431},[40,3621,73],{"class":72},[40,3623,77],{"class":76},[40,3625,3626],{"class":80},"5\n",[40,3628,3629],{"class":42,"line":436},[40,3630,3631],{"class":46},"# Bash has no ternary, but you can use:\n",[40,3633,3634,3636,3638,3640,3643,3646,3648,3651,3654,3656,3658,3660,3663],{"class":42,"line":442},[40,3635,87],{"class":72},[40,3637,185],{"class":76},[40,3639,702],{"class":98},[40,3641,3642],{"class":76}," ?",[40,3644,3645],{"class":72}," y ",[40,3647,77],{"class":76},[40,3649,3650],{"class":98}," 1",[40,3652,3653],{"class":76}," :",[40,3655,3645],{"class":72},[40,3657,77],{"class":76},[40,3659,702],{"class":98},[40,3661,3662],{"class":72},"))    ",[40,3664,3665],{"class":46},"# y is 1 if x > 0, else 0 — works in ((...))\n",[40,3667,3668],{"class":42,"line":448},[40,3669,3670],{"class":46},"# Or: y=0; ((x > 0)) && y=1  — more readable\n",[15,3672,3674],{"id":3673},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[28,3676,3677],{"language":30},[32,3678,3680],{"className":34,"code":3679,"language":30,"meta":36,"style":36},"# ── Integer division truncates toward ZERO, not floor ──\necho \"$((-7 \u002F 2))\"    # -3 (truncates toward zero: -3.5 → -3)\necho \"$((-7 % 2))\"    # -1 (remainder has sign of dividend)\n# For floor division of negatives: echo \"$((-(7) \u002F 2))\" → -3 (same, but be aware)\n\n# ── `((x = 0))` has exit status 1 (result is 0 = false) ──\nset -e\nx=1\n((x = 0))    # ✗ script EXITS — the assignment evaluates to 0, exit status 1!\n# Fix: ((x = 0)) || true  — or use: x=0 (plain assignment, no arithmetic context)\n\n# ── `[[ =~ ]]` regex is reset by each match ──\n[[ \"abc\" =~ a ]]; echo \"${BASH_REMATCH[0]}\"  # a\n[[ \"xyz\" =~ x ]]; echo \"${BASH_REMATCH[0]}\"  # x — previous match \"a\" is gone\n# Capture immediately after the match, don't reuse stale BASH_REMATCH.\n\n# ── `[[ =~ ]]` regex doesn't support \\d, \\w, \\s ──\n# These are PCRE shortcuts. Bash uses ERE (extended regex), which doesn't have them.\n[[ \"123\" =~ \\d+ ]]     # ✗ false — \\d is not ERE\n[[ \"123\" =~ [0-9]+ ]]  # ✓ true — use character classes\n\n# ── Glob in case patterns is not regex ──\ncase \"$file\" in\n    *.txt) echo \"text\" ;;   # glob — matches anything ending in .txt\n    *)     echo \"other\" ;;\nesac\n# case patterns are globs (*, ?, [...]), NOT regex. For regex, use [[ =~ ]].\n\n# ── Process substitution is Bash-only ──\n# \u003C(...) doesn't work in POSIX sh. Use temp files for portable scripts.\n\n# ── `[[ ]]` can't be used in POSIX sh ──\n# [[ ]] is a Bash extension. Use [ ] with careful quoting for POSIX scripts.\n",[20,3681,3682,3687,3705,3722,3727,3731,3736,3742,3750,3763,3768,3772,3777,3801,3824,3829,3833,3838,3843,3861,3875,3879,3884,3898,3919,3933,3938,3943,3947,3952,3957,3961,3966],{"__ignoreMap":36},[40,3683,3684],{"class":42,"line":43},[40,3685,3686],{"class":46},"# ── Integer division truncates toward ZERO, not floor ──\n",[40,3688,3689,3691,3693,3696,3698,3700,3702],{"class":42,"line":50},[40,3690,194],{"class":98},[40,3692,270],{"class":80},[40,3694,3695],{"class":250},"-7",[40,3697,297],{"class":80},[40,3699,333],{"class":98},[40,3701,282],{"class":80},[40,3703,3704],{"class":46},"    # -3 (truncates toward zero: -3.5 → -3)\n",[40,3706,3707,3709,3711,3713,3715,3717,3719],{"class":42,"line":56},[40,3708,194],{"class":98},[40,3710,270],{"class":80},[40,3712,3695],{"class":250},[40,3714,276],{"class":80},[40,3716,333],{"class":98},[40,3718,282],{"class":80},[40,3720,3721],{"class":46},"    # -1 (remainder has sign of dividend)\n",[40,3723,3724],{"class":42,"line":62},[40,3725,3726],{"class":46},"# For floor division of negatives: echo \"$((-(7) \u002F 2))\" → -3 (same, but be aware)\n",[40,3728,3729],{"class":42,"line":69},[40,3730,66],{"emptyLinePlaceholder":65},[40,3732,3733],{"class":42,"line":84},[40,3734,3735],{"class":46},"# ── `((x = 0))` has exit status 1 (result is 0 = false) ──\n",[40,3737,3738,3740],{"class":42,"line":108},[40,3739,2017],{"class":98},[40,3741,2020],{"class":98},[40,3743,3744,3746,3748],{"class":42,"line":123},[40,3745,73],{"class":72},[40,3747,77],{"class":76},[40,3749,736],{"class":80},[40,3751,3752,3754,3756,3758,3760],{"class":42,"line":137},[40,3753,87],{"class":72},[40,3755,77],{"class":76},[40,3757,702],{"class":98},[40,3759,3662],{"class":72},[40,3761,3762],{"class":46},"# ✗ script EXITS — the assignment evaluates to 0, exit status 1!\n",[40,3764,3765],{"class":42,"line":154},[40,3766,3767],{"class":46},"# Fix: ((x = 0)) || true  — or use: x=0 (plain assignment, no arithmetic context)\n",[40,3769,3770],{"class":42,"line":169},[40,3771,66],{"emptyLinePlaceholder":65},[40,3773,3774],{"class":42,"line":174},[40,3775,3776],{"class":46},"# ── `[[ =~ ]]` regex is reset by each match ──\n",[40,3778,3779,3781,3784,3787,3790,3792,3794,3796,3798],{"class":42,"line":180},[40,3780,874],{"class":72},[40,3782,3783],{"class":80},"\"abc\"",[40,3785,3786],{"class":76}," =~",[40,3788,3789],{"class":72}," a ]]; ",[40,3791,194],{"class":98},[40,3793,1053],{"class":80},[40,3795,1056],{"class":72},[40,3797,1059],{"class":80},[40,3799,3800],{"class":46},"  # a\n",[40,3802,3803,3805,3808,3810,3813,3815,3817,3819,3821],{"class":42,"line":203},[40,3804,874],{"class":72},[40,3806,3807],{"class":80},"\"xyz\"",[40,3809,3786],{"class":76},[40,3811,3812],{"class":72}," x ]]; ",[40,3814,194],{"class":98},[40,3816,1053],{"class":80},[40,3818,1056],{"class":72},[40,3820,1059],{"class":80},[40,3822,3823],{"class":46},"  # x — previous match \"a\" is gone\n",[40,3825,3826],{"class":42,"line":228},[40,3827,3828],{"class":46},"# Capture immediately after the match, don't reuse stale BASH_REMATCH.\n",[40,3830,3831],{"class":42,"line":233},[40,3832,66],{"emptyLinePlaceholder":65},[40,3834,3835],{"class":42,"line":239},[40,3836,3837],{"class":46},"# ── `[[ =~ ]]` regex doesn't support \\d, \\w, \\s ──\n",[40,3839,3840],{"class":42,"line":265},[40,3841,3842],{"class":46},"# These are PCRE shortcuts. Bash uses ERE (extended regex), which doesn't have them.\n",[40,3844,3845,3847,3850,3852,3855,3858],{"class":42,"line":288},[40,3846,874],{"class":72},[40,3848,3849],{"class":80},"\"123\"",[40,3851,3786],{"class":76},[40,3853,3854],{"class":98}," \\d",[40,3856,3857],{"class":72},"+ ]]     ",[40,3859,3860],{"class":46},"# ✗ false — \\d is not ERE\n",[40,3862,3863,3865,3867,3869,3872],{"class":42,"line":307},[40,3864,874],{"class":72},[40,3866,3849],{"class":80},[40,3868,3786],{"class":76},[40,3870,3871],{"class":72}," [0-9]+ ]]  ",[40,3873,3874],{"class":46},"# ✓ true — use character classes\n",[40,3876,3877],{"class":42,"line":326},[40,3878,66],{"emptyLinePlaceholder":65},[40,3880,3881],{"class":42,"line":346},[40,3882,3883],{"class":46},"# ── Glob in case patterns is not regex ──\n",[40,3885,3886,3889,3891,3893,3895],{"class":42,"line":364},[40,3887,3888],{"class":76},"case",[40,3890,540],{"class":80},[40,3892,1267],{"class":72},[40,3894,545],{"class":80},[40,3896,3897],{"class":76}," in\n",[40,3899,3900,3903,3906,3908,3910,3913,3916],{"class":42,"line":376},[40,3901,3902],{"class":76},"    *",[40,3904,2664],{"class":3905},"svAP2",[40,3907,2332],{"class":76},[40,3909,219],{"class":98},[40,3911,3912],{"class":80}," \"text\"",[40,3914,3915],{"class":72}," ;;   ",[40,3917,3918],{"class":46},"# glob — matches anything ending in .txt\n",[40,3920,3921,3924,3927,3930],{"class":42,"line":387},[40,3922,3923],{"class":76},"    *)",[40,3925,3926],{"class":98},"     echo",[40,3928,3929],{"class":80}," \"other\"",[40,3931,3932],{"class":72}," ;;\n",[40,3934,3935],{"class":42,"line":399},[40,3936,3937],{"class":76},"esac\n",[40,3939,3940],{"class":42,"line":409},[40,3941,3942],{"class":46},"# case patterns are globs (*, ?, [...]), NOT regex. For regex, use [[ =~ ]].\n",[40,3944,3945],{"class":42,"line":420},[40,3946,66],{"emptyLinePlaceholder":65},[40,3948,3949],{"class":42,"line":431},[40,3950,3951],{"class":46},"# ── Process substitution is Bash-only ──\n",[40,3953,3954],{"class":42,"line":436},[40,3955,3956],{"class":46},"# \u003C(...) doesn't work in POSIX sh. Use temp files for portable scripts.\n",[40,3958,3959],{"class":42,"line":442},[40,3960,66],{"emptyLinePlaceholder":65},[40,3962,3963],{"class":42,"line":448},[40,3964,3965],{"class":46},"# ── `[[ ]]` can't be used in POSIX sh ──\n",[40,3967,3968],{"class":42,"line":454},[40,3969,3970],{"class":46},"# [[ ]] is a Bash extension. Use [ ] with careful quoting for POSIX scripts.\n",[15,3972,3974],{"id":3973},"quick-quiz","🧠 Quick Quiz",[3976,3977,3978],"p",{},"What does this print?",[28,3980,3981],{"language":30},[32,3982,3984],{"className":34,"code":3983,"language":30,"meta":36,"style":36},"x=5\n((x--))\necho \"x=$x status=$?\"\n((x--))\necho \"x=$x status=$?\"\n",[20,3985,3986,3994,4002,4019,4027],{"__ignoreMap":36},[40,3987,3988,3990,3992],{"class":42,"line":43},[40,3989,73],{"class":72},[40,3991,77],{"class":76},[40,3993,3626],{"class":80},[40,3995,3996,3998,4000],{"class":42,"line":50},[40,3997,111],{"class":72},[40,3999,743],{"class":76},[40,4001,1831],{"class":72},[40,4003,4004,4006,4009,4011,4014,4017],{"class":42,"line":56},[40,4005,194],{"class":98},[40,4007,4008],{"class":80}," \"x=",[40,4010,1983],{"class":72},[40,4012,4013],{"class":80}," status=",[40,4015,4016],{"class":98},"$?",[40,4018,3131],{"class":80},[40,4020,4021,4023,4025],{"class":42,"line":62},[40,4022,111],{"class":72},[40,4024,743],{"class":76},[40,4026,1831],{"class":72},[40,4028,4029,4031,4033,4035,4037,4039],{"class":42,"line":69},[40,4030,194],{"class":98},[40,4032,4008],{"class":80},[40,4034,1983],{"class":72},[40,4036,4013],{"class":80},[40,4038,4016],{"class":98},[40,4040,3131],{"class":80},[4042,4043,4044,4048,4056,4059,4095,4115],"details",{},[4045,4046,4047],"summary",{},"Answer",[32,4049,4054],{"className":4050,"code":4052,"language":4053},[4051],"language-text","x=4 status=0\nx=3 status=0\n","text",[20,4055,4052],{"__ignoreMap":36},[3976,4057,4058],{},"Wait — let's trace carefully:",[4060,4061,4062,4087],"ol",{},[4063,4064,4065,4068,4069,4072,4073,4077,4078,4082,4083,4086],"li",{},[20,4066,4067],{},"x=5",". ",[20,4070,4071],{},"((x--))",": postfix decrement. The ",[4074,4075,4076],"em",{},"expression"," evaluates to the ",[4079,4080,4081],"strong",{},"old value"," (5), then x is decremented to 4. Since the expression result is 5 (non-zero), exit status is 0. ",[4079,4084,4085],{},"x=4, status=0",".",[4063,4088,4089,4091,4092,4086],{},[20,4090,4071],{},": expression evaluates to old value (4, non-zero), x decremented to 3. Exit status 0. ",[4079,4093,4094],{},"x=3, status=0",[3976,4096,4097,4098,4100,4101,4103,4104,4106,4107,4110,4111,4114],{},"But if x had been 1 before a ",[20,4099,4071],{},": expression evaluates to 1 (old value, non-zero) → exit 0, x becomes 0. Then another ",[20,4102,4071],{},": expression evaluates to 0 (old value) → exit ",[4079,4105,465],{}," (result is zero = false). With ",[20,4108,4109],{},"set -e",", the script would ",[4079,4112,4113],{},"exit"," here.",[3976,4116,4117,4120,4121,4123,4124,4127,4128,4131,4132,4134,4135,4086],{},[4079,4118,4119],{},"The lesson",": ",[20,4122,4071],{}," returns exit status based on the ",[4079,4125,4126],{},"expression value"," (the old x), not the new x. When x decrements to 0, the ",[4074,4129,4130],{},"next"," ",[20,4133,4071],{}," returns exit 1 because the old value is 0. This silently triggers ",[20,4136,4109],{},[4138,4139,4140],"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 pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}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 .svAP2, html code.shiki .svAP2{--shiki-default:#032F62;--shiki-github-dark:#DBEDFF}",{"title":36,"searchDepth":50,"depth":50,"links":4142},[4143,4145,4147,4149,4151,4152,4154,4156,4157,4158,4159,4160,4161],{"id":17,"depth":50,"text":4144},"Arithmetic Evaluation: ((...)) vs $((...))",{"id":482,"depth":50,"text":4146},"Anti-Pattern: Using expr or let for Arithmetic",{"id":645,"depth":50,"text":4148},"((...)) Exit Status Quirk",{"id":809,"depth":50,"text":4150},"Test Conditionals: [[... ]] vs [... ]",{"id":1377,"depth":50,"text":1378},{"id":1710,"depth":50,"text":4153},"Command Substitution: $(...) Internals",{"id":2046,"depth":50,"text":4155},"Process Substitution: \u003C(...) and >(...)",{"id":2444,"depth":50,"text":2445},{"id":2684,"depth":50,"text":2685},{"id":3079,"depth":50,"text":3080},{"id":3270,"depth":50,"text":3271},{"id":3673,"depth":50,"text":3674},{"id":3973,"depth":50,"text":3974},"Deep-dive into Bash arithmetic evaluation ((...)), test conditionals ([[]] vs []), command\u002Fprocess substitution internals, brace expansion, and glob mechanics. Code-first reference for senior engineers.","md",{},"\u002Fbash\u002F03-operators-and-expansions",{"title":5,"description":4162},"bash\u002F03-operators-and-expansions","HZ9jGBJ6mxp1f6sSLupBLYrCk7mqDZB4OsLs_HOOAs8",1789924644815]