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