[{"data":1,"prerenderedAt":603},["ShallowReactive",2],{"mdc-y6e64l-key":3,"mdc--wf2bc5-key":37,"mdc-uqtvf4-key":79,"mdc-4kttq0-key":109,"mdc--ebnghy-key":215,"mdc-onb0pp-key":227,"mdc--i9wqdu-key":312,"mdc--fa4hr6-key":453,"mdc--8n19f8-key":498,"mdc--580xjg-key":510,"mdc--9vxy6f-key":588},{"data":4,"body":5},{},{"type":6,"children":7},"root",[8,24],{"type":9,"tag":10,"props":11,"children":12},"element","p",{},[13,16,22],{"type":14,"value":15},"text","You already know how to run commands one at a time. A ",{"type":9,"tag":17,"props":18,"children":19},"strong",{},[20],{"type":14,"value":21},"script",{"type":14,"value":23}," is nothing more than a file containing the commands you would have typed, run by the same shell that would have run them interactively.",{"type":9,"tag":10,"props":25,"children":26},{},[27,29,35],{"type":14,"value":28},"That is the whole idea, and it is worth taking seriously — because it means there is no separate \"scripting language\" to learn. Everything you already type at the prompt is valid in a script, and almost everything in a script is valid at the prompt. What follows is mostly a tour of the parts you ",{"type":9,"tag":30,"props":31,"children":32},"em",{},[33],{"type":14,"value":34},"wouldn't",{"type":14,"value":36}," type by hand.",{"data":38,"body":39},{},{"type":6,"children":40},[41,48,53,66],{"type":9,"tag":42,"props":43,"children":45},"h2",{"id":44},"a-script-is-a-file-the-shell-reads",[46],{"type":14,"value":47},"A script is a file the shell reads",{"type":9,"tag":10,"props":49,"children":50},{},[51],{"type":14,"value":52},"Make one. Three lines is enough for a real script:",{"type":9,"tag":54,"props":55,"children":59},"pre",{"className":56,"code":58,"language":14},[57],"language-text","#!/bin/sh\n# Say hello and get out of the way.\necho \"Hello from a script\"\n",[60],{"type":9,"tag":61,"props":62,"children":64},"code",{"__ignoreMap":63},"",[65],{"type":14,"value":58},{"type":9,"tag":10,"props":67,"children":68},{},[69,71,77],{"type":14,"value":70},"Save it as ",{"type":9,"tag":61,"props":72,"children":74},{"className":73},[],[75],{"type":14,"value":76},"hello.sh",{"type":14,"value":78},". Now make it executable and run it:",{"data":80,"body":81},{},{"type":6,"children":82},[83,88],{"type":9,"tag":84,"props":85,"children":87},"terminal-teaser",{":lines":86},"[{\"cmd\":\"chmod +x hello.sh\",\"out\":\"\"},{\"cmd\":\"./hello.sh\",\"out\":\"Hello from a script\"},{\"cmd\":\"sh hello.sh\",\"out\":\"Hello from a script\"}]",[],{"type":9,"tag":10,"props":89,"children":90},{},[91,93,99,101,107],{"type":14,"value":92},"Two things there are worth pulling apart: why ",{"type":9,"tag":61,"props":94,"children":96},{"className":95},[],[97],{"type":14,"value":98},"chmod +x",{"type":14,"value":100},", and why ",{"type":9,"tag":61,"props":102,"children":104},{"className":103},[],[105],{"type":14,"value":106},"./",{"type":14,"value":108},".",{"data":110,"body":111},{},{"type":6,"children":112},[113,130,157],{"type":9,"tag":42,"props":114,"children":116},{"id":115},"why-chmod-x-and-why",[117,119,124,125],{"type":14,"value":118},"Why ",{"type":9,"tag":61,"props":120,"children":122},{"className":121},[],[123],{"type":14,"value":98},{"type":14,"value":100},{"type":9,"tag":61,"props":126,"children":128},{"className":127},[],[129],{"type":14,"value":106},{"type":9,"tag":10,"props":131,"children":132},{},[133,135,140,142,148,150,155],{"type":14,"value":134},"A file is only runnable if the ",{"type":9,"tag":17,"props":136,"children":137},{},[138],{"type":14,"value":139},"executable bit",{"type":14,"value":141}," is set on it. Creating a file with an editor doesn't set that bit — text files aren't programs by default, which is a good thing. ",{"type":9,"tag":61,"props":143,"children":145},{"className":144},[],[146],{"type":14,"value":147},"chmod +x hello.sh",{"type":14,"value":149}," sets it. For a script the readable bit matters too: the shell has to ",{"type":9,"tag":30,"props":151,"children":152},{},[153],{"type":14,"value":154},"read",{"type":14,"value":156}," the file to run it, unlike a compiled binary which the kernel loads directly.",{"type":9,"tag":10,"props":158,"children":159},{},[160,162,167,169,174,176,182,184,189,191,197,199,205,207,213],{"type":14,"value":161},"The ",{"type":9,"tag":61,"props":163,"children":165},{"className":164},[],[166],{"type":14,"value":106},{"type":14,"value":168}," is the other half. When you type a bare ",{"type":9,"tag":61,"props":170,"children":172},{"className":171},[],[173],{"type":14,"value":76},{"type":14,"value":175},", the shell searches ",{"type":9,"tag":61,"props":177,"children":179},{"className":178},[],[180],{"type":14,"value":181},"PATH",{"type":14,"value":183}," for it — and the current directory is not on ",{"type":9,"tag":61,"props":185,"children":187},{"className":186},[],[188],{"type":14,"value":181},{"type":14,"value":190}," on any sane system, precisely so that a file called ",{"type":9,"tag":61,"props":192,"children":194},{"className":193},[],[195],{"type":14,"value":196},"ls",{"type":14,"value":198}," sitting in a directory you happened to ",{"type":9,"tag":61,"props":200,"children":202},{"className":201},[],[203],{"type":14,"value":204},"cd",{"type":14,"value":206}," into cannot hijack the real one. ",{"type":9,"tag":61,"props":208,"children":210},{"className":209},[],[211],{"type":14,"value":212},"./hello.sh",{"type":14,"value":214}," is you saying \"this exact file, in this directory\", which skips the search entirely.",{"data":216,"body":217},{},{"type":6,"children":218},[219],{"type":9,"tag":220,"props":221,"children":226},"quiz",{":answer":222,":options":223,"explanation":224,"question":225},"0","[\"The executable bit isn't set on the file\",\"The script contains a command you're not allowed to run\",\"The file is in a directory that isn't on your `PATH`\"]","\"Permission denied\" here is about the file itself, not its contents — the shell never got as far as reading a single line. `chmod +x deploy.sh`. A `PATH` problem would say \"command not found\" instead, and a forbidden command inside the script would fail later, after the script had already started.","You run `./deploy.sh` and get \"Permission denied\". What is the most likely cause?",[],{"data":228,"body":229},{},{"type":6,"children":230},[231,237,257,292],{"type":9,"tag":42,"props":232,"children":234},{"id":233},"the-shebang-line",[235],{"type":14,"value":236},"The shebang line",{"type":9,"tag":10,"props":238,"children":239},{},[240,242,248,250,255],{"type":14,"value":241},"That first line — ",{"type":9,"tag":61,"props":243,"children":245},{"className":244},[],[246],{"type":14,"value":247},"#!/bin/sh",{"type":14,"value":249}," — is called the ",{"type":9,"tag":17,"props":251,"children":252},{},[253],{"type":14,"value":254},"shebang",{"type":14,"value":256},", and it is the one piece of syntax that isn't just a command you could have typed.",{"type":9,"tag":10,"props":258,"children":259},{},[260,262,268,270,275,277,283,285,290],{"type":14,"value":261},"To the shell it looks like a comment, because ",{"type":9,"tag":61,"props":263,"children":265},{"className":264},[],[266],{"type":14,"value":267},"#",{"type":14,"value":269}," starts a comment. But it isn't read by the shell at all. It is read by the ",{"type":9,"tag":30,"props":271,"children":272},{},[273],{"type":14,"value":274},"kernel",{"type":14,"value":276},", at the moment you execute the file. The kernel looks at the first two bytes; if they are ",{"type":9,"tag":61,"props":278,"children":280},{"className":279},[],[281],{"type":14,"value":282},"#!",{"type":14,"value":284},", it treats the rest of the line as the path to an interpreter, and runs ",{"type":9,"tag":17,"props":286,"children":287},{},[288],{"type":14,"value":289},"that",{"type":14,"value":291}," program with your file as its argument.",{"type":9,"tag":10,"props":293,"children":294},{},[295,297,302,304,310],{"type":14,"value":296},"So ",{"type":9,"tag":61,"props":298,"children":300},{"className":299},[],[301],{"type":14,"value":212},{"type":14,"value":303}," really becomes ",{"type":9,"tag":61,"props":305,"children":307},{"className":306},[],[308],{"type":14,"value":309},"/bin/sh ./hello.sh",{"type":14,"value":311},". This is why the shebang must be the very first line, and why it must be an absolute path — the kernel does no searching.",{"data":313,"body":314},{},{"type":6,"children":315},[316],{"type":9,"tag":317,"props":318,"children":320},"deep-dive",{"title":319},"`#!/bin/sh` or `#!/bin/bash`?",[321,333,375,409,442],{"type":9,"tag":10,"props":322,"children":323},{},[324,326,331],{"type":14,"value":325},"This course writes ",{"type":9,"tag":61,"props":327,"children":329},{"className":328},[],[330],{"type":14,"value":247},{"type":14,"value":332},", and that is a deliberate constraint rather than nostalgia.",{"type":9,"tag":10,"props":334,"children":335},{},[336,342,344,350,352,358,360,366,368,374],{"type":9,"tag":61,"props":337,"children":339},{"className":338},[],[340],{"type":14,"value":341},"/bin/sh",{"type":14,"value":343}," promises a POSIX shell — the common subset every Unix has agreed on. On Debian and Ubuntu it is actually ",{"type":9,"tag":61,"props":345,"children":347},{"className":346},[],[348],{"type":14,"value":349},"dash",{"type":14,"value":351},", a small fast shell. On Alpine it is ",{"type":9,"tag":61,"props":353,"children":355},{"className":354},[],[356],{"type":14,"value":357},"busybox ash",{"type":14,"value":359},". On many systems it is ",{"type":9,"tag":61,"props":361,"children":363},{"className":362},[],[364],{"type":14,"value":365},"bash",{"type":14,"value":367}," pretending to be ",{"type":9,"tag":61,"props":369,"children":371},{"className":370},[],[372],{"type":14,"value":373},"sh",{"type":14,"value":108},{"type":9,"tag":10,"props":376,"children":377},{},[378,384,386,392,394,400,402,407],{"type":9,"tag":61,"props":379,"children":381},{"className":380},[],[382],{"type":14,"value":383},"#!/bin/bash",{"type":14,"value":385}," gets you a much richer language: arrays, ",{"type":9,"tag":61,"props":387,"children":389},{"className":388},[],[390],{"type":14,"value":391},"[[ ]]",{"type":14,"value":393},", ",{"type":9,"tag":61,"props":395,"children":397},{"className":396},[],[398],{"type":14,"value":399},"local",{"type":14,"value":401},", string manipulation. It also gets you a script that dies immediately in an Alpine container, a BusyBox initramfs, or a minimal CI image — all places where scripts actually run and where ",{"type":9,"tag":61,"props":403,"children":405},{"className":404},[],[406],{"type":14,"value":365},{"type":14,"value":408}," frequently isn't installed.",{"type":9,"tag":10,"props":410,"children":411},{},[412,414,433,435,440],{"type":14,"value":413},"The rule worth internalising: ",{"type":9,"tag":17,"props":415,"children":416},{},[417,419,424,426,431],{"type":14,"value":418},"write ",{"type":9,"tag":61,"props":420,"children":422},{"className":421},[],[423],{"type":14,"value":373},{"type":14,"value":425}," for anything that has to be portable, and write ",{"type":9,"tag":61,"props":427,"children":429},{"className":428},[],[430],{"type":14,"value":365},{"type":14,"value":432}," deliberately, not by accident.",{"type":14,"value":434}," A script that declares ",{"type":9,"tag":61,"props":436,"children":438},{"className":437},[],[439],{"type":14,"value":247},{"type":14,"value":441}," and then uses a bash-only feature is the worst of both, because it only breaks on the machine you didn't test on.",{"type":9,"tag":10,"props":443,"children":444},{},[445,447,452],{"type":14,"value":446},"Everything in this course runs under a plain POSIX ",{"type":9,"tag":61,"props":448,"children":450},{"className":449},[],[451],{"type":14,"value":373},{"type":14,"value":108},{"data":454,"body":455},{},{"type":6,"children":456},[457,463,475,484,489],{"type":9,"tag":42,"props":458,"children":460},{"id":459},"comments-and-the-shape-of-a-real-script",[461],{"type":14,"value":462},"Comments and the shape of a real script",{"type":9,"tag":10,"props":464,"children":465},{},[466,468,473],{"type":14,"value":467},"Anything from a ",{"type":9,"tag":61,"props":469,"children":471},{"className":470},[],[472],{"type":14,"value":267},{"type":14,"value":474}," to the end of the line is ignored — except that shebang, and except inside quotes.",{"type":9,"tag":54,"props":476,"children":479},{"className":477,"code":478,"language":14},[57],"# A full-line comment.\necho \"Deploying\"   # ...and a trailing one.\necho \"The # here is inside quotes, so it prints\"\n",[480],{"type":9,"tag":61,"props":481,"children":482},{"__ignoreMap":63},[483],{"type":14,"value":478},{"type":9,"tag":10,"props":485,"children":486},{},[487],{"type":14,"value":488},"Real scripts tend to grow the same skeleton: shebang, a comment saying what the thing does, then the work.",{"type":9,"tag":54,"props":490,"children":493},{"className":491,"code":492,"language":14},[57],"#!/bin/sh\n#\n# backup.sh — copy the site content to the backup volume.\n# Run nightly from cron.\n\necho \"Starting backup at $(date)\"\ncp -r /var/www/site /mnt/backup/\necho \"Done\"\n",[494],{"type":9,"tag":61,"props":495,"children":496},{"__ignoreMap":63},[497],{"type":14,"value":492},{"data":499,"body":500},{},{"type":6,"children":501},[502],{"type":9,"tag":503,"props":504,"children":509},"fill-blank",{":answer":505,"hint":506,"placeholder":507,"prompt":508},"[\"chmod +x deploy.sh\",\"chmod u+x deploy.sh\",\"chmod 755 deploy.sh\",\"chmod a+x deploy.sh\",\"chmod ugo+x deploy.sh\"]","The command sets a permission bit, and the bit is the executable one.","chmod ...","Make the file `deploy.sh` executable.",[],{"data":511,"body":512},{},{"type":6,"children":513},[514,520,525,534,539,548,567],{"type":9,"tag":42,"props":515,"children":517},{"id":516},"two-ways-to-run-it-and-why-the-difference-matters",[518],{"type":14,"value":519},"Two ways to run it, and why the difference matters",{"type":9,"tag":10,"props":521,"children":522},{},[523],{"type":14,"value":524},"You have now seen both:",{"type":9,"tag":54,"props":526,"children":529},{"className":527,"code":528,"language":14},[57],"./hello.sh      # execute the file — kernel reads the shebang, starts a new shell\nsh hello.sh     # hand the file to sh yourself — shebang ignored entirely\n",[530],{"type":9,"tag":61,"props":531,"children":532},{"__ignoreMap":63},[533],{"type":14,"value":528},{"type":9,"tag":10,"props":535,"children":536},{},[537],{"type":14,"value":538},"There is a third, and it behaves differently enough to be worth knowing now:",{"type":9,"tag":54,"props":540,"children":543},{"className":541,"code":542,"language":14},[57],". ./hello.sh    # \"source\" it — run the lines in the CURRENT shell\n",[544],{"type":9,"tag":61,"props":545,"children":546},{"__ignoreMap":63},[547],{"type":14,"value":542},{"type":9,"tag":10,"props":549,"children":550},{},[551,553,558,560,565],{"type":14,"value":552},"The first two start a ",{"type":9,"tag":17,"props":554,"children":555},{},[556],{"type":14,"value":557},"new shell process",{"type":14,"value":559},". Variables it sets, directories it changes into, all of it vanishes when the script exits. That is why you cannot write a script that changes your shell's directory — the same reason ",{"type":9,"tag":61,"props":561,"children":563},{"className":562},[],[564],{"type":14,"value":204},{"type":14,"value":566}," has to be a builtin.",{"type":9,"tag":10,"props":568,"children":569},{},[570,572,578,580,586],{"type":14,"value":571},"Sourcing runs the lines in the shell you're already sitting in, which is how ",{"type":9,"tag":61,"props":573,"children":575},{"className":574},[],[576],{"type":14,"value":577},".bashrc",{"type":14,"value":579}," and ",{"type":9,"tag":61,"props":581,"children":583},{"className":582},[],[584],{"type":14,"value":585},".profile",{"type":14,"value":587}," work, and how scripts that are meant to set up your environment are used.",{"data":589,"body":590},{},{"type":6,"children":591},[592,598],{"type":9,"tag":220,"props":593,"children":597},{":answer":222,":options":594,"explanation":595,"question":596},"[\"Exactly where it was — the `cd` happened in a different process\",\"In `/var/log`\",\"It depends on whether the script exits successfully\"]","`./go.sh` starts a new shell. That child shell changes its own directory, then exits, taking the change with it. Run it as `. ./go.sh` and the `cd` happens in your shell instead.","A script contains `cd /var/log`. You run it with `./go.sh`. Where is your shell afterwards?",[],{"type":9,"tag":10,"props":599,"children":600},{},[601],{"type":14,"value":602},"Next up: variables — how to hold on to a value, and the two rules about spaces and quotes that cause more broken scripts than anything else in the shell.",1787908866327]