[{"data":1,"prerenderedAt":644},["ShallowReactive",2],{"mdc-mw0ge6-key":3,"mdc--sacil7-key":29,"mdc-o8tioh-key":77,"mdc--ky550p-key":285,"mdc--nm4ylq-key":297,"mdc-ynth19-key":337,"mdc--h5np36-key":380,"mdc-jd1fp9-key":390,"mdc-xbfhaw-key":447,"mdc--hll9s7-key":459},{"data":4,"body":5},{},{"type":6,"children":7},"root",[8,16],{"type":9,"tag":10,"props":11,"children":12},"element","p",{},[13],{"type":14,"value":15},"text","Between pressing enter and a program actually starting, the shell rewrites your line. It expands wildcards into filenames, substitutes variables, splits the result into words, and only then runs something.",{"type":9,"tag":10,"props":17,"children":18},{},[19,21,27],{"type":14,"value":20},"Quoting is how you switch parts of that machinery off. Understanding ",{"type":9,"tag":22,"props":23,"children":24},"em",{},[25],{"type":14,"value":26},"which",{"type":14,"value":28}," parts each kind of quote disables is the difference between guessing and knowing.",{"data":30,"body":31},{},{"type":6,"children":32},[33,40],{"type":9,"tag":34,"props":35,"children":37},"h2",{"id":36},"wildcards-are-expanded-by-the-shell-not-by-the-command",[38],{"type":14,"value":39},"Wildcards are expanded by the shell, not by the command",{"type":9,"tag":10,"props":41,"children":42},{},[43,45,52,54,60,62,68,70,75],{"type":14,"value":44},"Type ",{"type":9,"tag":46,"props":47,"children":49},"code",{"className":48},[],[50],{"type":14,"value":51},"ls *.txt",{"type":14,"value":53}," and ",{"type":9,"tag":46,"props":55,"children":57},{"className":56},[],[58],{"type":14,"value":59},"ls",{"type":14,"value":61}," never sees a ",{"type":9,"tag":46,"props":63,"children":65},{"className":64},[],[66],{"type":14,"value":67},"*",{"type":14,"value":69},". The shell looks in the directory first, finds the matching names, and hands ",{"type":9,"tag":46,"props":71,"children":73},{"className":72},[],[74],{"type":14,"value":59},{"type":14,"value":76}," the finished list.",{"data":78,"body":79},{},{"type":6,"children":80},[81,86,114,141,146,265],{"type":9,"tag":82,"props":83,"children":85},"terminal-teaser",{":lines":84},"[{\"cmd\":\"ls\",\"out\":\"notes.txt  report.txt  data.csv\"},{\"cmd\":\"ls *.txt\",\"out\":\"notes.txt  report.txt\"},{\"cmd\":\"echo *.txt\",\"out\":\"notes.txt report.txt\"},{\"cmd\":\"echo *.zip\",\"out\":\"*.zip\"}]",[],{"type":9,"tag":10,"props":87,"children":88},{},[89,91,97,99,105,107,112],{"type":14,"value":90},"That third line is the proof: ",{"type":9,"tag":46,"props":92,"children":94},{"className":93},[],[95],{"type":14,"value":96},"echo",{"type":14,"value":98}," has no idea about files, yet it printed filenames. The expansion — properly called ",{"type":9,"tag":100,"props":101,"children":102},"strong",{},[103],{"type":14,"value":104},"globbing",{"type":14,"value":106}," — happened before ",{"type":9,"tag":46,"props":108,"children":110},{"className":109},[],[111],{"type":14,"value":96},{"type":14,"value":113}," was started.",{"type":9,"tag":10,"props":115,"children":116},{},[117,119,125,127,132,134,139],{"type":14,"value":118},"The fourth line shows the rule when nothing matches: a POSIX shell leaves the pattern alone and passes the literal ",{"type":9,"tag":46,"props":120,"children":122},{"className":121},[],[123],{"type":14,"value":124},"*.zip",{"type":14,"value":126}," through. This surprises people, and it is the reason a loop over ",{"type":9,"tag":46,"props":128,"children":130},{"className":129},[],[131],{"type":14,"value":124},{"type":14,"value":133}," in an empty directory runs exactly once, with the filename ",{"type":9,"tag":46,"props":135,"children":137},{"className":136},[],[138],{"type":14,"value":124},{"type":14,"value":140},".",{"type":9,"tag":10,"props":142,"children":143},{},[144],{"type":14,"value":145},"The patterns themselves are small:",{"type":9,"tag":147,"props":148,"children":149},"table",{},[150,169],{"type":9,"tag":151,"props":152,"children":153},"thead",{},[154],{"type":9,"tag":155,"props":156,"children":157},"tr",{},[158,164],{"type":9,"tag":159,"props":160,"children":161},"th",{},[162],{"type":14,"value":163},"Pattern",{"type":9,"tag":159,"props":165,"children":166},{},[167],{"type":14,"value":168},"Matches",{"type":9,"tag":170,"props":171,"children":172},"tbody",{},[173,190,207,224,241],{"type":9,"tag":155,"props":174,"children":175},{},[176,185],{"type":9,"tag":177,"props":178,"children":179},"td",{},[180],{"type":9,"tag":46,"props":181,"children":183},{"className":182},[],[184],{"type":14,"value":67},{"type":9,"tag":177,"props":186,"children":187},{},[188],{"type":14,"value":189},"any run of characters, including none",{"type":9,"tag":155,"props":191,"children":192},{},[193,202],{"type":9,"tag":177,"props":194,"children":195},{},[196],{"type":9,"tag":46,"props":197,"children":199},{"className":198},[],[200],{"type":14,"value":201},"?",{"type":9,"tag":177,"props":203,"children":204},{},[205],{"type":14,"value":206},"exactly one character",{"type":9,"tag":155,"props":208,"children":209},{},[210,219],{"type":9,"tag":177,"props":211,"children":212},{},[213],{"type":9,"tag":46,"props":214,"children":216},{"className":215},[],[217],{"type":14,"value":218},"[abc]",{"type":9,"tag":177,"props":220,"children":221},{},[222],{"type":14,"value":223},"one character from the set",{"type":9,"tag":155,"props":225,"children":226},{},[227,236],{"type":9,"tag":177,"props":228,"children":229},{},[230],{"type":9,"tag":46,"props":231,"children":233},{"className":232},[],[234],{"type":14,"value":235},"[a-z]",{"type":9,"tag":177,"props":237,"children":238},{},[239],{"type":14,"value":240},"one character from the range",{"type":9,"tag":155,"props":242,"children":243},{},[244,253],{"type":9,"tag":177,"props":245,"children":246},{},[247],{"type":9,"tag":46,"props":248,"children":250},{"className":249},[],[251],{"type":14,"value":252},"[!abc]",{"type":9,"tag":177,"props":254,"children":255},{},[256,258,263],{"type":14,"value":257},"one character ",{"type":9,"tag":22,"props":259,"children":260},{},[261],{"type":14,"value":262},"not",{"type":14,"value":264}," in the set",{"type":9,"tag":10,"props":266,"children":267},{},[268,270,275,277,283],{"type":14,"value":269},"None of them match a leading ",{"type":9,"tag":46,"props":271,"children":273},{"className":272},[],[274],{"type":14,"value":140},{"type":14,"value":276}," — hidden files are hidden from globbing too, which is why ",{"type":9,"tag":46,"props":278,"children":280},{"className":279},[],[281],{"type":14,"value":282},"rm *",{"type":14,"value":284}," does not delete your dotfiles.",{"data":286,"body":287},{},{"type":6,"children":288},[289],{"type":9,"tag":290,"props":291,"children":296},"quiz",{":answer":292,":options":293,"explanation":294,"question":295},"0","[\"`report1.txt` and `reportA.txt`, but not `report.txt` or `report10.txt`\",\"Any file starting with `report` and ending in `.txt`\",\"Only files whose name is literally `report?.txt`\"]","`?` means exactly one character — no more, no fewer. `report.txt` has zero characters there and `report10.txt` has two, so neither matches.","What does `ls report?.txt` match?",[],{"data":298,"body":299},{},{"type":6,"children":300},[301,307,320,332],{"type":9,"tag":34,"props":302,"children":304},{"id":303},"double-quotes-keep-it-together-keep-substitution",[305],{"type":14,"value":306},"Double quotes: keep it together, keep substitution",{"type":9,"tag":10,"props":308,"children":309},{},[310,312,318],{"type":14,"value":311},"Double quotes stop word splitting and stop globbing. They leave ",{"type":9,"tag":46,"props":313,"children":315},{"className":314},[],[316],{"type":14,"value":317},"$",{"type":14,"value":319}," and backticks working.",{"type":9,"tag":321,"props":322,"children":326},"pre",{"className":323,"code":325,"language":14},[324],"language-text","FILES=\"*.txt\"\necho $FILES      # notes.txt report.txt   — glob happened after substitution\necho \"$FILES\"    # *.txt                  — glob suppressed\n",[327],{"type":9,"tag":46,"props":328,"children":330},{"__ignoreMap":329},"",[331],{"type":14,"value":325},{"type":9,"tag":10,"props":333,"children":334},{},[335],{"type":14,"value":336},"This is the pair of quotes you want almost all of the time. It preserves the value exactly while still letting you interpolate.",{"data":338,"body":339},{},{"type":6,"children":340},[341,347,352,361],{"type":9,"tag":34,"props":342,"children":344},{"id":343},"single-quotes-literally-everything",[345],{"type":14,"value":346},"Single quotes: literally everything",{"type":9,"tag":10,"props":348,"children":349},{},[350],{"type":14,"value":351},"Single quotes disable everything. No variables, no globs, no backticks, no escapes. The only character single quotes cannot contain is another single quote.",{"type":9,"tag":321,"props":353,"children":356},{"className":354,"code":355,"language":14},[324],"echo \"Cost: $5\"     # Cost:      — $5 looks like a variable, and it's empty\necho 'Cost: $5'     # Cost: $5\n",[357],{"type":9,"tag":46,"props":358,"children":359},{"__ignoreMap":329},[360],{"type":14,"value":355},{"type":9,"tag":10,"props":362,"children":363},{},[364,366,371,372,378],{"type":14,"value":365},"Use them when you mean the characters and nothing else: awk programs, sed expressions, regular expressions full of ",{"type":9,"tag":46,"props":367,"children":369},{"className":368},[],[370],{"type":14,"value":317},{"type":14,"value":53},{"type":9,"tag":46,"props":373,"children":375},{"className":374},[],[376],{"type":14,"value":377},"\\",{"type":14,"value":379},", passwords, JSON.",{"data":381,"body":382},{},{"type":6,"children":383},[384],{"type":9,"tag":290,"props":385,"children":389},{":answer":292,":options":386,"explanation":387,"question":388},"[\"In double quotes the shell would try to expand `$'` before grep ever saw the pattern\",\"Double quotes cannot contain a `.` character\",\"grep only accepts single-quoted patterns\"]","The pattern is for grep to interpret, not the shell. Single quotes hand it through untouched. It's the same reason awk one-liners are always in single quotes — they're full of `$1`, `$2`, and the shell would happily eat all of them.","You want to run `grep 'error.*$' app.log`. Why single quotes rather than double?",[],{"data":391,"body":392},{},{"type":6,"children":393},[394,400,405,414,419,428],{"type":9,"tag":34,"props":395,"children":397},{"id":396},"backslash-escape-one-character",[398],{"type":14,"value":399},"Backslash: escape one character",{"type":9,"tag":10,"props":401,"children":402},{},[403],{"type":14,"value":404},"A backslash quotes exactly the character after it.",{"type":9,"tag":321,"props":406,"children":409},{"className":407,"code":408,"language":14},[324],"echo I owe you \\$5      # I owe you $5\necho one\\ word          # one word — the space is escaped, not a separator\ntouch my\\ file.txt      # creates one file called \"my file.txt\"\n",[410],{"type":9,"tag":46,"props":411,"children":412},{"__ignoreMap":329},[413],{"type":14,"value":408},{"type":9,"tag":10,"props":415,"children":416},{},[417],{"type":14,"value":418},"At the end of a line, a backslash escapes the newline itself — which is how a long command is split across several lines:",{"type":9,"tag":321,"props":420,"children":423},{"className":421,"code":422,"language":14},[324],"tar -czf backup.tar.gz \\\n    --exclude=node_modules \\\n    --exclude=.git \\\n    ./site\n",[424],{"type":9,"tag":46,"props":425,"children":426},{"__ignoreMap":329},[427],{"type":14,"value":422},{"type":9,"tag":10,"props":429,"children":430},{},[431,433,438,440,445],{"type":14,"value":432},"Nothing may follow that backslash, not even a space. A trailing space after ",{"type":9,"tag":46,"props":434,"children":436},{"className":435},[],[437],{"type":14,"value":377},{"type":14,"value":439}," escapes the ",{"type":9,"tag":22,"props":441,"children":442},{},[443],{"type":14,"value":444},"space",{"type":14,"value":446},", the newline then ends the command, and you get a baffling error about a missing argument.",{"data":448,"body":449},{},{"type":6,"children":450},[451],{"type":9,"tag":452,"props":453,"children":458},"fill-blank",{":answer":454,"hint":455,"placeholder":456,"prompt":457},"[\"echo 'Total: $100 (*)'\",\"printf 'Total: $100 (*)\\\\n'\"]","One kind of quote turns off every form of expansion at once.","echo ...","Print the literal text `Total: $100 (*)` — every character exactly as written.",[],{"data":460,"body":461},{},{"type":6,"children":462},[463,631],{"type":9,"tag":464,"props":465,"children":467},"deep-dive",{"title":466},"The order things happen in",[468,473,589],{"type":9,"tag":10,"props":469,"children":470},{},[471],{"type":14,"value":472},"The shell processes a line in a fixed sequence, and most confusing behaviour is really a surprise about the ordering:",{"type":9,"tag":474,"props":475,"children":476},"ol",{},[477,488,514,547,584],{"type":9,"tag":478,"props":479,"children":480},"li",{},[481,486],{"type":9,"tag":100,"props":482,"children":483},{},[484],{"type":14,"value":485},"Quote removal is last",{"type":14,"value":487},", not first — quotes are noted, then acted on at the end.",{"type":9,"tag":478,"props":489,"children":490},{},[491,496,498,504,506,512],{"type":9,"tag":100,"props":492,"children":493},{},[494],{"type":14,"value":495},"Variable and command substitution",{"type":14,"value":497}," — ",{"type":9,"tag":46,"props":499,"children":501},{"className":500},[],[502],{"type":14,"value":503},"$VAR",{"type":14,"value":505},", ",{"type":9,"tag":46,"props":507,"children":509},{"className":508},[],[510],{"type":14,"value":511},"$(cmd)",{"type":14,"value":513},", and backticks are replaced by their values.",{"type":9,"tag":478,"props":515,"children":516},{},[517,522,524,529,531,537,539,545],{"type":9,"tag":100,"props":518,"children":519},{},[520],{"type":14,"value":521},"Word splitting",{"type":14,"value":523}," — the ",{"type":9,"tag":22,"props":525,"children":526},{},[527],{"type":14,"value":528},"result",{"type":14,"value":530}," of step 2 is split on whitespace. This is why ",{"type":9,"tag":46,"props":532,"children":534},{"className":533},[],[535],{"type":14,"value":536},"rm $FILE",{"type":14,"value":538}," breaks and ",{"type":9,"tag":46,"props":540,"children":542},{"className":541},[],[543],{"type":14,"value":544},"rm \"$FILE\"",{"type":14,"value":546}," doesn't: the quotes were already noted in step 1, so step 3 skips that word.",{"type":9,"tag":478,"props":548,"children":549},{},[550,555,556,561,562,567,568,574,576,582],{"type":9,"tag":100,"props":551,"children":552},{},[553],{"type":14,"value":554},"Globbing",{"type":14,"value":497},{"type":9,"tag":46,"props":557,"children":559},{"className":558},[],[560],{"type":14,"value":67},{"type":14,"value":505},{"type":9,"tag":46,"props":563,"children":565},{"className":564},[],[566],{"type":14,"value":201},{"type":14,"value":505},{"type":9,"tag":46,"props":569,"children":571},{"className":570},[],[572],{"type":14,"value":573},"[...]",{"type":14,"value":575}," are expanded against the filesystem. Again, on the result of substitution, which is why ",{"type":9,"tag":46,"props":577,"children":579},{"className":578},[],[580],{"type":14,"value":581},"FILES=\"*.txt\"; echo $FILES",{"type":14,"value":583}," lists files.",{"type":9,"tag":478,"props":585,"children":586},{},[587],{"type":14,"value":588},"The finished word list becomes the command and its arguments.",{"type":9,"tag":10,"props":590,"children":591},{},[592,594,599,601,606,608,614,616,621,623,629],{"type":14,"value":593},"Two consequences worth remembering. A variable's value is never globbed ",{"type":9,"tag":22,"props":595,"children":596},{},[597],{"type":14,"value":598},"before",{"type":14,"value":600}," substitution, only after — so a ",{"type":9,"tag":46,"props":602,"children":604},{"className":603},[],[605],{"type":14,"value":67},{"type":14,"value":607}," stored in a variable is live unless you quote the expansion. And the shell never re-runs this process on the result: a variable containing ",{"type":9,"tag":46,"props":609,"children":611},{"className":610},[],[612],{"type":14,"value":613},"$OTHER",{"type":14,"value":615}," expands to the literal characters ",{"type":9,"tag":46,"props":617,"children":619},{"className":618},[],[620],{"type":14,"value":613},{"type":14,"value":622},", not to the value of ",{"type":9,"tag":46,"props":624,"children":626},{"className":625},[],[627],{"type":14,"value":628},"OTHER",{"type":14,"value":630},". The shell expands once, then stops.",{"type":9,"tag":10,"props":632,"children":633},{},[634,636,642],{"type":14,"value":635},"Next up: exit status and ",{"type":9,"tag":46,"props":637,"children":639},{"className":638},[],[640],{"type":14,"value":641},"test",{"type":14,"value":643}," — how a script finds out whether the last thing it did actually worked.",1787908866327]