[{"data":1,"prerenderedAt":657},["ShallowReactive",2],{"mdc--4cpayi-key":3,"mdc--ovq4pj-key":38,"mdc--g8e343-key":89,"mdc-ua2o9v-key":103,"mdc--uwkp84-key":173,"mdc-4zsx0t-key":277,"mdc-dj3f4w-key":289,"mdc--si2u1c-key":322,"mdc--wuanfr-key":334,"mdc-3lb5r5-key":372,"mdc--2uqsbm-key":481,"mdc--klyg0u-key":491,"mdc--radrfl-key":606},{"data":4,"body":5},{},{"type":6,"children":7},"root",[8],{"type":9,"tag":10,"props":11,"children":12},"element","p",{},[13,16,22,24,29,31,36],{"type":14,"value":15},"text","Every process starts with three files already open: ",{"type":9,"tag":17,"props":18,"children":19},"strong",{},[20],{"type":14,"value":21},"standard input",{"type":14,"value":23}," (0), ",{"type":9,"tag":17,"props":25,"children":26},{},[27],{"type":14,"value":28},"standard output",{"type":14,"value":30}," (1), and ",{"type":9,"tag":17,"props":32,"children":33},{},[34],{"type":14,"value":35},"standard error",{"type":14,"value":37}," (2). Redirection is how a script rewires them — and getting it right is the difference between a cron job that tells you what went wrong and one that fails in silence.",{"data":39,"body":40},{},{"type":6,"children":41},[42,49,84],{"type":9,"tag":43,"props":44,"children":46},"h2",{"id":45},"the-three-streams",[47],{"type":14,"value":48},"The three streams",{"type":9,"tag":50,"props":51,"children":52},"ul",{},[53,64,74],{"type":9,"tag":54,"props":55,"children":56},"li",{},[57,62],{"type":9,"tag":17,"props":58,"children":59},{},[60],{"type":14,"value":61},"stdin (0)",{"type":14,"value":63}," — where a program reads from. The keyboard, by default.",{"type":9,"tag":54,"props":65,"children":66},{},[67,72],{"type":9,"tag":17,"props":68,"children":69},{},[70],{"type":14,"value":71},"stdout (1)",{"type":14,"value":73}," — where its results go. Your terminal, by default.",{"type":9,"tag":54,"props":75,"children":76},{},[77,82],{"type":9,"tag":17,"props":78,"children":79},{},[80],{"type":14,"value":81},"stderr (2)",{"type":14,"value":83}," — where its complaints go. Also your terminal, by default.",{"type":9,"tag":10,"props":85,"children":86},{},[87],{"type":14,"value":88},"That last one is the point of the design. Output and errors go to the same place interactively, so you see both — but they are separate channels, so a pipeline can process the results while the errors still reach a human.",{"data":90,"body":91},{},{"type":6,"children":92},[93,98],{"type":9,"tag":94,"props":95,"children":97},"terminal-teaser",{":lines":96},"[{\"cmd\":\"ls /etc /nope\",\"out\":\"ls: cannot access '/nope': No such file or directory\\n/etc:\\nhostname  passwd  hosts\"},{\"cmd\":\"ls /etc /nope > out.txt\",\"out\":\"ls: cannot access '/nope': No such file or directory\"},{\"cmd\":\"ls /etc /nope 2> err.txt\",\"out\":\"/etc:\\nhostname  passwd  hosts\"}]",[],{"type":9,"tag":10,"props":99,"children":100},{},[101],{"type":14,"value":102},"The second command sent results to a file and left the error on screen. The third did the opposite. Nothing was lost either time — the streams simply went to different places.",{"data":104,"body":105},{},{"type":6,"children":106},[107,113,126,159,164],{"type":9,"tag":43,"props":108,"children":110},{"id":109},"writing-output-to-a-file",[111],{"type":14,"value":112},"Writing output to a file",{"type":9,"tag":114,"props":115,"children":119},"pre",{"className":116,"code":118,"language":14},[117],"language-text","echo \"hello\" > file.txt      # create or TRUNCATE, then write\necho \"world\" >> file.txt     # append\n",[120],{"type":9,"tag":121,"props":122,"children":124},"code",{"__ignoreMap":123},"",[125],{"type":14,"value":118},{"type":9,"tag":10,"props":127,"children":128},{},[129,135,137,143,145,150,152,157],{"type":9,"tag":121,"props":130,"children":132},{"className":131},[],[133],{"type":14,"value":134},">",{"type":14,"value":136}," destroys the existing contents before writing a single byte. ",{"type":9,"tag":121,"props":138,"children":140},{"className":139},[],[141],{"type":14,"value":142},">>",{"type":14,"value":144}," adds to the end. The number of build scripts that have clobbered a config file by writing ",{"type":9,"tag":121,"props":146,"children":148},{"className":147},[],[149],{"type":14,"value":134},{"type":14,"value":151}," where they meant ",{"type":9,"tag":121,"props":153,"children":155},{"className":154},[],[156],{"type":14,"value":142},{"type":14,"value":158}," is not small.",{"type":9,"tag":10,"props":160,"children":161},{},[162],{"type":14,"value":163},"Redirecting a specific stream means naming its number:",{"type":9,"tag":114,"props":165,"children":168},{"className":166,"code":167,"language":14},[117],"command 1> out.txt      # same as > out.txt\ncommand 2> err.txt      # errors only\ncommand 2>> err.txt     # append errors\n",[169],{"type":9,"tag":121,"props":170,"children":171},{"__ignoreMap":123},[172],{"type":14,"value":167},{"data":174,"body":175},{},{"type":6,"children":176},[177,189,194,203,222,231,267],{"type":9,"tag":43,"props":178,"children":180},{"id":179},"_21-and-why-the-order-matters",[181,187],{"type":9,"tag":121,"props":182,"children":184},{"className":183},[],[185],{"type":14,"value":186},"2>&1",{"type":14,"value":188},", and why the order matters",{"type":9,"tag":10,"props":190,"children":191},{},[192],{"type":14,"value":193},"To send errors to the same place as output:",{"type":9,"tag":114,"props":195,"children":198},{"className":196,"code":197,"language":14},[117],"command > log.txt 2>&1\n",[199],{"type":9,"tag":121,"props":200,"children":201},{"__ignoreMap":123},[202],{"type":14,"value":197},{"type":9,"tag":10,"props":204,"children":205},{},[206,208,213,215,220],{"type":14,"value":207},"Read ",{"type":9,"tag":121,"props":209,"children":211},{"className":210},[],[212],{"type":14,"value":186},{"type":14,"value":214}," as \"make file descriptor 2 point at whatever 1 currently points at\". Which is why this is ",{"type":9,"tag":17,"props":216,"children":217},{},[218],{"type":14,"value":219},"not",{"type":14,"value":221}," the same:",{"type":9,"tag":114,"props":223,"children":226},{"className":224,"code":225,"language":14},[117],"command 2>&1 > log.txt\n",[227],{"type":9,"tag":121,"props":228,"children":229},{"__ignoreMap":123},[230],{"type":14,"value":225},{"type":9,"tag":10,"props":232,"children":233},{},[234,236,242,244,250,252,258,260,265],{"type":14,"value":235},"Redirections are processed left to right. In the broken version, ",{"type":9,"tag":121,"props":237,"children":239},{"className":238},[],[240],{"type":14,"value":241},"2",{"type":14,"value":243}," is pointed at the terminal (where ",{"type":9,"tag":121,"props":245,"children":247},{"className":246},[],[248],{"type":14,"value":249},"1",{"type":14,"value":251}," still points at that moment), and only ",{"type":9,"tag":253,"props":254,"children":255},"em",{},[256],{"type":14,"value":257},"then",{"type":14,"value":259}," is ",{"type":9,"tag":121,"props":261,"children":263},{"className":262},[],[264],{"type":14,"value":249},{"type":14,"value":266}," moved to the file. The result is output in the file and errors still on screen — the opposite of the intent.",{"type":9,"tag":10,"props":268,"children":269},{},[270,272],{"type":14,"value":271},"The rule: ",{"type":9,"tag":17,"props":273,"children":274},{},[275],{"type":14,"value":276},"redirect stdout first, then duplicate onto it.",{"data":278,"body":279},{},{"type":6,"children":280},[281],{"type":9,"tag":282,"props":283,"children":288},"quiz",{":answer":284,":options":285,"explanation":286,"question":287},"0","[\"`2>&1` copied the terminal destination before `>` moved stdout to the file\",\"Cron discards stderr regardless of redirection\",\"`2>&1` must come first to work at all\"]","Redirections apply in order. At the moment `2>&1` runs, stdout still points at the original destination, so stderr is aimed there — and moving stdout afterwards does not drag stderr along. Write `> /var/log/backup.log 2>&1`.","A cron job runs `backup.sh 2>&1 > /var/log/backup.log` and the log is empty of errors even when it fails. Why?",[],{"data":290,"body":291},{},{"type":6,"children":292},[293,303,308,317],{"type":9,"tag":43,"props":294,"children":296},{"id":295},"devnull",[297],{"type":9,"tag":121,"props":298,"children":300},{"className":299},[],[301],{"type":14,"value":302},"/dev/null",{"type":9,"tag":10,"props":304,"children":305},{},[306],{"type":14,"value":307},"A special file that discards everything written to it and is instantly empty when read.",{"type":9,"tag":114,"props":309,"children":312},{"className":310,"code":311,"language":14},[117],"command > /dev/null           # discard normal output, keep errors visible\ncommand 2> /dev/null          # discard errors, keep output\ncommand > /dev/null 2>&1      # silence entirely\n",[313],{"type":9,"tag":121,"props":314,"children":315},{"__ignoreMap":123},[316],{"type":14,"value":311},{"type":9,"tag":10,"props":318,"children":319},{},[320],{"type":14,"value":321},"The first form is the one to reach for by default. Silencing a command completely, as in the third, is how a script ends up failing without telling anyone — worth doing deliberately, not out of habit.",{"data":323,"body":324},{},{"type":6,"children":325},[326],{"type":9,"tag":327,"props":328,"children":333},"fill-blank",{":answer":329,"hint":330,"placeholder":331,"prompt":332},"[\"apt-get update > /var/log/update.log 2>&1\",\"apt-get update >/var/log/update.log 2>&1\"]","Move stdout to the file first, then point stderr at the same place.","apt-get update ...","Run `apt-get update`, sending both its normal output and its errors to `/var/log/update.log`.",[],{"data":335,"body":336},{},{"type":6,"children":337},[338,344,353],{"type":9,"tag":43,"props":339,"children":341},{"id":340},"reading-input-from-a-file",[342],{"type":14,"value":343},"Reading input from a file",{"type":9,"tag":114,"props":345,"children":348},{"className":346,"code":347,"language":14},[117],"while read -r LINE; do echo \"$LINE\"; done \u003C hosts.txt\nsort \u003C unsorted.txt > sorted.txt\n",[349],{"type":9,"tag":121,"props":350,"children":351},{"__ignoreMap":123},[352],{"type":14,"value":347},{"type":9,"tag":10,"props":354,"children":355},{},[356,362,364,370],{"type":9,"tag":121,"props":357,"children":359},{"className":358},[],[360],{"type":14,"value":361},"\u003C",{"type":14,"value":363}," connects a file to standard input. It is why the loop from the loops lesson takes its data at ",{"type":9,"tag":121,"props":365,"children":367},{"className":366},[],[368],{"type":14,"value":369},"done \u003C hosts.txt",{"type":14,"value":371}," — the redirect applies to the whole compound command.",{"data":373,"body":374},{},{"type":6,"children":375},[376,382,394,403,431,451,460],{"type":9,"tag":43,"props":377,"children":379},{"id":378},"here-documents",[380],{"type":14,"value":381},"Here-documents",{"type":9,"tag":10,"props":383,"children":384},{},[385,387,392],{"type":14,"value":386},"A ",{"type":9,"tag":17,"props":388,"children":389},{},[390],{"type":14,"value":391},"here-document",{"type":14,"value":393}," feeds a literal block of text to a command's standard input, and it is the cleanest way to emit multi-line output from a script:",{"type":9,"tag":114,"props":395,"children":398},{"className":396,"code":397,"language":14},[117],"cat \u003C\u003CEOF\nUsage: $(basename \"$0\") [options] HOST\n\n  -v    verbose\n  -h    this message\nEOF\n",[399],{"type":9,"tag":121,"props":400,"children":401},{"__ignoreMap":123},[402],{"type":14,"value":397},{"type":9,"tag":10,"props":404,"children":405},{},[406,408,414,416,422,424,429],{"type":14,"value":407},"Everything between the ",{"type":9,"tag":121,"props":409,"children":411},{"className":410},[],[412],{"type":14,"value":413},"\u003C\u003CEOF",{"type":14,"value":415}," line and a line consisting of exactly ",{"type":9,"tag":121,"props":417,"children":419},{"className":418},[],[420],{"type":14,"value":421},"EOF",{"type":14,"value":423}," becomes the input. The word is arbitrary — ",{"type":9,"tag":121,"props":425,"children":427},{"className":426},[],[428],{"type":14,"value":421},{"type":14,"value":430}," is convention, not syntax.",{"type":9,"tag":10,"props":432,"children":433},{},[434,436,441,443,449],{"type":14,"value":435},"By default a here-document expands variables and command substitutions, exactly like double quotes. ",{"type":9,"tag":17,"props":437,"children":438},{},[439],{"type":14,"value":440},"Quote the delimiter to turn that off",{"type":14,"value":442},", which you want whenever the text is a script, a config file, or anything containing a literal ",{"type":9,"tag":121,"props":444,"children":446},{"className":445},[],[447],{"type":14,"value":448},"$",{"type":14,"value":450},":",{"type":9,"tag":114,"props":452,"children":455},{"className":453,"code":454,"language":14},[117],"cat \u003C\u003C'EOF' > /etc/cron.d/backup\n0 3 * * * root /opt/bin/backup.sh >> $LOGFILE 2>&1\nEOF\n",[456],{"type":9,"tag":121,"props":457,"children":458},{"__ignoreMap":123},[459],{"type":14,"value":454},{"type":9,"tag":10,"props":461,"children":462},{},[463,465,471,473,479],{"type":14,"value":464},"With ",{"type":9,"tag":121,"props":466,"children":468},{"className":467},[],[469],{"type":14,"value":470},"'EOF'",{"type":14,"value":472}," quoted, ",{"type":9,"tag":121,"props":474,"children":476},{"className":475},[],[477],{"type":14,"value":478},"$LOGFILE",{"type":14,"value":480}," lands in the crontab as the four characters that cron will later expand. Without the quotes, the shell would have expanded it while writing the file — probably to nothing.",{"data":482,"body":483},{},{"type":6,"children":484},[485],{"type":9,"tag":282,"props":486,"children":490},{":answer":284,":options":487,"explanation":488,"question":489},"[\"The quoted form passes the text through literally; the unquoted form expands variables and `$( )` first\",\"The quoted form allows blank lines in the body\",\"There is none; the quotes are a style choice\"]","Unquoted behaves like double quotes, quoted behaves like single quotes — the same distinction as everywhere else in the shell. When you're generating a file that contains its own `$variables`, you almost always want the quoted form.","What is the difference between `cat \u003C\u003CEOF` and `cat \u003C\u003C'EOF'`?",[],{"data":492,"body":493},{},{"type":6,"children":494},[495],{"type":9,"tag":496,"props":497,"children":499},"deep-dive",{"title":498},"`\u003C\u003C-`, `tee`, and redirecting the whole script",[500,525,534,557,566,584,593],{"type":9,"tag":10,"props":501,"children":502},{},[503,508,510,516,518,523],{"type":9,"tag":17,"props":504,"children":505},{},[506],{"type":14,"value":507},"Indented here-documents.",{"type":14,"value":509}," A here-doc's terminator must be at the start of the line, which looks wrong inside an indented function. ",{"type":9,"tag":121,"props":511,"children":513},{"className":512},[],[514],{"type":14,"value":515},"\u003C\u003C-",{"type":14,"value":517}," strips ",{"type":9,"tag":253,"props":519,"children":520},{},[521],{"type":14,"value":522},"leading tabs",{"type":14,"value":524}," (tabs only, not spaces) from every line, terminator included:",{"type":9,"tag":114,"props":526,"children":529},{"className":527,"code":528,"language":14},[117],"deploy() {\n    cat \u003C\u003C-EOF\n        Deploying to $HOST\n        Started at $(date)\n    EOF\n}\n",[530],{"type":9,"tag":121,"props":531,"children":532},{"__ignoreMap":123},[533],{"type":14,"value":528},{"type":9,"tag":10,"props":535,"children":536},{},[537,548,550,555],{"type":9,"tag":17,"props":538,"children":539},{},[540,546],{"type":9,"tag":121,"props":541,"children":543},{"className":542},[],[544],{"type":14,"value":545},"tee",{"type":14,"value":547}," — write and pass through.",{"type":14,"value":549}," When output needs to go to a file ",{"type":9,"tag":253,"props":551,"children":552},{},[553],{"type":14,"value":554},"and",{"type":14,"value":556}," onward:",{"type":9,"tag":114,"props":558,"children":561},{"className":559,"code":560,"language":14},[117],"make 2>&1 | tee build.log        # watch it scroll and keep a copy\nmake 2>&1 | tee -a build.log     # append rather than truncate\n",[562],{"type":9,"tag":121,"props":563,"children":564},{"__ignoreMap":123},[565],{"type":14,"value":560},{"type":9,"tag":10,"props":567,"children":568},{},[569,574,576,582],{"type":9,"tag":17,"props":570,"children":571},{},[572],{"type":14,"value":573},"Redirecting the entire script.",{"type":14,"value":575}," ",{"type":9,"tag":121,"props":577,"children":579},{"className":578},[],[580],{"type":14,"value":581},"exec",{"type":14,"value":583}," without a command changes the current shell's own descriptors, and everything after it is affected:",{"type":9,"tag":114,"props":585,"children":588},{"className":586,"code":587,"language":14},[117],"#!/bin/sh\nexec >> /var/log/myjob.log 2>&1\n\necho \"Started at $(date)\"     # goes to the log, not the terminal\n",[589],{"type":9,"tag":121,"props":590,"children":591},{"__ignoreMap":123},[592],{"type":14,"value":587},{"type":9,"tag":10,"props":594,"children":595},{},[596,598,604],{"type":14,"value":597},"Three lines at the top of a cron script and every ",{"type":9,"tag":121,"props":599,"children":601},{"className":600},[],[602],{"type":14,"value":603},"echo",{"type":14,"value":605}," in it becomes a log entry, with no redirection on the crontab line and no way to forget one. It also captures output from programs the script calls, which a per-command redirect would miss.",{"data":607,"body":608},{},{"type":6,"children":609},[610,616,626,631,640,652],{"type":9,"tag":43,"props":611,"children":613},{"id":612},"why-this-matters-most-in-cron",[614],{"type":14,"value":615},"Why this matters most in cron",{"type":9,"tag":10,"props":617,"children":618},{},[619,621],{"type":14,"value":620},"A command run from cron has no terminal. Anything it writes to stdout or stderr is mailed to the account — if local mail is configured, which on a modern server it usually is not. Which means the default behaviour of an unredirected cron job is: ",{"type":9,"tag":17,"props":622,"children":623},{},[624],{"type":14,"value":625},"output is generated, then silently thrown away.",{"type":9,"tag":10,"props":627,"children":628},{},[629],{"type":14,"value":630},"So a job that has been failing for six weeks looks identical to one that has been working. The fix is one line:",{"type":9,"tag":114,"props":632,"children":635},{"className":633,"code":634,"language":14},[117],"0 3 * * * /opt/bin/backup.sh >> /var/log/backup.log 2>&1\n",[636],{"type":9,"tag":121,"props":637,"children":638},{"__ignoreMap":123},[639],{"type":14,"value":634},{"type":9,"tag":10,"props":641,"children":642},{},[643,645,650],{"type":14,"value":644},"or the ",{"type":9,"tag":121,"props":646,"children":648},{"className":647},[],[649],{"type":14,"value":581},{"type":14,"value":651}," form inside the script itself, which is better because it cannot be forgotten by whoever edits the crontab next.",{"type":9,"tag":10,"props":653,"children":654},{},[655],{"type":14,"value":656},"Next up: the last lesson — the handful of habits that separate a script that works on your machine from one you're willing to run unattended.",1787908866616]