[{"data":1,"prerenderedAt":2397},["ShallowReactive",2],{"page-\u002Fbash\u002F01-introduction-and-setup":3},{"id":4,"title":5,"body":6,"description":2390,"extension":2391,"meta":2392,"navigation":106,"path":2393,"seo":2394,"stem":2395,"__hash__":2396},"content\u002Fbash\u002F01-introduction-and-setup.md","Bash 01 — Shell Architecture, Shebang Semantics & Strict-Mode Internals",{"type":7,"value":8,"toc":2375},"minimark",[9,14,19,235,239,243,1036,1040,1093,1100,1393,1400,1599,1609,1781,1785,1873,1877,2129,2133,2280,2284,2287,2318,2371],[10,11,13],"h1",{"id":12},"_01-shell-architecture-shebang-semantics-strict-mode-internals","01 — Shell Architecture, Shebang Semantics & Strict-Mode Internals",[15,16,18],"h2",{"id":17},"how-the-kernel-resolves-a-shebang","How the Kernel Resolves a Shebang",[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","#!\u002Fusr\u002Fbin\u002Fenv bash\n# The kernel's execve() reads the first ~128 bytes (BINPRM_BUF_SIZE).\n# If it finds \"#!\", the rest of the line is split into interpreter + optional single arg.\n#   #!\u002Fusr\u002Fbin\u002Fenv bash        → execve(\"\u002Fusr\u002Fbin\u002Fenv\", [\"env\", \"bash\", scriptpath])\n#   #!\u002Fbin\u002Fbash                → execve(\"\u002Fbin\u002Fbash\", [\"bash\", scriptpath])\n#   #!\u002Fusr\u002Fbin\u002Fenv bash -Eeuo pipefail → execve(\"\u002Fusr\u002Fbin\u002Fenv\", [\"env\", \"bash -Eeuo pipefail\", scriptpath])\n#     ^-- \"bash -Eeuo pipefail\" is passed as ONE arg to env (kernel allows max 1 arg).\n#          env splits it via its own argv parsing — works on Linux, NOT portable to all Unices.\n#\n# CRLF trap: \"#!\u002Fusr\u002Fbin\u002Fenv bash\\r\\n\" → interpreter name is \"bash\\r\" → \"No such file or directory\".\n# Fix: sed -i 's\u002F\\r$\u002F\u002F' script.sh  OR  configure editor to LF.\n\n# Verify the resolved interpreter path and version at runtime:\nprintf 'interpreter: %s\\n' \"$(readlink -f \"$(command -v bash)\")\"\nprintf 'bash version: %s\\n' \"$BASH_VERSION\"           # e.g. 5.2.15(1)-release\nprintf 'bash major:  %d\\n' \"$BASH_VERSINFO[0]\"        # e.g. 5 — use for feature gating\n(( BASH_VERSINFO[0] >= 4 )) || { echo \"Requires Bash 4+ (associative arrays, mapfile).\" >&2; exit 1; }\n","",[30,31,32,41,47,53,59,65,71,77,83,89,95,101,108,114,146,167,186],"code",{"__ignoreMap":28},[33,34,37],"span",{"class":35,"line":36},"line",1,[33,38,40],{"class":39},"sdCPZ","#!\u002Fusr\u002Fbin\u002Fenv bash\n",[33,42,44],{"class":35,"line":43},2,[33,45,46],{"class":39},"# The kernel's execve() reads the first ~128 bytes (BINPRM_BUF_SIZE).\n",[33,48,50],{"class":35,"line":49},3,[33,51,52],{"class":39},"# If it finds \"#!\", the rest of the line is split into interpreter + optional single arg.\n",[33,54,56],{"class":35,"line":55},4,[33,57,58],{"class":39},"#   #!\u002Fusr\u002Fbin\u002Fenv bash        → execve(\"\u002Fusr\u002Fbin\u002Fenv\", [\"env\", \"bash\", scriptpath])\n",[33,60,62],{"class":35,"line":61},5,[33,63,64],{"class":39},"#   #!\u002Fbin\u002Fbash                → execve(\"\u002Fbin\u002Fbash\", [\"bash\", scriptpath])\n",[33,66,68],{"class":35,"line":67},6,[33,69,70],{"class":39},"#   #!\u002Fusr\u002Fbin\u002Fenv bash -Eeuo pipefail → execve(\"\u002Fusr\u002Fbin\u002Fenv\", [\"env\", \"bash -Eeuo pipefail\", scriptpath])\n",[33,72,74],{"class":35,"line":73},7,[33,75,76],{"class":39},"#     ^-- \"bash -Eeuo pipefail\" is passed as ONE arg to env (kernel allows max 1 arg).\n",[33,78,80],{"class":35,"line":79},8,[33,81,82],{"class":39},"#          env splits it via its own argv parsing — works on Linux, NOT portable to all Unices.\n",[33,84,86],{"class":35,"line":85},9,[33,87,88],{"class":39},"#\n",[33,90,92],{"class":35,"line":91},10,[33,93,94],{"class":39},"# CRLF trap: \"#!\u002Fusr\u002Fbin\u002Fenv bash\\r\\n\" → interpreter name is \"bash\\r\" → \"No such file or directory\".\n",[33,96,98],{"class":35,"line":97},11,[33,99,100],{"class":39},"# Fix: sed -i 's\u002F\\r$\u002F\u002F' script.sh  OR  configure editor to LF.\n",[33,102,104],{"class":35,"line":103},12,[33,105,107],{"emptyLinePlaceholder":106},true,"\n",[33,109,111],{"class":35,"line":110},13,[33,112,113],{"class":39},"# Verify the resolved interpreter path and version at runtime:\n",[33,115,117,121,125,128,132,135,137,140,143],{"class":35,"line":116},14,[33,118,120],{"class":119},"snvgF","printf",[33,122,124],{"class":123},"sJ6F3"," 'interpreter: %s\\n'",[33,126,127],{"class":123}," \"$(",[33,129,131],{"class":130},"sIsaT","readlink",[33,133,134],{"class":119}," -f",[33,136,127],{"class":123},[33,138,139],{"class":119},"command",[33,141,142],{"class":119}," -v",[33,144,145],{"class":123}," bash)\")\"\n",[33,147,149,151,154,157,161,164],{"class":35,"line":148},15,[33,150,120],{"class":119},[33,152,153],{"class":123}," 'bash version: %s\\n'",[33,155,156],{"class":123}," \"",[33,158,160],{"class":159},"ssxIu","$BASH_VERSION",[33,162,163],{"class":123},"\"",[33,165,166],{"class":39},"           # e.g. 5.2.15(1)-release\n",[33,168,170,172,175,177,180,183],{"class":35,"line":169},16,[33,171,120],{"class":119},[33,173,174],{"class":123}," 'bash major:  %d\\n'",[33,176,156],{"class":123},[33,178,179],{"class":159},"$BASH_VERSINFO",[33,181,182],{"class":123},"[0]\"",[33,184,185],{"class":39},"        # e.g. 5 — use for feature gating\n",[33,187,189,192,195,198,202,205,208,211,214,217,220,223,226,229,232],{"class":35,"line":188},17,[33,190,191],{"class":159},"(( BASH_VERSINFO[",[33,193,194],{"class":119},"0",[33,196,197],{"class":159},"] ",[33,199,201],{"class":200},"svdQ7",">=",[33,203,204],{"class":119}," 4",[33,206,207],{"class":159}," )) ",[33,209,210],{"class":200},"||",[33,212,213],{"class":159}," { ",[33,215,216],{"class":119},"echo",[33,218,219],{"class":123}," \"Requires Bash 4+ (associative arrays, mapfile).\"",[33,221,222],{"class":200}," >&2",[33,224,225],{"class":159},"; ",[33,227,228],{"class":119},"exit",[33,230,231],{"class":119}," 1",[33,233,234],{"class":159},"; }\n",[15,236,238],{"id":237},"production-script-skeleton-annotated","Production Script Skeleton (Annotated)",[240,241,242],"p",{},"Every script below uses this skeleton. Each line is load-bearing.",[20,244,245],{"language":22},[24,246,248],{"className":26,"code":247,"language":22,"meta":28,"style":28},"#!\u002Fusr\u002Fbin\u002Fenv bash\n# ── Strict mode ──────────────────────────────────────────────\nset -Eeuo pipefail\n#  │ │ │  └─ pipefail: pipeline returns rightmost non-zero exit (default: returns last cmd only).\n#  │ │ └──── u: unset variable expansion is a fatal error (not empty string).\n#  │ └────── e: exit immediately on any command failure (with caveats — see below).\n#  └──────── E: ERR trap inherits into functions and subshells (critical for trap ERR).\n#\n# ── Defensive globals ───────────────────────────────────────\nshopt -s inherit_errexit 2>\u002Fdev\u002Fnull || true  # Bash 4.4+: subshells inherit `set -e` (default: off!)\nshopt -s nullglob        # Unmatched globs expand to nothing (not the literal pattern).\nshopt -s globstar        # ** matches recursively (find **\u002F*.py).\nshopt -s extglob         # Extended pattern matching: ?(), *(), +(), @(), !().\nIFS=$' \\t\\n'            # Explicit IFS — word-splitting on space, tab, newline only.\n\n# ── Constants ───────────────────────────────────────────────\nreadonly SCRIPT_NAME=$(basename \"${BASH_SOURCE[0]}\")\nreadonly SCRIPT_DIR=$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)  # resolves symlinks? No — see 13.\nreadonly LOG_FD=2       # stderr\n\n# ── Error handling ──────────────────────────────────────────\nerr_report() {\n    printf '[%s] %s:%d: %s failed (exit %d)\\n' \\\n        \"$(date -Iseconds)\" \"$SCRIPT_NAME\" \"${1:-0}\" \"${BASH_COMMAND:-?}\" \"${2:-0}\" >&\"$LOG_FD\"\n}\ntrap 'err_report \"$LINENO\" \"$?\"' ERR       # fires on any uncaught command failure (needs set -e -E).\ntrap 'cleanup' EXIT                          # fires on any exit path — normal, set -e abort, signal.\n\ncleanup() {\n    local exit_code=$?\n    [[ -n \"${tmpdir:-}\" && -d \"$tmpdir\" ]] && rm -rf -- \"$tmpdir\"\n    exit \"$exit_code\"   # preserve the original exit code (trap EXIT receives it).\n}\n\n# ── Logging ─────────────────────────────────────────────────\ndeclare -i LOG_LEVEL=${LOG_LEVEL:-3}  # 0=trace 1=debug 2=info 3=warn 4=error 5=off\nlog() {\n    local level=$1; shift\n    local -A levels=([TRACE]=0 [DEBUG]=1 [INFO]=2 [WARN]=3 [ERROR]=4 [OFF]=5)\n    (( levels[$level] >= LOG_LEVEL )) && printf '[%s] %s: %s\\n' \"$(date -Iseconds)\" \"$level\" \"$*\" >&\"$LOG_FD\"\n}\n\ndie() { log ERROR \"$*\"; exit 1; }\n\n# ── Main ─────────────────────────────────────────────────────\nmain() {\n    log INFO \"Starting $SCRIPT_NAME\"\n    tmpdir=$(mktemp -d)  # mktemp -d: creates 0700-permission temp dir — race-free, no symlink attack.\n    log DEBUG \"tmpdir=$tmpdir\"\n    # ... script logic ...\n    log INFO \"Done\"\n}\n\nmain \"$@\"\n",[30,249,250,254,259,270,275,280,285,290,294,299,325,337,349,361,381,385,390,418,457,473,478,484,493,505,572,578,593,607,612,620,634,684,700,705,710,716,741,749,768,844,890,895,900,928,933,939,947,963,983,998,1004,1014,1019,1024],{"__ignoreMap":28},[33,251,252],{"class":35,"line":36},[33,253,40],{"class":39},[33,255,256],{"class":35,"line":43},[33,257,258],{"class":39},"# ── Strict mode ──────────────────────────────────────────────\n",[33,260,261,264,267],{"class":35,"line":49},[33,262,263],{"class":119},"set",[33,265,266],{"class":119}," -Eeuo",[33,268,269],{"class":123}," pipefail\n",[33,271,272],{"class":35,"line":55},[33,273,274],{"class":39},"#  │ │ │  └─ pipefail: pipeline returns rightmost non-zero exit (default: returns last cmd only).\n",[33,276,277],{"class":35,"line":61},[33,278,279],{"class":39},"#  │ │ └──── u: unset variable expansion is a fatal error (not empty string).\n",[33,281,282],{"class":35,"line":67},[33,283,284],{"class":39},"#  │ └────── e: exit immediately on any command failure (with caveats — see below).\n",[33,286,287],{"class":35,"line":73},[33,288,289],{"class":39},"#  └──────── E: ERR trap inherits into functions and subshells (critical for trap ERR).\n",[33,291,292],{"class":35,"line":79},[33,293,88],{"class":39},[33,295,296],{"class":35,"line":85},[33,297,298],{"class":39},"# ── Defensive globals ───────────────────────────────────────\n",[33,300,301,304,307,310,313,316,319,322],{"class":35,"line":91},[33,302,303],{"class":130},"shopt",[33,305,306],{"class":119}," -s",[33,308,309],{"class":123}," inherit_errexit",[33,311,312],{"class":200}," 2>",[33,314,315],{"class":123},"\u002Fdev\u002Fnull",[33,317,318],{"class":200}," ||",[33,320,321],{"class":119}," true",[33,323,324],{"class":39},"  # Bash 4.4+: subshells inherit `set -e` (default: off!)\n",[33,326,327,329,331,334],{"class":35,"line":97},[33,328,303],{"class":130},[33,330,306],{"class":119},[33,332,333],{"class":123}," nullglob",[33,335,336],{"class":39},"        # Unmatched globs expand to nothing (not the literal pattern).\n",[33,338,339,341,343,346],{"class":35,"line":103},[33,340,303],{"class":130},[33,342,306],{"class":119},[33,344,345],{"class":123}," globstar",[33,347,348],{"class":39},"        # ** matches recursively (find **\u002F*.py).\n",[33,350,351,353,355,358],{"class":35,"line":110},[33,352,303],{"class":130},[33,354,306],{"class":119},[33,356,357],{"class":123}," extglob",[33,359,360],{"class":39},"         # Extended pattern matching: ?(), *(), +(), @(), !().\n",[33,362,363,366,369,372,375,378],{"class":35,"line":116},[33,364,365],{"class":159},"IFS",[33,367,368],{"class":200},"=",[33,370,371],{"class":123},"$' ",[33,373,374],{"class":119},"\\t\\n",[33,376,377],{"class":123},"'",[33,379,380],{"class":39},"            # Explicit IFS — word-splitting on space, tab, newline only.\n",[33,382,383],{"class":35,"line":148},[33,384,107],{"emptyLinePlaceholder":106},[33,386,387],{"class":35,"line":169},[33,388,389],{"class":39},"# ── Constants ───────────────────────────────────────────────\n",[33,391,392,395,398,400,403,406,409,412,415],{"class":35,"line":188},[33,393,394],{"class":200},"readonly",[33,396,397],{"class":159}," SCRIPT_NAME",[33,399,368],{"class":200},[33,401,402],{"class":159},"$(",[33,404,405],{"class":130},"basename",[33,407,408],{"class":123}," \"${",[33,410,411],{"class":159},"BASH_SOURCE",[33,413,414],{"class":123},"[0]}\"",[33,416,417],{"class":159},")\n",[33,419,421,423,426,428,430,433,435,438,440,442,445,448,451,454],{"class":35,"line":420},18,[33,422,394],{"class":200},[33,424,425],{"class":159}," SCRIPT_DIR",[33,427,368],{"class":200},[33,429,402],{"class":159},[33,431,432],{"class":119},"cd",[33,434,127],{"class":123},[33,436,437],{"class":130},"dirname",[33,439,408],{"class":123},[33,441,411],{"class":159},[33,443,444],{"class":123},"[0]}\")\"",[33,446,447],{"class":159}," && ",[33,449,450],{"class":119},"pwd",[33,452,453],{"class":159},")  ",[33,455,456],{"class":39},"# resolves symlinks? No — see 13.\n",[33,458,460,462,465,467,470],{"class":35,"line":459},19,[33,461,394],{"class":200},[33,463,464],{"class":159}," LOG_FD",[33,466,368],{"class":200},[33,468,469],{"class":119},"2",[33,471,472],{"class":39},"       # stderr\n",[33,474,476],{"class":35,"line":475},20,[33,477,107],{"emptyLinePlaceholder":106},[33,479,481],{"class":35,"line":480},21,[33,482,483],{"class":39},"# ── Error handling ──────────────────────────────────────────\n",[33,485,487,490],{"class":35,"line":486},22,[33,488,489],{"class":130},"err_report",[33,491,492],{"class":159},"() {\n",[33,494,496,499,502],{"class":35,"line":495},23,[33,497,498],{"class":119},"    printf",[33,500,501],{"class":123}," '[%s] %s:%d: %s failed (exit %d)\\n'",[33,503,504],{"class":119}," \\\n",[33,506,508,511,514,517,520,522,525,527,529,532,535,538,540,542,545,547,550,552,555,557,559,561,564,566,569],{"class":35,"line":507},24,[33,509,510],{"class":123},"        \"$(",[33,512,513],{"class":130},"date",[33,515,516],{"class":119}," -Iseconds",[33,518,519],{"class":123},")\"",[33,521,156],{"class":123},[33,523,524],{"class":159},"$SCRIPT_NAME",[33,526,163],{"class":123},[33,528,156],{"class":123},[33,530,531],{"class":119},"${1",[33,533,534],{"class":200},":-",[33,536,537],{"class":119},"0}",[33,539,163],{"class":123},[33,541,408],{"class":123},[33,543,544],{"class":159},"BASH_COMMAND",[33,546,534],{"class":200},[33,548,549],{"class":123},"?}\"",[33,551,156],{"class":123},[33,553,554],{"class":119},"${2",[33,556,534],{"class":200},[33,558,537],{"class":119},[33,560,163],{"class":123},[33,562,563],{"class":200}," >&",[33,565,163],{"class":123},[33,567,568],{"class":159},"$LOG_FD",[33,570,571],{"class":123},"\"\n",[33,573,575],{"class":35,"line":574},25,[33,576,577],{"class":159},"}\n",[33,579,581,584,587,590],{"class":35,"line":580},26,[33,582,583],{"class":119},"trap",[33,585,586],{"class":123}," 'err_report \"$LINENO\" \"$?\"'",[33,588,589],{"class":123}," ERR",[33,591,592],{"class":39},"       # fires on any uncaught command failure (needs set -e -E).\n",[33,594,596,598,601,604],{"class":35,"line":595},27,[33,597,583],{"class":119},[33,599,600],{"class":123}," 'cleanup'",[33,602,603],{"class":123}," EXIT",[33,605,606],{"class":39},"                          # fires on any exit path — normal, set -e abort, signal.\n",[33,608,610],{"class":35,"line":609},28,[33,611,107],{"emptyLinePlaceholder":106},[33,613,615,618],{"class":35,"line":614},29,[33,616,617],{"class":130},"cleanup",[33,619,492],{"class":159},[33,621,623,626,629,631],{"class":35,"line":622},30,[33,624,625],{"class":200},"    local",[33,627,628],{"class":159}," exit_code",[33,630,368],{"class":200},[33,632,633],{"class":119},"$?\n",[33,635,637,640,643,645,648,650,653,656,659,661,664,666,669,672,675,678,680,682],{"class":35,"line":636},31,[33,638,639],{"class":159},"    [[ ",[33,641,642],{"class":200},"-n",[33,644,408],{"class":123},[33,646,647],{"class":159},"tmpdir",[33,649,534],{"class":200},[33,651,652],{"class":123},"}\"",[33,654,655],{"class":200}," &&",[33,657,658],{"class":200}," -d",[33,660,156],{"class":123},[33,662,663],{"class":159},"$tmpdir",[33,665,163],{"class":123},[33,667,668],{"class":159}," ]] && ",[33,670,671],{"class":130},"rm",[33,673,674],{"class":119}," -rf",[33,676,677],{"class":119}," --",[33,679,156],{"class":123},[33,681,663],{"class":159},[33,683,571],{"class":123},[33,685,687,690,692,695,697],{"class":35,"line":686},32,[33,688,689],{"class":119},"    exit",[33,691,156],{"class":123},[33,693,694],{"class":159},"$exit_code",[33,696,163],{"class":123},[33,698,699],{"class":39},"   # preserve the original exit code (trap EXIT receives it).\n",[33,701,703],{"class":35,"line":702},33,[33,704,577],{"class":159},[33,706,708],{"class":35,"line":707},34,[33,709,107],{"emptyLinePlaceholder":106},[33,711,713],{"class":35,"line":712},35,[33,714,715],{"class":39},"# ── Logging ─────────────────────────────────────────────────\n",[33,717,719,722,725,728,730,733,735,738],{"class":35,"line":718},36,[33,720,721],{"class":200},"declare",[33,723,724],{"class":119}," -i",[33,726,727],{"class":159}," LOG_LEVEL",[33,729,368],{"class":200},[33,731,732],{"class":159},"${LOG_LEVEL",[33,734,534],{"class":200},[33,736,737],{"class":159},"3}  ",[33,739,740],{"class":39},"# 0=trace 1=debug 2=info 3=warn 4=error 5=off\n",[33,742,744,747],{"class":35,"line":743},37,[33,745,746],{"class":130},"log",[33,748,492],{"class":159},[33,750,752,754,757,759,763,765],{"class":35,"line":751},38,[33,753,625],{"class":200},[33,755,756],{"class":159}," level",[33,758,368],{"class":200},[33,760,762],{"class":761},"sCrzJ","$1",[33,764,225],{"class":159},[33,766,767],{"class":119},"shift\n",[33,769,771,773,776,779,781,784,787,790,792,795,798,800,803,805,808,810,812,814,817,819,822,824,827,829,832,834,837,839,842],{"class":35,"line":770},39,[33,772,625],{"class":200},[33,774,775],{"class":119}," -A",[33,777,778],{"class":159}," levels",[33,780,368],{"class":200},[33,782,783],{"class":159},"([",[33,785,786],{"class":130},"TRACE",[33,788,789],{"class":159},"]=",[33,791,194],{"class":119},[33,793,794],{"class":159}," [",[33,796,797],{"class":130},"DEBUG",[33,799,789],{"class":159},[33,801,802],{"class":119},"1",[33,804,794],{"class":159},[33,806,807],{"class":130},"INFO",[33,809,789],{"class":159},[33,811,469],{"class":119},[33,813,794],{"class":159},[33,815,816],{"class":130},"WARN",[33,818,789],{"class":159},[33,820,821],{"class":119},"3",[33,823,794],{"class":159},[33,825,826],{"class":130},"ERROR",[33,828,789],{"class":159},[33,830,831],{"class":119},"4",[33,833,794],{"class":159},[33,835,836],{"class":130},"OFF",[33,838,789],{"class":159},[33,840,841],{"class":119},"5",[33,843,417],{"class":159},[33,845,847,850,852,855,857,860,862,864,866,868,870,873,875,877,880,882,884,886,888],{"class":35,"line":846},40,[33,848,849],{"class":159},"    (( levels[$level] ",[33,851,201],{"class":200},[33,853,854],{"class":159}," LOG_LEVEL )) && ",[33,856,120],{"class":119},[33,858,859],{"class":123}," '[%s] %s: %s\\n'",[33,861,127],{"class":123},[33,863,513],{"class":130},[33,865,516],{"class":119},[33,867,519],{"class":123},[33,869,156],{"class":123},[33,871,872],{"class":159},"$level",[33,874,163],{"class":123},[33,876,156],{"class":123},[33,878,879],{"class":119},"$*",[33,881,163],{"class":123},[33,883,563],{"class":200},[33,885,163],{"class":123},[33,887,568],{"class":159},[33,889,571],{"class":123},[33,891,893],{"class":35,"line":892},41,[33,894,577],{"class":159},[33,896,898],{"class":35,"line":897},42,[33,899,107],{"emptyLinePlaceholder":106},[33,901,903,906,909,911,914,916,918,920,922,924,926],{"class":35,"line":902},43,[33,904,905],{"class":130},"die",[33,907,908],{"class":159},"() { ",[33,910,746],{"class":130},[33,912,913],{"class":123}," ERROR",[33,915,156],{"class":123},[33,917,879],{"class":119},[33,919,163],{"class":123},[33,921,225],{"class":159},[33,923,228],{"class":119},[33,925,231],{"class":119},[33,927,234],{"class":159},[33,929,931],{"class":35,"line":930},44,[33,932,107],{"emptyLinePlaceholder":106},[33,934,936],{"class":35,"line":935},45,[33,937,938],{"class":39},"# ── Main ─────────────────────────────────────────────────────\n",[33,940,942,945],{"class":35,"line":941},46,[33,943,944],{"class":130},"main",[33,946,492],{"class":159},[33,948,950,953,956,959,961],{"class":35,"line":949},47,[33,951,952],{"class":130},"    log",[33,954,955],{"class":123}," INFO",[33,957,958],{"class":123}," \"Starting ",[33,960,524],{"class":159},[33,962,571],{"class":123},[33,964,966,969,971,973,976,978,980],{"class":35,"line":965},48,[33,967,968],{"class":159},"    tmpdir",[33,970,368],{"class":200},[33,972,402],{"class":159},[33,974,975],{"class":130},"mktemp",[33,977,658],{"class":119},[33,979,453],{"class":159},[33,981,982],{"class":39},"# mktemp -d: creates 0700-permission temp dir — race-free, no symlink attack.\n",[33,984,986,988,991,994,996],{"class":35,"line":985},49,[33,987,952],{"class":130},[33,989,990],{"class":123}," DEBUG",[33,992,993],{"class":123}," \"tmpdir=",[33,995,663],{"class":159},[33,997,571],{"class":123},[33,999,1001],{"class":35,"line":1000},50,[33,1002,1003],{"class":39},"    # ... script logic ...\n",[33,1005,1007,1009,1011],{"class":35,"line":1006},51,[33,1008,952],{"class":130},[33,1010,955],{"class":123},[33,1012,1013],{"class":123}," \"Done\"\n",[33,1015,1017],{"class":35,"line":1016},52,[33,1018,577],{"class":159},[33,1020,1022],{"class":35,"line":1021},53,[33,1023,107],{"emptyLinePlaceholder":106},[33,1025,1027,1029,1031,1034],{"class":35,"line":1026},54,[33,1028,944],{"class":130},[33,1030,156],{"class":123},[33,1032,1033],{"class":119},"$@",[33,1035,571],{"class":123},[15,1037,1039],{"id":1038},"anti-pattern-the-naive-shebang","Anti-Pattern: The Naive Shebang",[20,1041,1042],{"language":22},[24,1043,1045],{"className":26,"code":1044,"language":22,"meta":28,"style":28},"# ❌ NAIVE — hardcodes path, breaks on NixOS, Homebrew, conda, alpine\n#!\u002Fbin\u002Fbash\n\n# ❌ NAIVE — passes flags as single arg to env, non-portable\n#!\u002Fusr\u002Fbin\u002Fenv bash -euo pipefail\n\n# ✅ CORRECT — portable, strict mode set in-body (not shebang)\n#!\u002Fusr\u002Fbin\u002Fenv bash\nset -euo pipefail\n",[30,1046,1047,1052,1057,1061,1066,1071,1075,1080,1084],{"__ignoreMap":28},[33,1048,1049],{"class":35,"line":36},[33,1050,1051],{"class":39},"# ❌ NAIVE — hardcodes path, breaks on NixOS, Homebrew, conda, alpine\n",[33,1053,1054],{"class":35,"line":43},[33,1055,1056],{"class":39},"#!\u002Fbin\u002Fbash\n",[33,1058,1059],{"class":35,"line":49},[33,1060,107],{"emptyLinePlaceholder":106},[33,1062,1063],{"class":35,"line":55},[33,1064,1065],{"class":39},"# ❌ NAIVE — passes flags as single arg to env, non-portable\n",[33,1067,1068],{"class":35,"line":61},[33,1069,1070],{"class":39},"#!\u002Fusr\u002Fbin\u002Fenv bash -euo pipefail\n",[33,1072,1073],{"class":35,"line":67},[33,1074,107],{"emptyLinePlaceholder":106},[33,1076,1077],{"class":35,"line":73},[33,1078,1079],{"class":39},"# ✅ CORRECT — portable, strict mode set in-body (not shebang)\n",[33,1081,1082],{"class":35,"line":79},[33,1083,40],{"class":39},[33,1085,1086,1088,1091],{"class":35,"line":85},[33,1087,263],{"class":119},[33,1089,1090],{"class":119}," -euo",[33,1092,269],{"class":123},[15,1094,1096,1099],{"id":1095},"set-e-doesnt-catch-everything",[30,1097,1098],{},"set -e"," Doesn't Catch Everything",[20,1101,1102],{"language":22},[24,1103,1105],{"className":26,"code":1104,"language":22,"meta":28,"style":28},"set -e\n\n# ── Commands whose exit status is \"tested\" don't trigger -e ──\nfalse && echo \"never\"          # OK — false is tested by &&\nif false; then echo \"never\"; fi # OK — false is tested by if\nfalse || true                   # OK — false is tested by ||\n! false                         # OK — negated\n\n# ── Pipeline: only the LAST command's status matters (without pipefail) ──\nfalse | true                     # exit 0 — false is masked!\n# pipefail fixes this: set -o pipefail  → false | true exits 1.\n\n# ── Functions: a function returning non-zero triggers -e ONLY if called as a simple command ──\nmaybe_fail() { return 1; }\nmaybe_fail                       # ✗ script exits (simple command)\nmaybe_fail || true               # OK — tested by ||\nif maybe_fail; then echo \"ok\"; fi # OK — tested by if\n\n# ── Subshell vs command substitution ──\n(false)                           # ✗ subshell failure propagates with -e\nx=$(false)                       # ✗ command substitution failure propagates with -e (Bash 4.4+)\nx=$(false) || true               # OK — tested\n\n# ── `grep` returns 1 when no match — silently kills the script ──\ngrep \"pattern\" file.txt          # ✗ if no match → exit 1 → script dies\ngrep \"pattern\" file.txt || true  # OK — explicit \"no match is fine\"\ngrep -q \"pattern\" file.txt && echo \"found\"  # OK — tested by &&\n",[30,1106,1107,1114,1118,1123,1138,1164,1175,1185,1189,1194,1206,1211,1215,1220,1234,1241,1252,1275,1279,1284,1297,1314,1334,1338,1343,1357,1372],{"__ignoreMap":28},[33,1108,1109,1111],{"class":35,"line":36},[33,1110,263],{"class":119},[33,1112,1113],{"class":119}," -e\n",[33,1115,1116],{"class":35,"line":43},[33,1117,107],{"emptyLinePlaceholder":106},[33,1119,1120],{"class":35,"line":49},[33,1121,1122],{"class":39},"# ── Commands whose exit status is \"tested\" don't trigger -e ──\n",[33,1124,1125,1128,1130,1132,1135],{"class":35,"line":55},[33,1126,1127],{"class":119},"false",[33,1129,447],{"class":159},[33,1131,216],{"class":119},[33,1133,1134],{"class":123}," \"never\"",[33,1136,1137],{"class":39},"          # OK — false is tested by &&\n",[33,1139,1140,1143,1146,1148,1151,1154,1156,1158,1161],{"class":35,"line":61},[33,1141,1142],{"class":200},"if",[33,1144,1145],{"class":119}," false",[33,1147,225],{"class":159},[33,1149,1150],{"class":200},"then",[33,1152,1153],{"class":119}," echo",[33,1155,1134],{"class":123},[33,1157,225],{"class":159},[33,1159,1160],{"class":200},"fi",[33,1162,1163],{"class":39}," # OK — false is tested by if\n",[33,1165,1166,1168,1170,1172],{"class":35,"line":67},[33,1167,1127],{"class":119},[33,1169,318],{"class":200},[33,1171,321],{"class":119},[33,1173,1174],{"class":39},"                   # OK — false is tested by ||\n",[33,1176,1177,1180,1182],{"class":35,"line":73},[33,1178,1179],{"class":200},"!",[33,1181,1145],{"class":119},[33,1183,1184],{"class":39},"                         # OK — negated\n",[33,1186,1187],{"class":35,"line":79},[33,1188,107],{"emptyLinePlaceholder":106},[33,1190,1191],{"class":35,"line":85},[33,1192,1193],{"class":39},"# ── Pipeline: only the LAST command's status matters (without pipefail) ──\n",[33,1195,1196,1198,1201,1203],{"class":35,"line":91},[33,1197,1127],{"class":119},[33,1199,1200],{"class":200}," |",[33,1202,321],{"class":119},[33,1204,1205],{"class":39},"                     # exit 0 — false is masked!\n",[33,1207,1208],{"class":35,"line":97},[33,1209,1210],{"class":39},"# pipefail fixes this: set -o pipefail  → false | true exits 1.\n",[33,1212,1213],{"class":35,"line":103},[33,1214,107],{"emptyLinePlaceholder":106},[33,1216,1217],{"class":35,"line":110},[33,1218,1219],{"class":39},"# ── Functions: a function returning non-zero triggers -e ONLY if called as a simple command ──\n",[33,1221,1222,1225,1227,1230,1232],{"class":35,"line":116},[33,1223,1224],{"class":130},"maybe_fail",[33,1226,908],{"class":159},[33,1228,1229],{"class":200},"return",[33,1231,231],{"class":119},[33,1233,234],{"class":159},[33,1235,1236,1238],{"class":35,"line":148},[33,1237,1224],{"class":130},[33,1239,1240],{"class":39},"                       # ✗ script exits (simple command)\n",[33,1242,1243,1245,1247,1249],{"class":35,"line":169},[33,1244,1224],{"class":130},[33,1246,318],{"class":200},[33,1248,321],{"class":119},[33,1250,1251],{"class":39},"               # OK — tested by ||\n",[33,1253,1254,1256,1259,1261,1263,1265,1268,1270,1272],{"class":35,"line":188},[33,1255,1142],{"class":200},[33,1257,1258],{"class":130}," maybe_fail",[33,1260,225],{"class":159},[33,1262,1150],{"class":200},[33,1264,1153],{"class":119},[33,1266,1267],{"class":123}," \"ok\"",[33,1269,225],{"class":159},[33,1271,1160],{"class":200},[33,1273,1274],{"class":39}," # OK — tested by if\n",[33,1276,1277],{"class":35,"line":420},[33,1278,107],{"emptyLinePlaceholder":106},[33,1280,1281],{"class":35,"line":459},[33,1282,1283],{"class":39},"# ── Subshell vs command substitution ──\n",[33,1285,1286,1289,1291,1294],{"class":35,"line":475},[33,1287,1288],{"class":159},"(",[33,1290,1127],{"class":119},[33,1292,1293],{"class":159},")                           ",[33,1295,1296],{"class":39},"# ✗ subshell failure propagates with -e\n",[33,1298,1299,1302,1304,1306,1308,1311],{"class":35,"line":480},[33,1300,1301],{"class":159},"x",[33,1303,368],{"class":200},[33,1305,402],{"class":159},[33,1307,1127],{"class":119},[33,1309,1310],{"class":159},")                       ",[33,1312,1313],{"class":39},"# ✗ command substitution failure propagates with -e (Bash 4.4+)\n",[33,1315,1316,1318,1320,1322,1324,1327,1329,1331],{"class":35,"line":486},[33,1317,1301],{"class":159},[33,1319,368],{"class":200},[33,1321,402],{"class":159},[33,1323,1127],{"class":119},[33,1325,1326],{"class":159},") ",[33,1328,210],{"class":200},[33,1330,321],{"class":119},[33,1332,1333],{"class":39},"               # OK — tested\n",[33,1335,1336],{"class":35,"line":495},[33,1337,107],{"emptyLinePlaceholder":106},[33,1339,1340],{"class":35,"line":507},[33,1341,1342],{"class":39},"# ── `grep` returns 1 when no match — silently kills the script ──\n",[33,1344,1345,1348,1351,1354],{"class":35,"line":574},[33,1346,1347],{"class":130},"grep",[33,1349,1350],{"class":123}," \"pattern\"",[33,1352,1353],{"class":123}," file.txt",[33,1355,1356],{"class":39},"          # ✗ if no match → exit 1 → script dies\n",[33,1358,1359,1361,1363,1365,1367,1369],{"class":35,"line":580},[33,1360,1347],{"class":130},[33,1362,1350],{"class":123},[33,1364,1353],{"class":123},[33,1366,318],{"class":200},[33,1368,321],{"class":119},[33,1370,1371],{"class":39},"  # OK — explicit \"no match is fine\"\n",[33,1373,1374,1376,1379,1381,1383,1385,1387,1390],{"class":35,"line":595},[33,1375,1347],{"class":130},[33,1377,1378],{"class":119}," -q",[33,1380,1350],{"class":123},[33,1382,1353],{"class":123},[33,1384,447],{"class":159},[33,1386,216],{"class":119},[33,1388,1389],{"class":123}," \"found\"",[33,1391,1392],{"class":39},"  # OK — tested by &&\n",[15,1394,1396,1399],{"id":1395},"set-u-and-the-default-expansion-trap",[30,1397,1398],{},"set -u"," and the Default Expansion Trap",[20,1401,1402],{"language":22},[24,1403,1405],{"className":26,"code":1404,"language":22,"meta":28,"style":28},"set -u\n\necho \"$UNDEFINED_VAR\"             # ✗ bash: UNDEFINED_VAR: unbound variable — fatal\necho \"${UNDEFINED_VAR:-}\"         # OK — :- provides empty default, doesn't error\necho \"${UNDEFINED_VAR:-default}\"  # OK — provides \"default\"\necho \"${UNDEFINED_VAR-default}\"   # OK — but ONLY if unset; empty string still errors\necho \"${UNDEFINED_VAR:?missing}\"  # ✗ prints \"missing\" to stderr, exits — even with :-\n\n# ── Arrays are tricky with set -u ──\ndeclare -a arr=()\necho \"${arr[@]}\"                  # OK — empty array, no error (Bash 4.4+)\n# Bash \u003C 4.4: this errors (\"unbound variable\") — use ${arr[@]:-}\n\ndeclare -A map=()\necho \"${map[key]}\"                # ✗ if key doesn't exist → unbound (some versions)\necho \"${map[key]:-}\"              # OK — safe access pattern\n",[30,1406,1407,1414,1418,1432,1448,1466,1480,1499,1503,1508,1523,1544,1549,1553,1566,1581],{"__ignoreMap":28},[33,1408,1409,1411],{"class":35,"line":36},[33,1410,263],{"class":119},[33,1412,1413],{"class":119}," -u\n",[33,1415,1416],{"class":35,"line":43},[33,1417,107],{"emptyLinePlaceholder":106},[33,1419,1420,1422,1424,1427,1429],{"class":35,"line":49},[33,1421,216],{"class":119},[33,1423,156],{"class":123},[33,1425,1426],{"class":159},"$UNDEFINED_VAR",[33,1428,163],{"class":123},[33,1430,1431],{"class":39},"             # ✗ bash: UNDEFINED_VAR: unbound variable — fatal\n",[33,1433,1434,1436,1438,1441,1443,1445],{"class":35,"line":55},[33,1435,216],{"class":119},[33,1437,408],{"class":123},[33,1439,1440],{"class":159},"UNDEFINED_VAR",[33,1442,534],{"class":200},[33,1444,652],{"class":123},[33,1446,1447],{"class":39},"         # OK — :- provides empty default, doesn't error\n",[33,1449,1450,1452,1454,1456,1458,1461,1463],{"class":35,"line":61},[33,1451,216],{"class":119},[33,1453,408],{"class":123},[33,1455,1440],{"class":159},[33,1457,534],{"class":200},[33,1459,1460],{"class":159},"default",[33,1462,652],{"class":123},[33,1464,1465],{"class":39},"  # OK — provides \"default\"\n",[33,1467,1468,1470,1472,1475,1477],{"class":35,"line":67},[33,1469,216],{"class":119},[33,1471,408],{"class":123},[33,1473,1474],{"class":159},"UNDEFINED_VAR-default",[33,1476,652],{"class":123},[33,1478,1479],{"class":39},"   # OK — but ONLY if unset; empty string still errors\n",[33,1481,1482,1484,1486,1488,1491,1494,1496],{"class":35,"line":73},[33,1483,216],{"class":119},[33,1485,408],{"class":123},[33,1487,1440],{"class":159},[33,1489,1490],{"class":200},":?",[33,1492,1493],{"class":159},"missing",[33,1495,652],{"class":123},[33,1497,1498],{"class":39},"  # ✗ prints \"missing\" to stderr, exits — even with :-\n",[33,1500,1501],{"class":35,"line":79},[33,1502,107],{"emptyLinePlaceholder":106},[33,1504,1505],{"class":35,"line":85},[33,1506,1507],{"class":39},"# ── Arrays are tricky with set -u ──\n",[33,1509,1510,1512,1515,1518,1520],{"class":35,"line":91},[33,1511,721],{"class":200},[33,1513,1514],{"class":119}," -a",[33,1516,1517],{"class":159}," arr",[33,1519,368],{"class":200},[33,1521,1522],{"class":159},"()\n",[33,1524,1525,1527,1529,1532,1535,1538,1541],{"class":35,"line":97},[33,1526,216],{"class":119},[33,1528,408],{"class":123},[33,1530,1531],{"class":159},"arr",[33,1533,1534],{"class":123},"[",[33,1536,1537],{"class":200},"@",[33,1539,1540],{"class":123},"]}\"",[33,1542,1543],{"class":39},"                  # OK — empty array, no error (Bash 4.4+)\n",[33,1545,1546],{"class":35,"line":103},[33,1547,1548],{"class":39},"# Bash \u003C 4.4: this errors (\"unbound variable\") — use ${arr[@]:-}\n",[33,1550,1551],{"class":35,"line":110},[33,1552,107],{"emptyLinePlaceholder":106},[33,1554,1555,1557,1559,1562,1564],{"class":35,"line":116},[33,1556,721],{"class":200},[33,1558,775],{"class":119},[33,1560,1561],{"class":159}," map",[33,1563,368],{"class":200},[33,1565,1522],{"class":159},[33,1567,1568,1570,1572,1575,1578],{"class":35,"line":148},[33,1569,216],{"class":119},[33,1571,408],{"class":123},[33,1573,1574],{"class":159},"map",[33,1576,1577],{"class":123},"[key]}\"",[33,1579,1580],{"class":39},"                # ✗ if key doesn't exist → unbound (some versions)\n",[33,1582,1583,1585,1587,1589,1592,1594,1596],{"class":35,"line":169},[33,1584,216],{"class":119},[33,1586,408],{"class":123},[33,1588,1574],{"class":159},[33,1590,1591],{"class":123},"[key]",[33,1593,534],{"class":200},[33,1595,652],{"class":123},[33,1597,1598],{"class":39},"              # OK — safe access pattern\n",[15,1600,1602,1603,1605,1606,1608],{"id":1601},"edge-cases-echo-vs-printf-portability","Edge Cases: ",[30,1604,216],{}," vs ",[30,1607,120],{}," Portability",[20,1610,1611],{"language":22},[24,1612,1614],{"className":26,"code":1613,"language":22,"meta":28,"style":28},"# ── echo is non-portable for escape sequences ──\necho -e \"line1\\nline2\"    # Bash: works. dash\u002FPOSIX sh: prints \"-e line1\\nline2\" literally.\necho -n \"no newline\"      # Bash: works. Some systems: -n printed literally.\n\n# ── printf is the portable choice ──\nprintf '%s\\n' \"line1\" \"line2\"     # always: two lines with newlines\nprintf 'exit: %d\\n' 42            # C-style format — consistent everywhere\nprintf '%-20s %5d\\n' \"label\" 42   # left-align string (20 cols), right-align int (5 cols)\nprintf '%x\\n' 255                 # ff (hex)\nprintf '%b\\n' 'tab\\there'         # interpret backslash escapes (like echo -e, but portable)\nprintf '%q\\n' \"string with spaces\" # shell-quoted: string\\ with\\ spaces (safe for re-eval)\n\n# ── echo behavior depends on xpg_echo and shopt ──\nshopt -s xpg_echo   # echo now interprets \\n, \\t (like echo -e always)\necho \"line1\\nline2\"  # prints two lines (xpg_echo on)\nshopt -u xpg_echo   # echo now prints \\n literally\n",[30,1615,1616,1621,1634,1647,1651,1656,1672,1685,1700,1713,1726,1739,1743,1748,1760,1769],{"__ignoreMap":28},[33,1617,1618],{"class":35,"line":36},[33,1619,1620],{"class":39},"# ── echo is non-portable for escape sequences ──\n",[33,1622,1623,1625,1628,1631],{"class":35,"line":43},[33,1624,216],{"class":119},[33,1626,1627],{"class":119}," -e",[33,1629,1630],{"class":123}," \"line1\\nline2\"",[33,1632,1633],{"class":39},"    # Bash: works. dash\u002FPOSIX sh: prints \"-e line1\\nline2\" literally.\n",[33,1635,1636,1638,1641,1644],{"class":35,"line":49},[33,1637,216],{"class":119},[33,1639,1640],{"class":119}," -n",[33,1642,1643],{"class":123}," \"no newline\"",[33,1645,1646],{"class":39},"      # Bash: works. Some systems: -n printed literally.\n",[33,1648,1649],{"class":35,"line":55},[33,1650,107],{"emptyLinePlaceholder":106},[33,1652,1653],{"class":35,"line":61},[33,1654,1655],{"class":39},"# ── printf is the portable choice ──\n",[33,1657,1658,1660,1663,1666,1669],{"class":35,"line":67},[33,1659,120],{"class":119},[33,1661,1662],{"class":123}," '%s\\n'",[33,1664,1665],{"class":123}," \"line1\"",[33,1667,1668],{"class":123}," \"line2\"",[33,1670,1671],{"class":39},"     # always: two lines with newlines\n",[33,1673,1674,1676,1679,1682],{"class":35,"line":73},[33,1675,120],{"class":119},[33,1677,1678],{"class":123}," 'exit: %d\\n'",[33,1680,1681],{"class":119}," 42",[33,1683,1684],{"class":39},"            # C-style format — consistent everywhere\n",[33,1686,1687,1689,1692,1695,1697],{"class":35,"line":79},[33,1688,120],{"class":119},[33,1690,1691],{"class":123}," '%-20s %5d\\n'",[33,1693,1694],{"class":123}," \"label\"",[33,1696,1681],{"class":119},[33,1698,1699],{"class":39},"   # left-align string (20 cols), right-align int (5 cols)\n",[33,1701,1702,1704,1707,1710],{"class":35,"line":85},[33,1703,120],{"class":119},[33,1705,1706],{"class":123}," '%x\\n'",[33,1708,1709],{"class":119}," 255",[33,1711,1712],{"class":39},"                 # ff (hex)\n",[33,1714,1715,1717,1720,1723],{"class":35,"line":91},[33,1716,120],{"class":119},[33,1718,1719],{"class":123}," '%b\\n'",[33,1721,1722],{"class":123}," 'tab\\there'",[33,1724,1725],{"class":39},"         # interpret backslash escapes (like echo -e, but portable)\n",[33,1727,1728,1730,1733,1736],{"class":35,"line":97},[33,1729,120],{"class":119},[33,1731,1732],{"class":123}," '%q\\n'",[33,1734,1735],{"class":123}," \"string with spaces\"",[33,1737,1738],{"class":39}," # shell-quoted: string\\ with\\ spaces (safe for re-eval)\n",[33,1740,1741],{"class":35,"line":103},[33,1742,107],{"emptyLinePlaceholder":106},[33,1744,1745],{"class":35,"line":110},[33,1746,1747],{"class":39},"# ── echo behavior depends on xpg_echo and shopt ──\n",[33,1749,1750,1752,1754,1757],{"class":35,"line":116},[33,1751,303],{"class":130},[33,1753,306],{"class":119},[33,1755,1756],{"class":123}," xpg_echo",[33,1758,1759],{"class":39},"   # echo now interprets \\n, \\t (like echo -e always)\n",[33,1761,1762,1764,1766],{"class":35,"line":148},[33,1763,216],{"class":119},[33,1765,1630],{"class":123},[33,1767,1768],{"class":39},"  # prints two lines (xpg_echo on)\n",[33,1770,1771,1773,1776,1778],{"class":35,"line":169},[33,1772,303],{"class":130},[33,1774,1775],{"class":119}," -u",[33,1777,1756],{"class":123},[33,1779,1780],{"class":39},"   # echo now prints \\n literally\n",[15,1782,1784],{"id":1783},"edge-cases-crlf-line-endings","Edge Cases: CRLF Line Endings",[20,1786,1787],{"language":22},[24,1788,1790],{"className":26,"code":1789,"language":22,"meta":28,"style":28},"# Symptom: bash: .\u002Fscript.sh: \u002Fusr\u002Fbin\u002Fenv: bad interpreter: No such file or directory\n# Cause: shebang is \"#!\u002Fusr\u002Fbin\u002Fenv bash\\r\" — \\r is part of the interpreter name.\n\n# Detect:\nfile script.sh  # \"ASCII text, with CRLF line terminators\"\n\n# Fix:\nsed -i 's\u002F\\r$\u002F\u002F' script.sh\n# Or: dos2unix script.sh\n# Or in Vim: :set fileformat=unix\n\n# Prevent in Git:\n# .gitattributes:\n# *.sh text eol=lf\n",[30,1791,1792,1797,1802,1806,1811,1822,1826,1831,1844,1849,1854,1858,1863,1868],{"__ignoreMap":28},[33,1793,1794],{"class":35,"line":36},[33,1795,1796],{"class":39},"# Symptom: bash: .\u002Fscript.sh: \u002Fusr\u002Fbin\u002Fenv: bad interpreter: No such file or directory\n",[33,1798,1799],{"class":35,"line":43},[33,1800,1801],{"class":39},"# Cause: shebang is \"#!\u002Fusr\u002Fbin\u002Fenv bash\\r\" — \\r is part of the interpreter name.\n",[33,1803,1804],{"class":35,"line":49},[33,1805,107],{"emptyLinePlaceholder":106},[33,1807,1808],{"class":35,"line":55},[33,1809,1810],{"class":39},"# Detect:\n",[33,1812,1813,1816,1819],{"class":35,"line":61},[33,1814,1815],{"class":130},"file",[33,1817,1818],{"class":123}," script.sh",[33,1820,1821],{"class":39},"  # \"ASCII text, with CRLF line terminators\"\n",[33,1823,1824],{"class":35,"line":67},[33,1825,107],{"emptyLinePlaceholder":106},[33,1827,1828],{"class":35,"line":73},[33,1829,1830],{"class":39},"# Fix:\n",[33,1832,1833,1836,1838,1841],{"class":35,"line":79},[33,1834,1835],{"class":130},"sed",[33,1837,724],{"class":119},[33,1839,1840],{"class":123}," 's\u002F\\r$\u002F\u002F'",[33,1842,1843],{"class":123}," script.sh\n",[33,1845,1846],{"class":35,"line":85},[33,1847,1848],{"class":39},"# Or: dos2unix script.sh\n",[33,1850,1851],{"class":35,"line":91},[33,1852,1853],{"class":39},"# Or in Vim: :set fileformat=unix\n",[33,1855,1856],{"class":35,"line":97},[33,1857,107],{"emptyLinePlaceholder":106},[33,1859,1860],{"class":35,"line":103},[33,1861,1862],{"class":39},"# Prevent in Git:\n",[33,1864,1865],{"class":35,"line":110},[33,1866,1867],{"class":39},"# .gitattributes:\n",[33,1869,1870],{"class":35,"line":116},[33,1871,1872],{"class":39},"# *.sh text eol=lf\n",[15,1874,1876],{"id":1875},"tips-tricks","💡 Tips & Tricks",[20,1878,1879],{"language":22},[24,1880,1882],{"className":26,"code":1881,"language":22,"meta":28,"style":28},"# ── Feature-gate by Bash version ──\nif (( BASH_VERSINFO[0] >= 4 )); then\n    declare -A config       # associative arrays (Bash 4+)\nelse\n    # Fallback: parallel indexed arrays or a config file\n    :\nfi\n\n# ── Gate by feature, not version (more robust) ──\nif ! declare -A _test_map 2>\u002Fdev\u002Fnull; then\n    die \"Associative arrays not available — need Bash 4+.\"\nfi\n\n# ── Detect if script is being sourced ──\n# When sourced, BASH_SOURCE[0] != BASH_SOURCE[1] (caller). When executed, they match.\n(return 0 2>\u002Fdev\u002Fnull) && SOURCED=1 || SOURCED=0\nif (( SOURCED )); then\n    # Don't run main() when sourced — act as a library.\n    :\nelse\n    main \"$@\"\nfi\n\n# ── Atomic script self-exec: re-exec with bash if run by sh ──\nif [ -z \"${BASH_VERSION:-}\" ]; then\n    exec bash \"$0\" \"$@\"  # re-exec under bash if invoked by sh\u002Fdash\nfi\n",[30,1883,1884,1889,1910,1923,1928,1933,1938,1943,1947,1952,1975,1983,1987,1991,1996,2001,2031,2040,2045,2049,2053,2064,2068,2072,2077,2101,2125],{"__ignoreMap":28},[33,1885,1886],{"class":35,"line":36},[33,1887,1888],{"class":39},"# ── Feature-gate by Bash version ──\n",[33,1890,1891,1893,1896,1898,1900,1902,1904,1907],{"class":35,"line":43},[33,1892,1142],{"class":200},[33,1894,1895],{"class":159}," (( BASH_VERSINFO[",[33,1897,194],{"class":119},[33,1899,197],{"class":159},[33,1901,201],{"class":200},[33,1903,204],{"class":119},[33,1905,1906],{"class":159}," )); ",[33,1908,1909],{"class":200},"then\n",[33,1911,1912,1915,1917,1920],{"class":35,"line":49},[33,1913,1914],{"class":200},"    declare",[33,1916,775],{"class":119},[33,1918,1919],{"class":159}," config       ",[33,1921,1922],{"class":39},"# associative arrays (Bash 4+)\n",[33,1924,1925],{"class":35,"line":55},[33,1926,1927],{"class":200},"else\n",[33,1929,1930],{"class":35,"line":61},[33,1931,1932],{"class":39},"    # Fallback: parallel indexed arrays or a config file\n",[33,1934,1935],{"class":35,"line":67},[33,1936,1937],{"class":119},"    :\n",[33,1939,1940],{"class":35,"line":73},[33,1941,1942],{"class":200},"fi\n",[33,1944,1945],{"class":35,"line":79},[33,1946,107],{"emptyLinePlaceholder":106},[33,1948,1949],{"class":35,"line":85},[33,1950,1951],{"class":39},"# ── Gate by feature, not version (more robust) ──\n",[33,1953,1954,1956,1959,1962,1964,1967,1970,1973],{"class":35,"line":91},[33,1955,1142],{"class":200},[33,1957,1958],{"class":200}," !",[33,1960,1961],{"class":200}," declare",[33,1963,775],{"class":119},[33,1965,1966],{"class":159}," _test_map 2",[33,1968,1969],{"class":200},">",[33,1971,1972],{"class":159},"\u002Fdev\u002Fnull; ",[33,1974,1909],{"class":200},[33,1976,1977,1980],{"class":35,"line":97},[33,1978,1979],{"class":130},"    die",[33,1981,1982],{"class":123}," \"Associative arrays not available — need Bash 4+.\"\n",[33,1984,1985],{"class":35,"line":103},[33,1986,1942],{"class":200},[33,1988,1989],{"class":35,"line":110},[33,1990,107],{"emptyLinePlaceholder":106},[33,1992,1993],{"class":35,"line":116},[33,1994,1995],{"class":39},"# ── Detect if script is being sourced ──\n",[33,1997,1998],{"class":35,"line":148},[33,1999,2000],{"class":39},"# When sourced, BASH_SOURCE[0] != BASH_SOURCE[1] (caller). When executed, they match.\n",[33,2002,2003,2005,2007,2010,2012,2014,2017,2019,2021,2023,2026,2028],{"class":35,"line":169},[33,2004,1288],{"class":159},[33,2006,1229],{"class":200},[33,2008,2009],{"class":119}," 0",[33,2011,312],{"class":200},[33,2013,315],{"class":123},[33,2015,2016],{"class":159},") && SOURCED",[33,2018,368],{"class":200},[33,2020,802],{"class":123},[33,2022,318],{"class":200},[33,2024,2025],{"class":159}," SOURCED",[33,2027,368],{"class":200},[33,2029,2030],{"class":123},"0\n",[33,2032,2033,2035,2038],{"class":35,"line":188},[33,2034,1142],{"class":200},[33,2036,2037],{"class":159}," (( SOURCED )); ",[33,2039,1909],{"class":200},[33,2041,2042],{"class":35,"line":420},[33,2043,2044],{"class":39},"    # Don't run main() when sourced — act as a library.\n",[33,2046,2047],{"class":35,"line":459},[33,2048,1937],{"class":119},[33,2050,2051],{"class":35,"line":475},[33,2052,1927],{"class":200},[33,2054,2055,2058,2060,2062],{"class":35,"line":480},[33,2056,2057],{"class":130},"    main",[33,2059,156],{"class":123},[33,2061,1033],{"class":119},[33,2063,571],{"class":123},[33,2065,2066],{"class":35,"line":486},[33,2067,1942],{"class":200},[33,2069,2070],{"class":35,"line":495},[33,2071,107],{"emptyLinePlaceholder":106},[33,2073,2074],{"class":35,"line":507},[33,2075,2076],{"class":39},"# ── Atomic script self-exec: re-exec with bash if run by sh ──\n",[33,2078,2079,2081,2084,2087,2089,2092,2094,2096,2099],{"class":35,"line":574},[33,2080,1142],{"class":200},[33,2082,2083],{"class":159}," [ ",[33,2085,2086],{"class":200},"-z",[33,2088,408],{"class":123},[33,2090,2091],{"class":159},"BASH_VERSION",[33,2093,534],{"class":200},[33,2095,652],{"class":123},[33,2097,2098],{"class":159}," ]; ",[33,2100,1909],{"class":200},[33,2102,2103,2106,2109,2111,2114,2116,2118,2120,2122],{"class":35,"line":580},[33,2104,2105],{"class":119},"    exec",[33,2107,2108],{"class":123}," bash",[33,2110,156],{"class":123},[33,2112,2113],{"class":119},"$0",[33,2115,163],{"class":123},[33,2117,156],{"class":123},[33,2119,1033],{"class":119},[33,2121,163],{"class":123},[33,2123,2124],{"class":39},"  # re-exec under bash if invoked by sh\u002Fdash\n",[33,2126,2127],{"class":35,"line":595},[33,2128,1942],{"class":200},[15,2130,2132],{"id":2131},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[20,2134,2135],{"language":22},[24,2136,2138],{"className":26,"code":2137,"language":22,"meta":28,"style":28},"# ── Shebang max length is 128 bytes (BINPRM_BUF_SIZE - 2) ──\n# A path longer than 126 chars silently fails with \"No such file or directory\".\n\n# ── Shebang with spaces: kernel splits on first whitespace ──\n#!\u002Fusr\u002Fbin\u002Fenv bash  # \"bash\" is the single arg to env\n# But: #!\u002Fusr\u002Fbin\u002Fenv \u002Fopt\u002Fbash  → fails (env doesn't resolve absolute paths without PATH lookup)\n\n# ── `set -e` is disabled inside command substitution in Bash \u003C 4.4 ──\n# Bash 4.3: x=$(false); echo \"still here\"  → prints \"still here\" (no exit!)\n# Bash 4.4+: x=$(false); echo \"still here\" → exits (inherit_errexit helps too)\n\n# ── `set -e` in a subshell with `|` ──\nset -e\n(echo \"subshell\"; false) | cat   # subshell exits 1, but pipefail needed to catch it\n# Without pipefail: cat exits 0 → pipeline exits 0 → no failure detected.\n\n# ── `trap ERR` doesn't fire on `exit` ──\n# trap ERR fires on command failures. trap EXIT fires on any exit (including exit, set -e, signals).\n# For cleanup, use trap EXIT. For logging the failing command, use trap ERR.\n# Combine: trap 'err_handler' ERR; trap 'cleanup' EXIT\n\n# ── `BASH_SOURCE[0]` vs `$0` ──\n# $0: the name the script was invoked as (may be a symlink, or \"bash\" if sourced)\n# BASH_SOURCE[0]: the file path of the current script (works when sourced)\n# Use BASH_SOURCE[0] for SCRIPT_DIR — $0 is unreliable.\n",[30,2139,2140,2145,2150,2154,2159,2164,2169,2173,2178,2183,2188,2192,2197,2203,2227,2232,2236,2241,2246,2251,2256,2260,2265,2270,2275],{"__ignoreMap":28},[33,2141,2142],{"class":35,"line":36},[33,2143,2144],{"class":39},"# ── Shebang max length is 128 bytes (BINPRM_BUF_SIZE - 2) ──\n",[33,2146,2147],{"class":35,"line":43},[33,2148,2149],{"class":39},"# A path longer than 126 chars silently fails with \"No such file or directory\".\n",[33,2151,2152],{"class":35,"line":49},[33,2153,107],{"emptyLinePlaceholder":106},[33,2155,2156],{"class":35,"line":55},[33,2157,2158],{"class":39},"# ── Shebang with spaces: kernel splits on first whitespace ──\n",[33,2160,2161],{"class":35,"line":61},[33,2162,2163],{"class":39},"#!\u002Fusr\u002Fbin\u002Fenv bash  # \"bash\" is the single arg to env\n",[33,2165,2166],{"class":35,"line":67},[33,2167,2168],{"class":39},"# But: #!\u002Fusr\u002Fbin\u002Fenv \u002Fopt\u002Fbash  → fails (env doesn't resolve absolute paths without PATH lookup)\n",[33,2170,2171],{"class":35,"line":73},[33,2172,107],{"emptyLinePlaceholder":106},[33,2174,2175],{"class":35,"line":79},[33,2176,2177],{"class":39},"# ── `set -e` is disabled inside command substitution in Bash \u003C 4.4 ──\n",[33,2179,2180],{"class":35,"line":85},[33,2181,2182],{"class":39},"# Bash 4.3: x=$(false); echo \"still here\"  → prints \"still here\" (no exit!)\n",[33,2184,2185],{"class":35,"line":91},[33,2186,2187],{"class":39},"# Bash 4.4+: x=$(false); echo \"still here\" → exits (inherit_errexit helps too)\n",[33,2189,2190],{"class":35,"line":97},[33,2191,107],{"emptyLinePlaceholder":106},[33,2193,2194],{"class":35,"line":103},[33,2195,2196],{"class":39},"# ── `set -e` in a subshell with `|` ──\n",[33,2198,2199,2201],{"class":35,"line":110},[33,2200,263],{"class":119},[33,2202,1113],{"class":119},[33,2204,2205,2207,2209,2212,2214,2216,2218,2221,2224],{"class":35,"line":116},[33,2206,1288],{"class":159},[33,2208,216],{"class":119},[33,2210,2211],{"class":123}," \"subshell\"",[33,2213,225],{"class":159},[33,2215,1127],{"class":119},[33,2217,1326],{"class":159},[33,2219,2220],{"class":200},"|",[33,2222,2223],{"class":130}," cat",[33,2225,2226],{"class":39},"   # subshell exits 1, but pipefail needed to catch it\n",[33,2228,2229],{"class":35,"line":148},[33,2230,2231],{"class":39},"# Without pipefail: cat exits 0 → pipeline exits 0 → no failure detected.\n",[33,2233,2234],{"class":35,"line":169},[33,2235,107],{"emptyLinePlaceholder":106},[33,2237,2238],{"class":35,"line":188},[33,2239,2240],{"class":39},"# ── `trap ERR` doesn't fire on `exit` ──\n",[33,2242,2243],{"class":35,"line":420},[33,2244,2245],{"class":39},"# trap ERR fires on command failures. trap EXIT fires on any exit (including exit, set -e, signals).\n",[33,2247,2248],{"class":35,"line":459},[33,2249,2250],{"class":39},"# For cleanup, use trap EXIT. For logging the failing command, use trap ERR.\n",[33,2252,2253],{"class":35,"line":475},[33,2254,2255],{"class":39},"# Combine: trap 'err_handler' ERR; trap 'cleanup' EXIT\n",[33,2257,2258],{"class":35,"line":480},[33,2259,107],{"emptyLinePlaceholder":106},[33,2261,2262],{"class":35,"line":486},[33,2263,2264],{"class":39},"# ── `BASH_SOURCE[0]` vs `$0` ──\n",[33,2266,2267],{"class":35,"line":495},[33,2268,2269],{"class":39},"# $0: the name the script was invoked as (may be a symlink, or \"bash\" if sourced)\n",[33,2271,2272],{"class":35,"line":507},[33,2273,2274],{"class":39},"# BASH_SOURCE[0]: the file path of the current script (works when sourced)\n",[33,2276,2277],{"class":35,"line":574},[33,2278,2279],{"class":39},"# Use BASH_SOURCE[0] for SCRIPT_DIR — $0 is unreliable.\n",[15,2281,2283],{"id":2282},"quick-quiz","🧠 Quick Quiz",[240,2285,2286],{},"What exit code does this produce, and why?",[20,2288,2289],{"language":22},[24,2290,2292],{"className":26,"code":2291,"language":22,"meta":28,"style":28},"set -euo pipefail\nfalse | true\necho \"reached\"\n",[30,2293,2294,2302,2311],{"__ignoreMap":28},[33,2295,2296,2298,2300],{"class":35,"line":36},[33,2297,263],{"class":119},[33,2299,1090],{"class":119},[33,2301,269],{"class":123},[33,2303,2304,2306,2308],{"class":35,"line":43},[33,2305,1127],{"class":119},[33,2307,1200],{"class":200},[33,2309,2310],{"class":119}," true\n",[33,2312,2313,2315],{"class":35,"line":49},[33,2314,216],{"class":119},[33,2316,2317],{"class":123}," \"reached\"\n",[2319,2320,2321,2325,2353],"details",{},[2322,2323,2324],"summary",{},"Answer",[2326,2327,2328,2341],"ul",{},[2329,2330,2331,2333,2334,2337,2338,2340],"li",{},[30,2332,1098],{}," alone: pipeline exits with ",[30,2335,2336],{},"true","'s status (0). Script continues, prints \"reached\". ",[30,2339,1127],{}," is silently masked.",[2329,2342,2343,2346,2347,2349,2350,2352],{},[30,2344,2345],{},"set -o pipefail",": pipeline exits with ",[30,2348,1127],{},"'s status (1). ",[30,2351,1098],{}," catches it, script dies before \"reached\".",[240,2354,2355,2359,2360,2362,2363,2366,2367,2370],{},[2356,2357,2358],"strong",{},"The lesson",": ",[30,2361,1098],{}," without ",[30,2364,2365],{},"pipefail"," is a false sense of safety — the first command in a pipeline can fail silently. Always use the full ",[30,2368,2369],{},"set -euo pipefail",".",[2372,2373,2374],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}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 .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 .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}",{"title":28,"searchDepth":43,"depth":43,"links":2376},[2377,2378,2379,2380,2382,2384,2386,2387,2388,2389],{"id":17,"depth":43,"text":18},{"id":237,"depth":43,"text":238},{"id":1038,"depth":43,"text":1039},{"id":1095,"depth":43,"text":2381},"set -e Doesn't Catch Everything",{"id":1395,"depth":43,"text":2383},"set -u and the Default Expansion Trap",{"id":1601,"depth":43,"text":2385},"Edge Cases: echo vs printf Portability",{"id":1783,"depth":43,"text":1784},{"id":1875,"depth":43,"text":1876},{"id":2131,"depth":43,"text":2132},{"id":2282,"depth":43,"text":2283},"Deep-dive into Bash interpreter resolution, shebang mechanics, strict-mode internals (set -euo pipefail), and production-grade script scaffolding. Code-first reference for mid-to-senior engineers.","md",{},"\u002Fbash\u002F01-introduction-and-setup",{"title":5,"description":2390},"bash\u002F01-introduction-and-setup","V-BkSQqJVoiubGFz8EcDl7DyQdblHUi6T2eJoz5oLGI",1789924644815]