[{"data":1,"prerenderedAt":745},["ShallowReactive",2],{"mdc--djgnt2-key":3,"mdc--e74cwn-key":24,"mdc-a8hjyd-key":75,"mdc--xj8opf-key":126,"mdc--w53x5d-key":170,"mdc-82d156-key":182,"mdc-lr9z6-key":220,"mdc--2zdm7m-key":326,"mdc-lral9l-key":402,"mdc--xskhax-key":414,"mdc-edl5sw-key":633,"mdc-uo4gxi-key":643},{"data":4,"body":5},{},{"type":6,"children":7},"root",[8],{"type":9,"tag":10,"props":11,"children":12},"element","p",{},[13,16,22],{"type":14,"value":15},"text","The shell's real power is not its own syntax — it is that it can run any program on the system and use the result as a value. ",{"type":9,"tag":17,"props":18,"children":19},"strong",{},[20],{"type":14,"value":21},"Command substitution",{"type":14,"value":23}," is the mechanism, and it's one character of syntax for an enormous amount of leverage.",{"data":25,"body":26},{},{"type":6,"children":27},[28,42,54],{"type":9,"tag":29,"props":30,"children":32},"h2",{"id":31},"captures-output",[33,40],{"type":9,"tag":34,"props":35,"children":37},"code",{"className":36},[],[38],{"type":14,"value":39},"$( ... )",{"type":14,"value":41}," captures output",{"type":9,"tag":43,"props":44,"children":48},"pre",{"className":45,"code":47,"language":14},[46],"language-text","TODAY=$(date +%Y-%m-%d)\necho \"Backup for $TODAY\"\n",[49],{"type":9,"tag":34,"props":50,"children":52},{"__ignoreMap":51},"",[53],{"type":14,"value":47},{"type":9,"tag":10,"props":55,"children":56},{},[57,59,65,67,73],{"type":14,"value":58},"Everything between ",{"type":9,"tag":34,"props":60,"children":62},{"className":61},[],[63],{"type":14,"value":64},"$(",{"type":14,"value":66}," and ",{"type":9,"tag":34,"props":68,"children":70},{"className":69},[],[71],{"type":14,"value":72},")",{"type":14,"value":74}," is run in a subshell, and the whole expression is replaced by whatever that command printed to standard output. Trailing newlines are stripped, which is almost always what you want.",{"data":76,"body":77},{},{"type":6,"children":78},[79,84,104,113],{"type":9,"tag":80,"props":81,"children":83},"terminal-teaser",{":lines":82},"[{\"cmd\":\"echo \\\"There are $(ls | wc -l) files here\\\"\",\"out\":\"There are 12 files here\"},{\"cmd\":\"USER_COUNT=$(who | wc -l)\",\"out\":\"\"},{\"cmd\":\"echo \\\"$USER_COUNT logged in\\\"\",\"out\":\"3 logged in\"}]",[],{"type":9,"tag":10,"props":85,"children":86},{},[87,89,95,97,102],{"type":14,"value":88},"You will also see the older backtick form, ",{"type":9,"tag":34,"props":90,"children":92},{"className":91},[],[93],{"type":14,"value":94},"`date`",{"type":14,"value":96},". It does the same thing and is still valid, but ",{"type":9,"tag":34,"props":98,"children":100},{"className":99},[],[101],{"type":14,"value":39},{"type":14,"value":103}," nests without escaping madness and is far easier to read:",{"type":9,"tag":43,"props":105,"children":108},{"className":106,"code":107,"language":14},[46],"OWNER=$(stat -c %U \"$(dirname \"$FILE\")\")     # fine\nOWNER=`stat -c %U \\`dirname $FILE\\``          # the same thing, backticks\n",[109],{"type":9,"tag":34,"props":110,"children":111},{"__ignoreMap":51},[112],{"type":14,"value":107},{"type":9,"tag":10,"props":114,"children":115},{},[116,118,124],{"type":14,"value":117},"Prefer ",{"type":9,"tag":34,"props":119,"children":121},{"className":120},[],[122],{"type":14,"value":123},"$( )",{"type":14,"value":125},". The only reason to know backticks is that older scripts are full of them.",{"data":127,"body":128},{},{"type":6,"children":129},[130,136,148,157],{"type":9,"tag":29,"props":131,"children":133},{"id":132},"it-runs-in-a-subshell",[134],{"type":14,"value":135},"It runs in a subshell",{"type":9,"tag":10,"props":137,"children":138},{},[139,141,146],{"type":14,"value":140},"The command inside runs in a ",{"type":9,"tag":17,"props":142,"children":143},{},[144],{"type":14,"value":145},"child",{"type":14,"value":147}," of your shell. That has one consequence people get bitten by:",{"type":9,"tag":43,"props":149,"children":152},{"className":150,"code":151,"language":14},[46],"DIR=$(cd /tmp && pwd)\necho \"$DIR\"     # /tmp\npwd             # still wherever you were\n",[153],{"type":9,"tag":34,"props":154,"children":155},{"__ignoreMap":51},[156],{"type":14,"value":151},{"type":9,"tag":10,"props":158,"children":159},{},[160,162,168],{"type":14,"value":161},"The ",{"type":9,"tag":34,"props":163,"children":165},{"className":164},[],[166],{"type":14,"value":167},"cd",{"type":14,"value":169}," happened in the subshell and died with it. Same for any variable set inside — this is the same one-way rule as running a script.",{"data":171,"body":172},{},{"type":6,"children":173},[174],{"type":9,"tag":175,"props":176,"children":181},"quiz",{":answer":177,":options":178,"explanation":179,"question":180},"0","[\"The first captures the output into a variable; the second writes it into a file named COUNT\",\"They're equivalent — the second is just older syntax\",\"The second fails because COUNT isn't declared\"]","`>` is redirection: it creates a file. Command substitution is the only way to get a program's output into a shell variable. The second form silently leaves a file called COUNT in your directory.","Why does `COUNT=$(grep -c error app.log)` work, but `grep -c error app.log > COUNT` not do the same thing?",[],{"data":183,"body":184},{},{"type":6,"children":185},[186,192,197,206,211],{"type":9,"tag":29,"props":187,"children":189},{"id":188},"quote-the-substitution",[190],{"type":14,"value":191},"Quote the substitution",{"type":9,"tag":10,"props":193,"children":194},{},[195],{"type":14,"value":196},"The result is expanded exactly like a variable — which means it gets word-split and globbed unless quoted:",{"type":9,"tag":43,"props":198,"children":201},{"className":199,"code":200,"language":14},[46],"FILES=$(ls *.txt)\necho $FILES        # newlines became spaces\necho \"$FILES\"      # one filename per line, preserved\n",[202],{"type":9,"tag":34,"props":203,"children":204},{"__ignoreMap":51},[205],{"type":14,"value":200},{"type":9,"tag":10,"props":207,"children":208},{},[209],{"type":14,"value":210},"Assigning to a variable is safe. Using it is where the quotes matter:",{"type":9,"tag":43,"props":212,"children":215},{"className":213,"code":214,"language":14},[46],"rm \"$(cat to-delete.txt)\"       # correct if the file holds one name\n",[216],{"type":9,"tag":34,"props":217,"children":218},{"__ignoreMap":51},[219],{"type":14,"value":214},{"data":221,"body":222},{},{"type":6,"children":223},[224],{"type":9,"tag":225,"props":226,"children":228},"deep-dive",{"title":227},"`IFS`, and where the newlines go",[229,249,254,263,268,277,289,298],{"type":9,"tag":10,"props":230,"children":231},{},[232,234,240,242,247],{"type":14,"value":233},"When the shell splits a substitution into words, it splits on the characters in ",{"type":9,"tag":34,"props":235,"children":237},{"className":236},[],[238],{"type":14,"value":239},"IFS",{"type":14,"value":241}," — the ",{"type":9,"tag":17,"props":243,"children":244},{},[245],{"type":14,"value":246},"Internal Field Separator",{"type":14,"value":248},". By default that is space, tab, and newline.",{"type":9,"tag":10,"props":250,"children":251},{},[252],{"type":14,"value":253},"That is why this happens:",{"type":9,"tag":43,"props":255,"children":258},{"className":256,"code":257,"language":14},[46],"NAMES=$(grep -i steve /etc/passwd | cut -d: -f1)\necho \"Found: $NAMES\"\nFound: steve fred\n",[259],{"type":9,"tag":34,"props":260,"children":261},{"__ignoreMap":51},[262],{"type":14,"value":257},{"type":9,"tag":10,"props":264,"children":265},{},[266],{"type":14,"value":267},"The command printed two lines. The unquoted expansion split them on newline and rejoined them with a space. Quote it and the newlines survive:",{"type":9,"tag":43,"props":269,"children":272},{"className":270,"code":271,"language":14},[46],"echo \"$NAMES\"\nsteve\nfred\n",[273],{"type":9,"tag":34,"props":274,"children":275},{"__ignoreMap":51},[276],{"type":14,"value":271},{"type":9,"tag":10,"props":278,"children":279},{},[280,282,287],{"type":14,"value":281},"You can also change ",{"type":9,"tag":34,"props":283,"children":285},{"className":284},[],[286],{"type":14,"value":239},{"type":14,"value":288}," deliberately, which is the standard way to parse delimited data:",{"type":9,"tag":43,"props":290,"children":293},{"className":291,"code":292,"language":14},[46],"OLD_IFS=$IFS\nIFS=:\nwhile read -r NAME PASS UID GID REST\ndo\n  echo \"$NAME has uid $UID\"\ndone \u003C /etc/passwd\nIFS=$OLD_IFS\n",[294],{"type":9,"tag":34,"props":295,"children":296},{"__ignoreMap":51},[297],{"type":14,"value":292},{"type":9,"tag":10,"props":299,"children":300},{},[301,303,309,311,317,319,324],{"type":14,"value":302},"Setting ",{"type":9,"tag":34,"props":304,"children":306},{"className":305},[],[307],{"type":14,"value":308},"IFS=:",{"type":14,"value":310}," makes ",{"type":9,"tag":34,"props":312,"children":314},{"className":313},[],[315],{"type":14,"value":316},"read",{"type":14,"value":318}," split each line on colons instead of whitespace, and the extra fields all pile into the last variable named. Save and restore it — leaving ",{"type":9,"tag":34,"props":320,"children":322},{"className":321},[],[323],{"type":14,"value":239},{"type":14,"value":325}," modified will produce baffling behaviour further down the script.",{"data":327,"body":328},{},{"type":6,"children":329},[330,336,347,356,393],{"type":9,"tag":29,"props":331,"children":333},{"id":332},"arithmetic-while-were-here",[334],{"type":14,"value":335},"Arithmetic, while we're here",{"type":9,"tag":10,"props":337,"children":338},{},[339,345],{"type":9,"tag":34,"props":340,"children":342},{"className":341},[],[343],{"type":14,"value":344},"$(( ... ))",{"type":14,"value":346}," looks similar and does something different: integer arithmetic, entirely inside the shell.",{"type":9,"tag":43,"props":348,"children":351},{"className":349,"code":350,"language":14},[46],"COUNT=$((COUNT + 1))\nTOTAL=$((PRICE * QTY))\nPERCENT=$((DONE * 100 / TOTAL))\n",[352],{"type":9,"tag":34,"props":353,"children":354},{"__ignoreMap":51},[355],{"type":14,"value":350},{"type":9,"tag":10,"props":357,"children":358},{},[359,361,367,369,375,377,383,385,391],{"type":14,"value":360},"Variables inside don't need a ",{"type":9,"tag":34,"props":362,"children":364},{"className":363},[],[365],{"type":14,"value":366},"$",{"type":14,"value":368},". Division is integer division — ",{"type":9,"tag":34,"props":370,"children":372},{"className":371},[],[373],{"type":14,"value":374},"$((7 / 2))",{"type":14,"value":376}," is 3 — and there are no floating point numbers at all. If you need decimals you have to shell out to ",{"type":9,"tag":34,"props":378,"children":380},{"className":379},[],[381],{"type":14,"value":382},"awk",{"type":14,"value":384}," or ",{"type":9,"tag":34,"props":386,"children":388},{"className":387},[],[389],{"type":14,"value":390},"bc",{"type":14,"value":392},":",{"type":9,"tag":43,"props":394,"children":397},{"className":395,"code":396,"language":14},[46],"AVG=$(echo \"scale=2; $TOTAL / $COUNT\" | bc)\n",[398],{"type":9,"tag":34,"props":399,"children":400},{"__ignoreMap":51},[401],{"type":14,"value":396},{"data":403,"body":404},{},{"type":6,"children":405},[406],{"type":9,"tag":407,"props":408,"children":413},"fill-blank",{":answer":409,"hint":410,"placeholder":411,"prompt":412},"[\"HOST=$(hostname)\",\"HOST=\\\"$(hostname)\\\"\",\"HOST=`hostname`\"]","Assignment on the left, a captured command on the right.","HOST=...","Store the current hostname in a variable called `HOST`, using command substitution.",[],{"data":415,"body":416},{},{"type":6,"children":417},[418,424,429,606,611,620],{"type":9,"tag":29,"props":419,"children":421},{"id":420},"the-programs-you-will-actually-reach-for",[422],{"type":14,"value":423},"The programs you will actually reach for",{"type":9,"tag":10,"props":425,"children":426},{},[427],{"type":14,"value":428},"Command substitution is only as good as the commands available, and a handful of text utilities do most of the work in real scripts:",{"type":9,"tag":430,"props":431,"children":432},"table",{},[433,452],{"type":9,"tag":434,"props":435,"children":436},"thead",{},[437],{"type":9,"tag":438,"props":439,"children":440},"tr",{},[441,447],{"type":9,"tag":442,"props":443,"children":444},"th",{},[445],{"type":14,"value":446},"Tool",{"type":9,"tag":442,"props":448,"children":449},{},[450],{"type":14,"value":451},"What it's for",{"type":9,"tag":453,"props":454,"children":455},"tbody",{},[456,474,491,507,524,540,565,582],{"type":9,"tag":438,"props":457,"children":458},{},[459,469],{"type":9,"tag":460,"props":461,"children":462},"td",{},[463],{"type":9,"tag":34,"props":464,"children":466},{"className":465},[],[467],{"type":14,"value":468},"grep",{"type":9,"tag":460,"props":470,"children":471},{},[472],{"type":14,"value":473},"keep the lines that match",{"type":9,"tag":438,"props":475,"children":476},{},[477,486],{"type":9,"tag":460,"props":478,"children":479},{},[480],{"type":9,"tag":34,"props":481,"children":483},{"className":482},[],[484],{"type":14,"value":485},"cut",{"type":9,"tag":460,"props":487,"children":488},{},[489],{"type":14,"value":490},"pull out fields by delimiter or column",{"type":9,"tag":438,"props":492,"children":493},{},[494,502],{"type":9,"tag":460,"props":495,"children":496},{},[497],{"type":9,"tag":34,"props":498,"children":500},{"className":499},[],[501],{"type":14,"value":438},{"type":9,"tag":460,"props":503,"children":504},{},[505],{"type":14,"value":506},"translate or delete characters",{"type":9,"tag":438,"props":508,"children":509},{},[510,519],{"type":9,"tag":460,"props":511,"children":512},{},[513],{"type":9,"tag":34,"props":514,"children":516},{"className":515},[],[517],{"type":14,"value":518},"sed",{"type":9,"tag":460,"props":520,"children":521},{},[522],{"type":14,"value":523},"substitute and edit, line by line",{"type":9,"tag":438,"props":525,"children":526},{},[527,535],{"type":9,"tag":460,"props":528,"children":529},{},[530],{"type":9,"tag":34,"props":531,"children":533},{"className":532},[],[534],{"type":14,"value":382},{"type":9,"tag":460,"props":536,"children":537},{},[538],{"type":14,"value":539},"field-aware processing with real logic",{"type":9,"tag":438,"props":541,"children":542},{},[543,560],{"type":9,"tag":460,"props":544,"children":545},{},[546,552,554],{"type":9,"tag":34,"props":547,"children":549},{"className":548},[],[550],{"type":14,"value":551},"sort",{"type":14,"value":553}," / ",{"type":9,"tag":34,"props":555,"children":557},{"className":556},[],[558],{"type":14,"value":559},"uniq",{"type":9,"tag":460,"props":561,"children":562},{},[563],{"type":14,"value":564},"order and deduplicate",{"type":9,"tag":438,"props":566,"children":567},{},[568,577],{"type":9,"tag":460,"props":569,"children":570},{},[571],{"type":9,"tag":34,"props":572,"children":574},{"className":573},[],[575],{"type":14,"value":576},"wc",{"type":9,"tag":460,"props":578,"children":579},{},[580],{"type":14,"value":581},"count lines, words, bytes",{"type":9,"tag":438,"props":583,"children":584},{},[585,601],{"type":9,"tag":460,"props":586,"children":587},{},[588,594,595],{"type":9,"tag":34,"props":589,"children":591},{"className":590},[],[592],{"type":14,"value":593},"head",{"type":14,"value":553},{"type":9,"tag":34,"props":596,"children":598},{"className":597},[],[599],{"type":14,"value":600},"tail",{"type":9,"tag":460,"props":602,"children":603},{},[604],{"type":14,"value":605},"take from either end",{"type":9,"tag":10,"props":607,"children":608},{},[609],{"type":14,"value":610},"A single line combining four of them is entirely normal, and reads left to right:",{"type":9,"tag":43,"props":612,"children":615},{"className":613,"code":614,"language":14},[46],"# The ten IPs with the most requests in a log\nawk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10\n",[616],{"type":9,"tag":34,"props":617,"children":618},{"__ignoreMap":51},[619],{"type":14,"value":614},{"type":9,"tag":10,"props":621,"children":622},{},[623,625,631],{"type":14,"value":624},"Note the single quotes around the awk program: it is full of ",{"type":9,"tag":34,"props":626,"children":628},{"className":627},[],[629],{"type":14,"value":630},"$1",{"type":14,"value":632},", and double quotes would have let the shell eat it before awk saw it — exactly the case from the quoting lesson.",{"data":634,"body":635},{},{"type":6,"children":636},[637],{"type":9,"tag":175,"props":638,"children":642},{":answer":177,":options":639,"explanation":640,"question":641},"[\"`uniq` only collapses *adjacent* duplicate lines, so equal lines must be brought together first\",\"`uniq` requires sorted input to avoid an error\",\"It isn't needed; it's there for readability\"]","`uniq` is a streaming tool — it compares each line to the previous one and nothing else, which is what lets it work on input of any size. Sorting is what puts identical lines next to each other. The second `sort -rn` then ranks by the counts `uniq -c` added.","Why is `sort` needed before `uniq -c` in that pipeline?",[],{"data":644,"body":645},{},{"type":6,"children":646},[647,653,658,667,680,689,740],{"type":9,"tag":29,"props":648,"children":650},{"id":649},"dont-reach-for-a-program-you-dont-need",[651],{"type":14,"value":652},"Don't reach for a program you don't need",{"type":9,"tag":10,"props":654,"children":655},{},[656],{"type":14,"value":657},"The flip side: every external command is a process, and in a loop that adds up.",{"type":9,"tag":43,"props":659,"children":662},{"className":660,"code":661,"language":14},[46],"# 10,000 processes\nfor i in $(seq 1 10000); do TOTAL=$(expr $TOTAL + $i); done\n\n# zero processes\nfor i in $(seq 1 10000); do TOTAL=$((TOTAL + $i)); done\n",[663],{"type":9,"tag":34,"props":664,"children":665},{"__ignoreMap":51},[666],{"type":14,"value":661},{"type":9,"tag":10,"props":668,"children":669},{},[670,672,678],{"type":14,"value":671},"The same applies to the famous \"useless use of ",{"type":9,"tag":34,"props":673,"children":675},{"className":674},[],[676],{"type":14,"value":677},"cat",{"type":14,"value":679},"\":",{"type":9,"tag":43,"props":681,"children":684},{"className":682,"code":683,"language":14},[46],"cat file.txt | grep error      # two processes\ngrep error file.txt            # one\n",[685],{"type":9,"tag":34,"props":686,"children":687},{"__ignoreMap":51},[688],{"type":14,"value":683},{"type":9,"tag":10,"props":690,"children":691},{},[692,694,700,702,708,710,716,718,724,725,731,732,738],{"type":14,"value":693},"It is not worth being precious about — but knowing that ",{"type":9,"tag":34,"props":695,"children":697},{"className":696},[],[698],{"type":14,"value":699},"$(( ))",{"type":14,"value":701},", ",{"type":9,"tag":34,"props":703,"children":705},{"className":704},[],[706],{"type":14,"value":707},"case",{"type":14,"value":709},", and ",{"type":9,"tag":34,"props":711,"children":713},{"className":712},[],[714],{"type":14,"value":715},"test",{"type":14,"value":717}," are all builtins, while ",{"type":9,"tag":34,"props":719,"children":721},{"className":720},[],[722],{"type":14,"value":723},"expr",{"type":14,"value":701},{"type":9,"tag":34,"props":726,"children":728},{"className":727},[],[729],{"type":14,"value":730},"basename",{"type":14,"value":709},{"type":9,"tag":34,"props":733,"children":735},{"className":734},[],[736],{"type":14,"value":737},"seq",{"type":14,"value":739}," are not, will make a slow script obvious when you meet one.",{"type":9,"tag":10,"props":741,"children":742},{},[743],{"type":14,"value":744},"Next up: functions — naming a block of a script so the rest of it can stop repeating itself.",1787908866327]