{"id":251,"date":"2026-10-30T12:28:20","date_gmt":"2026-10-30T12:28:20","guid":{"rendered":"https:\/\/devdojo.co.in\/?p=251"},"modified":"2026-10-30T12:28:21","modified_gmt":"2026-10-30T12:28:21","slug":"devops-series-part-2-linux-shell-basics","status":"publish","type":"post","link":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/30\/devops-series-part-2-linux-shell-basics\/","title":{"rendered":"DevOps CI\/CD Part 2: Linux and Shell Basics Every DevOps Engineer Needs"},"content":{"rendered":"\n<div class=\"dd-post\">\n<style>\n.single article:has(.dd-hero) .entry-media{display:none}\n.dd-post{--navy:#0b1020;--blue:#3178c6;--violet:#7c5cff;--ink:#1f2433;--muted:#5b6275;--line:#e3e6ef;font-size:17px;line-height:1.75;color:var(--ink)}\n.dd-post h2{margin:2.2em 0 .6em;font-size:1.6em;line-height:1.3;scroll-margin-top:90px}\n.dd-post h3{margin:1.6em 0 .5em;font-size:1.22em;scroll-margin-top:90px}\n.dd-post a{color:var(--blue);text-decoration:underline;text-underline-offset:3px}\n.dd-post pre{background:#0f1430;color:#e6e9f5;padding:18px 20px;border-radius:12px;overflow-x:auto;line-height:1.6;border:1px solid #232a52;margin:1.2em 0;font-size:14.5px !important}\n.dd-post pre code{font-size:1em !important;background:none;color:inherit;padding:0;font-family:\"JetBrains Mono\",\"Fira Code\",Consolas,Menlo,monospace;white-space:pre}\n.dd-post :not(pre)>code{background:#eef1fb;color:#2b3a8f;padding:2px 6px;border-radius:5px;font-size:.9em}\n.dd-post .dd-file{display:inline-block;background:#151b3b;color:#c9d1ff;font:600 13px\/1 \"JetBrains Mono\",Consolas,monospace;padding:7px 12px;border-radius:8px 8px 0 0;margin:1em 0 -1.2em;position:relative}\n.dd-post .dd-hero{margin:0 0 1.6em;border-radius:16px;overflow:hidden;box-shadow:0 10px 30px rgba(11,16,32,.25)}\n.dd-post .dd-hero svg{display:block;width:100%;height:auto}\n.dd-post .dd-hero .node{transition:transform .25s,filter .25s;transform-box:fill-box;transform-origin:center;cursor:pointer}\n.dd-post .dd-hero .node:hover{transform:scale(1.08);filter:drop-shadow(0 0 10px #7c5cff)}\n.dd-post .dd-series{display:inline-flex;align-items:center;gap:8px;background:#151b3b;color:#dfe3ff;border-radius:999px;padding:6px 14px;font-size:.88em;font-weight:600;margin:0 0 .8em}\n.dd-post .dd-series span{background:var(--violet);color:#fff;border-radius:999px;padding:2px 10px}\n.dd-post .dd-prereq{font-size:.96em;color:var(--muted);border-left:3px solid var(--line);padding-left:12px;margin:.6em 0 1.2em}\n.dd-post .dd-summary{background:linear-gradient(135deg,#eef3ff,#f4efff);border:1px solid #d6dcf5;border-left:5px solid var(--violet);border-radius:12px;padding:18px 22px;margin:1.4em 0}\n.dd-post .dd-summary strong.lbl{display:block;font-size:1.05em;margin-bottom:6px;color:#3a2fa0}\n.dd-post .dd-summary ul{margin:.3em 0 0 1.1em}\n.dd-post .dd-toc{background:#f8f9fc;border:1px solid var(--line);border-radius:12px;padding:16px 22px;margin:1.4em 0}\n.dd-post .dd-toc ol{margin:.4em 0 0 1.2em;columns:2;column-gap:28px}\n.dd-post .dd-toc li{break-inside:avoid;margin:.15em 0}\n@media(max-width:640px){.dd-post .dd-toc ol{columns:1}}\n.dd-post .dd-tip,.dd-post .dd-note,.dd-post .dd-warn{border-radius:10px;padding:14px 18px;margin:1.3em 0}\n.dd-post .dd-tip{background:#ecfbf3;border-left:4px solid #1f9d61}\n.dd-post .dd-note{background:#eef4ff;border-left:4px solid var(--blue)}\n.dd-post .dd-warn{background:#fff6e8;border-left:4px solid #e08a00}\n.dd-post .dd-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:14px;margin:1.3em 0}\n.dd-post .dd-card{border:1px solid var(--line);border-radius:12px;padding:14px 16px;background:#fff;box-shadow:0 2px 8px rgba(20,30,70,.05)}\n.dd-post .dd-card b{display:block;color:#3a2fa0;margin-bottom:4px}\n.dd-post table{width:100%;border-collapse:collapse;margin:1.3em 0;font-size:.95em}\n.dd-post th,.dd-post td{border:1px solid var(--line);padding:10px 12px;text-align:left;vertical-align:top}\n.dd-post th{background:#151b3b;color:#fff}\n.dd-post tr:nth-child(even) td{background:#f8f9fc}\n.dd-post .dd-tablewrap{overflow-x:auto}\n.dd-post figure{margin:1.6em 0;text-align:center}\n.dd-post figure svg{width:100%;height:auto;border-radius:12px}\n.dd-post figcaption{font-size:.9em;color:var(--muted);margin-top:8px}\n.dd-post details{border:1px solid var(--line);border-radius:10px;padding:12px 16px;margin:.7em 0;background:#fff}\n.dd-post details summary{cursor:pointer;font-weight:600}\n.dd-post details[open] summary{margin-bottom:8px;color:#3a2fa0}\n.dd-post .dd-roadmap{border:2px solid #d6dcf5;background:#fbfbff}\n.dd-post .dd-roadmap h4{margin:1em 0 .3em;color:#151b3b;font-size:1em;text-transform:uppercase;letter-spacing:.04em}\n.dd-post .dd-roadmap ol{margin:.2em 0 .6em 1.4em;padding:0}\n.dd-post .dd-roadmap li{margin:.2em 0}\n.dd-post .dd-roadmap .here{background:var(--violet);color:#fff;border-radius:999px;padding:1px 9px;font-size:.8em;font-weight:700;margin-left:6px}\n.dd-post .dd-roadmap .soon{color:#8a90a6;font-size:.82em;margin-left:6px}\n.dd-post .dd-tabs{border:1px solid var(--line);border-radius:12px;padding:6px 16px 16px;margin:1.4em 0;background:#fbfbfe}\n.dd-post .dd-tabs>input{position:absolute;opacity:0;pointer-events:none}\n.dd-post .dd-tabs .labels label{display:inline-block;padding:8px 16px;margin:10px 6px 0 0;border-radius:999px;border:1px solid #cfd5ea;cursor:pointer;font-weight:600;font-size:.95em;background:#fff;transition:background .2s}\n.dd-post .dd-tabs .labels label:hover{border-color:var(--violet)}\n.dd-post .dd-tabs .panel{display:none;margin-top:12px}\n.dd-post .dd-tabs #ti1:checked~.labels label[for=ti1],.dd-post .dd-tabs #ti2:checked~.labels label[for=ti2],.dd-post .dd-tabs #ti3:checked~.labels label[for=ti3],.dd-post .dd-tabs #ti4:checked~.labels label[for=ti4]{background:var(--violet);color:#fff;border-color:var(--violet)}\n.dd-post .dd-tabs #ti1:checked~.p1,.dd-post .dd-tabs #ti2:checked~.p2,.dd-post .dd-tabs #ti3:checked~.p3,.dd-post .dd-tabs #ti4:checked~.p4{display:block}\n.dd-post .dd-tabs #ti1:focus-visible~.labels label[for=ti1],.dd-post .dd-tabs #ti2:focus-visible~.labels label[for=ti2],.dd-post .dd-tabs #ti3:focus-visible~.labels label[for=ti3],.dd-post .dd-tabs #ti4:focus-visible~.labels label[for=ti4]{outline:2px solid var(--blue);outline-offset:2px}\n.dd-post .dd-cta{background:linear-gradient(135deg,#0b1020,#151b3b);color:#e8ebff;border-radius:16px;padding:24px 26px;margin:2em 0;text-align:center}\n.dd-post .dd-cta h3{color:#fff;margin-top:0}\n.dd-post .dd-cta a{color:#a99bff}\n.dd-post .dd-vsc{position:relative;margin:1.5em 0;border-radius:12px;overflow:hidden;background:#1e1e1e;border:1px solid #2b2b2b;box-shadow:0 14px 34px rgba(0,0,0,.28);font-family:\"Segoe UI\",system-ui,sans-serif}\n.dd-post .dd-vsc>input{position:absolute;opacity:0;pointer-events:none}\n.dd-post .dd-vsc-bar{display:flex;align-items:center;gap:12px;background:#323233;color:#cccccc;font-size:12.5px;padding:8px 12px}\n.dd-post .dd-dots{display:inline-flex;gap:7px}.dd-post .dd-dots i{width:12px;height:12px;border-radius:50%;background:#ff5f57;display:block}.dd-post .dd-dots i:nth-child(2){background:#febc2e}.dd-post .dd-dots i:nth-child(3){background:#28c840}\n.dd-post .dd-vsc-title{flex:1;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:50px;opacity:.85}\n.dd-post .dd-vsc-tabs{display:flex;align-items:stretch;background:#252526;border-bottom:1px solid #1e1e1e;overflow-x:auto}\n.dd-post .dd-vsc-tabs label{display:flex;align-items:center;gap:7px;padding:9px 16px;color:#969696;font-size:13px;cursor:pointer;background:#2d2d2d;border-right:1px solid #252526;border-top:1px solid transparent;white-space:nowrap;margin:0;transition:color .15s}\n.dd-post .dd-vsc-tabs label:hover{color:#e0e0e0}\n.dd-post .dd-vsc-tabs .ic{font:700 9.5px\/1 Consolas,monospace;padding:3px 4px;border-radius:3px;color:#1e1e1e}\n.dd-post .ic.py{background:#ffd43b}.dd-post .ic.js{background:#f1e05a}.dd-post .ic.sh{background:#89e051}\n.dd-post .dd-vsc-tabs label.run{margin-left:auto;background:transparent;border:0;color:#89d185;font-weight:600;display:none}\n.dd-post .dd-vsc-tabs label.run:hover{color:#b5f0b0;background:#2a2d2e}\n.dd-post .dd-vsc-pane{display:none}\n.dd-post .dd-vsc>.f1:checked~.dd-vsc-tabs .t1,.dd-post .dd-vsc>.f2:checked~.dd-vsc-tabs .t2,.dd-post .dd-vsc>.f3:checked~.dd-vsc-tabs .t3{background:#1e1e1e;color:#ffffff;border-top-color:#3178c6}\n.dd-post .dd-vsc>.f1:checked~.p1,.dd-post .dd-vsc>.f2:checked~.p2,.dd-post .dd-vsc>.f3:checked~.p3{display:block}\n.dd-post .dd-vsc>.f1:checked~.dd-vsc-tabs .run1,.dd-post .dd-vsc>.f2:checked~.dd-vsc-tabs .run2,.dd-post .dd-vsc>.f3:checked~.dd-vsc-tabs .run3{display:flex}\n.dd-post .dd-vsc>.f1:focus-visible~.dd-vsc-tabs .t1,.dd-post .dd-vsc>.f2:focus-visible~.dd-vsc-tabs .t2,.dd-post .dd-vsc>.f3:focus-visible~.dd-vsc-tabs .t3,.dd-post .dd-vsc>input[type=checkbox]:focus-visible~.dd-vsc-tabs .run{outline:2px solid #3178c6;outline-offset:-2px}\n.dd-post .dd-vsc-crumbs{background:#1e1e1e;color:#a9a9a9;font-size:12px;padding:5px 16px 2px}\n.dd-post .dd-vsc pre.dd-vsc-code{background:#1e1e1e;color:#d4d4d4;border:0;border-radius:0;margin:0;padding:8px 0 14px;counter-reset:ln;line-height:1.62;overflow-x:auto}\n.dd-post .dd-vsc pre code{font-family:Consolas,\"JetBrains Mono\",\"Fira Code\",Menlo,monospace;color:inherit;background:none;white-space:pre}\n.dd-post .dd-vsc .l{display:inline-block;min-width:100%;padding-right:18px;box-sizing:border-box}\n.dd-post .dd-vsc .l::before{counter-increment:ln;content:counter(ln);display:inline-block;width:3.2em;padding-right:1.4em;text-align:right;color:#6e7681;user-select:none;-webkit-user-select:none}\n.dd-post .dd-vsc .l:hover{background:#2a2d2e}.dd-post .dd-vsc .l:hover::before{color:#c6c6c6}\n.dd-post .dd-vsc .k,.dd-post .dd-vsc .kn{color:#c586c0}.dd-post .dd-vsc .kc{color:#569cd6}.dd-post .dd-vsc .s{color:#ce9178}.dd-post .dd-vsc .c{color:#6a9955}.dd-post .dd-vsc .m{color:#b5cea8}.dd-post .dd-vsc .nf{color:#dcdcaa}.dd-post .dd-vsc .nc{color:#4ec9b0}.dd-post .dd-vsc .nb{color:#4ec9b0}.dd-post .dd-vsc .nd{color:#dcdcaa}.dd-post .dd-vsc .n{color:#9cdcfe}.dd-post .dd-vsc .o{color:#d4d4d4}.dd-post .dd-vsc .gp{color:#89d185;user-select:none;-webkit-user-select:none}\n.dd-post .dd-vsc-term{display:none;border-top:1px solid #3c3c3c;background:#181818}\n.dd-post .dd-vsc>.r1:checked~.p1 .dd-vsc-term,.dd-post .dd-vsc>.r2:checked~.p2 .dd-vsc-term,.dd-post .dd-vsc>.r3:checked~.p3 .dd-vsc-term{display:block;animation:ddterm .35s ease-out}\n@keyframes ddterm{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}\n.dd-post .dd-term-head{display:flex;gap:18px;align-items:center;font-size:11px;letter-spacing:.06em;color:#8b8b8b;padding:7px 16px 0}\n.dd-post .dd-term-head span:first-child{color:#e7e7e7;border-bottom:1px solid #e7e7e7;padding-bottom:4px}\n.dd-post .dd-term-head .dd-term-note{margin-left:auto;letter-spacing:0;text-transform:none;color:#d7ba7d;border:0;font-size:11.5px}\n.dd-post .dd-vsc-term pre{background:#181818;color:#cccccc;border:0;margin:0;padding:10px 16px 14px;border-radius:0}\n.dd-post .dd-vsc-status{display:flex;flex-wrap:wrap;gap:4px 14px;background:#007acc;color:#fff;font-size:11.5px;padding:4px 12px}\n.dd-post .dd-vsc-status .sp{flex:1}\n.dd-post .dd-vsc-termonly pre.dd-vsc-code{background:#181818;padding:12px 16px}\n@media(max-width:640px){.dd-post .dd-vsc-title{padding-right:0;text-align:left}.dd-post .dd-vsc-status span:nth-child(n+4){display:none}.dd-post .dd-vsc .l::before{width:2.2em;padding-right:1em}}\n.dd-post .dd-vsc-hint{font-size:.93em;color:var(--muted);margin-top:-.6em}\n.dd-post .dd-next{border:1px dashed var(--violet);border-radius:12px;padding:16px 20px;margin:1.6em 0;background:#f7f4ff}\n.dd-post .ic.yml{background:#cb3837;color:#fff}\n<\/style>\n\n<div class=\"dd-hero\">\n<svg viewBox=\"0 0 1200 520\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" role=\"img\" aria-label=\"DevOps CI\/CD series Part 2 banner: a Linux terminal surrounded by the six shell skills covered in this part: files, permissions, pipes, processes, environment variables and scripts\">\n<defs>\n<linearGradient id=\"ddbg\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\"><stop offset=\"0\" stop-color=\"#0b1020\"\/><stop offset=\"1\" stop-color=\"#151b3b\"\/><\/linearGradient>\n<linearGradient id=\"ddac\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\"><stop offset=\"0\" stop-color=\"#3178c6\"\/><stop offset=\"1\" stop-color=\"#7c5cff\"\/><\/linearGradient>\n<radialGradient id=\"ddglow\" cx=\".5\" cy=\".5\" r=\".5\"><stop offset=\"0\" stop-color=\"#7c5cff\" stop-opacity=\".45\"\/><stop offset=\"1\" stop-color=\"#7c5cff\" stop-opacity=\"0\"\/><\/radialGradient>\n<\/defs>\n<rect x=\"0\" y=\"-60\" width=\"1200\" height=\"640\" fill=\"url(#ddbg)\"\/>\n<circle cx=\"900\" cy=\"270\" r=\"280\" fill=\"url(#ddglow)\"\/>\n<g opacity=\".12\" stroke=\"#9fb2ff\"><path d=\"M0 100H1200M0 200H1200M0 300H1200M0 400H1200M200 0V520M400 0V520M600 0V520M800 0V520M1000 0V520\"\/><\/g>\n<rect x=\"60\" y=\"46\" width=\"128\" height=\"38\" rx=\"19\" fill=\"url(#ddac)\"\/>\n<text x=\"124\" y=\"71\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"18\" font-weight=\"700\" fill=\"#fff\">PART 2<\/text>\n<text x=\"204\" y=\"71\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"18\" fill=\"#aab4e8\">DevOps CI\/CD Series \u00b7 DevDojo<\/text>\n<text x=\"60\" y=\"150\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"54\" font-weight=\"800\" fill=\"#ffffff\">Linux &amp; Shell<\/text>\n<text x=\"60\" y=\"212\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"54\" font-weight=\"800\" fill=\"url(#ddac)\">Basics for DevOps<\/text>\n<text x=\"60\" y=\"258\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"23\" fill=\"#c9d1ff\">Files, permissions, pipes and scripts<\/text>\n<text x=\"60\" y=\"290\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"23\" fill=\"#c9d1ff\">that every pipeline runs on<\/text>\n<g font-family=\"Consolas,Menlo,monospace\" font-size=\"17\">\n<rect x=\"60\" y=\"330\" width=\"470\" height=\"140\" rx=\"12\" fill=\"#11162e\" stroke=\"#2a3366\"\/>\n<circle cx=\"82\" cy=\"350\" r=\"6\" fill=\"#ff5f57\"\/><circle cx=\"102\" cy=\"350\" r=\"6\" fill=\"#febc2e\"\/><circle cx=\"122\" cy=\"350\" r=\"6\" fill=\"#28c840\"\/>\n<text x=\"80\" y=\"385\" fill=\"#89d185\">$ <tspan fill=\"#e6e9f5\">chmod +x deploy.sh<\/tspan><\/text>\n<text x=\"80\" y=\"413\" fill=\"#89d185\">$ <tspan fill=\"#e6e9f5\">.\/deploy.sh &amp;&amp; echo &#8220;shipped&#8221;<\/tspan><\/text>\n<text x=\"80\" y=\"441\" fill=\"#c9d1ff\">shipped<tspan fill=\"#e6e9f5\"> <\/tspan><tspan fill=\"#89d185\">\u258c<animate attributeName=\"opacity\" values=\"1;0;1\" dur=\"1.1s\" repeatCount=\"indefinite\"\/><\/tspan><\/text>\n<\/g>\n<path id=\"ddorb\" d=\"M900 120 C 1010 120, 1090 190, 1090 270 C 1090 350, 1010 420, 900 420 C 790 420, 710 350, 710 270 C 710 190, 790 120, 900 120 Z\" fill=\"none\" stroke=\"url(#ddac)\" stroke-width=\"3\" stroke-dasharray=\"8 8\" opacity=\".75\"\/>\n<circle r=\"7\" fill=\"#7c5cff\"><animateMotion dur=\"9s\" repeatCount=\"indefinite\"><mpath href=\"#ddorb\"\/><\/animateMotion><\/circle>\n<circle r=\"5\" fill=\"#3178c6\"><animateMotion dur=\"9s\" begin=\"-3s\" repeatCount=\"indefinite\"><mpath href=\"#ddorb\"\/><\/animateMotion><\/circle>\n<circle r=\"5\" fill=\"#61dafb\"><animateMotion dur=\"9s\" begin=\"-6s\" repeatCount=\"indefinite\"><mpath href=\"#ddorb\"\/><\/animateMotion><\/circle>\n<g class=\"node\"><rect x=\"840\" y=\"220\" width=\"120\" height=\"100\" rx=\"18\" fill=\"#1b2250\" stroke=\"url(#ddac)\" stroke-width=\"3\"\/><text x=\"900\" y=\"268\" text-anchor=\"middle\" font-family=\"Consolas,Menlo,monospace\" font-size=\"34\" font-weight=\"700\" fill=\"#89e051\">$_<\/text><text x=\"900\" y=\"300\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"16\" font-weight=\"700\" fill=\"#fff\">bash<\/text><\/g>\n<g class=\"node\"><rect x=\"846\" y=\"103\" width=\"108\" height=\"34\" rx=\"17\" fill=\"#1b2250\" stroke=\"#3178c6\" stroke-width=\"2\"\/><text x=\"900\" y=\"126\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"16\" font-weight=\"700\" fill=\"#fff\">Files<\/text><\/g>\n<g class=\"node\"><rect x=\"1022\" y=\"163\" width=\"128\" height=\"34\" rx=\"17\" fill=\"#1b2250\" stroke=\"#3178c6\" stroke-width=\"2\"\/><text x=\"1086\" y=\"186\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"16\" font-weight=\"700\" fill=\"#fff\">Permissions<\/text><\/g>\n<g class=\"node\"><rect x=\"1032\" y=\"343\" width=\"108\" height=\"34\" rx=\"17\" fill=\"#1b2250\" stroke=\"#7c5cff\" stroke-width=\"2\"\/><text x=\"1086\" y=\"366\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"16\" font-weight=\"700\" fill=\"#fff\">Pipes<\/text><\/g>\n<g class=\"node\"><rect x=\"840\" y=\"403\" width=\"120\" height=\"34\" rx=\"17\" fill=\"#1b2250\" stroke=\"#7c5cff\" stroke-width=\"2\"\/><text x=\"900\" y=\"426\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"16\" font-weight=\"700\" fill=\"#fff\">Processes<\/text><\/g>\n<g class=\"node\"><rect x=\"650\" y=\"343\" width=\"122\" height=\"34\" rx=\"17\" fill=\"#1b2250\" stroke=\"#7c5cff\" stroke-width=\"2\"\/><text x=\"711\" y=\"366\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"16\" font-weight=\"700\" fill=\"#fff\">Env vars<\/text><\/g>\n<g class=\"node\"><rect x=\"656\" y=\"163\" width=\"110\" height=\"34\" rx=\"17\" fill=\"#1b2250\" stroke=\"#3178c6\" stroke-width=\"2\"\/><text x=\"711\" y=\"186\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"16\" font-weight=\"700\" fill=\"#fff\">Scripts<\/text><\/g>\n<text x=\"1140\" y=\"500\" text-anchor=\"end\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"16\" fill=\"#8f9bd6\">devdojo.co.in<\/text>\n<\/svg>\n<\/div>\n\n<div class=\"dd-series\">Series: DevOps CI\/CD <span>Part 2 of 30<\/span><\/div>\n<p class=\"dd-prereq\"><strong>What you should know first:<\/strong> read <a href=\"https:\/\/devdojo.co.in\/index.php\/2026\/10\/21\/devops-series-part-1-what-is-devops-ci-cd\/\">Part 1: What Is DevOps and How Does CI\/CD Work?<\/a> so you know what a pipeline and a runner are. You need a Linux or macOS terminal, or WSL on Windows. No earlier Linux experience is needed.<\/p>\n\n<p>In Part 1 you built a small pipeline and saw that every <code>run:<\/code> line was a shell command running on a Linux machine. That is why <strong>Linux for DevOps<\/strong> is the first real skill in this series. CI runners, Docker containers, cloud servers and Kubernetes nodes almost all run Linux, and you talk to them through a shell. In this part you will learn the Linux commands and shell basics that DevOps engineers use every day: moving around the file system, file permissions, pipes and logs, environment variables, exit codes and processes. Then you will write two real scripts, a log report and a health check, and run them inside a GitHub Actions pipeline.<\/p>\n\n<div class=\"dd-summary\">\n<strong class=\"lbl\">Quick Summary<\/strong>\n<ul>\n<li>A <strong>shell<\/strong> (usually <strong>bash<\/strong>) is the program that reads your commands. Pipelines, Dockerfiles and servers all use it.<\/li>\n<li>Learn about 25 core commands: <code>ls<\/code>, <code>cd<\/code>, <code>mkdir<\/code>, <code>cp<\/code>, <code>mv<\/code>, <code>cat<\/code>, <code>grep<\/code>, <code>awk<\/code>, <code>chmod<\/code>, <code>ps<\/code>, <code>curl<\/code> and friends.<\/li>\n<li><strong>Permissions<\/strong> (<code>rwx<\/code>, numbers like 755 and 600) decide who can read, change or run a file. \u201cPermission denied\u201d is the most common pipeline error.<\/li>\n<li><strong>Pipes<\/strong> (<code>|<\/code>) and <strong>redirection<\/strong> (<code>&gt;<\/code>, <code>&gt;&gt;<\/code>, <code>2&gt;<\/code>) join small tools into powerful one-liners.<\/li>\n<li>Every command returns an <strong>exit code<\/strong>: 0 means success, anything else means failure. CI tools use it to decide if a step passed.<\/li>\n<li>Start every script with <code>set -euo pipefail<\/code>, quote your variables and check scripts with <strong>ShellCheck<\/strong>.<\/li>\n<\/ul>\n<\/div>\n\n<details class=\"dd-roadmap\">\n<summary>Full DevOps CI\/CD roadmap (30 parts)<\/summary>\n<h4>Beginner<\/h4>\n<ol start=\"1\">\n<li><a href=\"https:\/\/devdojo.co.in\/index.php\/2026\/10\/21\/devops-series-part-1-what-is-devops-ci-cd\/\">What Is DevOps and How Does CI\/CD Work?<\/a><\/li>\n<li>Linux and Shell Basics Every DevOps Engineer Needs <span class=\"here\">You are here<\/span><\/li>\n<li>Git for Teams: Branches, Pull Requests and Trunk-Based Development <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Automated Testing for Pipelines: Unit, Integration and End-to-End <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Your First CI Pipeline with GitHub Actions <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Docker Basics: Images, Containers and Dockerfiles <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Writing Small, Secure, Production-Ready Dockerfiles <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Container Registries and Image Versioning <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Build Artifacts, Versioning and Semantic Releases <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Continuous Delivery vs Continuous Deployment: Your First Release Pipeline <span class=\"soon\">Coming soon<\/span><\/li>\n<\/ol>\n<h4>Intermediate<\/h4>\n<ol start=\"11\">\n<li>Environments, Configuration and Secrets Management <span class=\"soon\">Coming soon<\/span><\/li>\n<li>GitHub Actions Deep Dive: Matrix Builds, Caching and Reusable Workflows <span class=\"soon\">Coming soon<\/span><\/li>\n<li>GitLab CI\/CD and Jenkins: How Other CI Tools Work <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Infrastructure as Code with Terraform <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Configuration Management with Ansible <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Kubernetes Essentials for CI\/CD <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Packaging Apps for Kubernetes with Helm and Kustomize <span class=\"soon\">Coming soon<\/span><\/li>\n<li>GitOps with Argo CD <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Deployment Strategies: Rolling, Blue-Green and Canary <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Feature Flags and Progressive Delivery <span class=\"soon\">Coming soon<\/span><\/li>\n<\/ol>\n<h4>Advanced<\/h4>\n<ol start=\"21\">\n<li>DevSecOps: Code, Dependency and Container Scanning in CI <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Software Supply Chain Security: SBOMs, Signing and SLSA <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Observability: Metrics, Logs and Traces with Prometheus, Grafana and OpenTelemetry <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Alerts, SLOs and Incident Response <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Fast CI for Monorepos: Affected Builds and Remote Caching <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Self-Hosted Runners and Scaling CI Cost-Effectively <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Platform Engineering and Internal Developer Platforms <span class=\"soon\">Coming soon<\/span><\/li>\n<li>AI in DevOps: AI Code Review and Agentic Pipelines <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Measuring DevOps with DORA Metrics <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Final Project: A Complete Pipeline from Commit to Production <span class=\"soon\">Coming soon<\/span><\/li>\n<\/ol>\n<\/details>\n\n<div class=\"dd-toc\"><strong>Table of contents<\/strong>\n<nav aria-label=\"Table of contents\"><ol>\n<li><a href=\"#why-linux\">Why Linux for DevOps?<\/a><\/li>\n<li><a href=\"#shell-basics\">Terminal, shell and file system<\/a><\/li>\n<li><a href=\"#files\">Working with files and folders<\/a><\/li>\n<li><a href=\"#permissions\">File permissions<\/a><\/li>\n<li><a href=\"#pipes\">Pipes, redirection and logs<\/a><\/li>\n<li><a href=\"#env\">Environment variables and exit codes<\/a><\/li>\n<li><a href=\"#processes\">Processes and services<\/a><\/li>\n<li><a href=\"#scripts\">Your first DevOps shell scripts<\/a><\/li>\n<li><a href=\"#safe-scripts\">Writing safe scripts<\/a><\/li>\n<li><a href=\"#errors\">Common errors and fixes<\/a><\/li>\n<li><a href=\"#best-practices\">Best practices<\/a><\/li>\n<li><a href=\"#practice\">Practice task<\/a><\/li>\n<li><a href=\"#faq\">FAQ<\/a><\/li>\n<\/ol><\/nav>\n<\/div>\n<\/div>\n\n\n\n<div class=\"dd-post\">\n<h2 id=\"why-linux\">Why Linux for DevOps matters<\/h2>\n<p>When you write <code>runs-on: ubuntu-latest<\/code> in a GitHub Actions file, GitHub gives you a fresh Ubuntu Linux machine. When you build a Docker image, it is based on a small Linux system such as Debian or Alpine. When you deploy to AWS, Azure, Google Cloud or a Kubernetes cluster, your app almost always runs on Linux. So if you can work comfortably in a Linux shell, you can understand and fix every step of a pipeline.<\/p>\n<figure>\n<svg viewBox=\"0 0 1000 290\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" role=\"img\" aria-label=\"Diagram: Linux runs at every step of a CI\/CD pipeline, from the developer laptop shell, to the CI runner, the Docker container and the production server\">\n<rect width=\"1000\" height=\"290\" fill=\"#0f1430\"\/>\n<defs><marker id=\"ar1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto\"><path d=\"M0 0L10 5L0 10z\" fill=\"#7c5cff\"\/><\/marker><\/defs>\n<text x=\"500\" y=\"40\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"22\" font-weight=\"700\" fill=\"#fff\">Where Linux shows up in a CI\/CD pipeline<\/text>\n<g font-family=\"Segoe UI,Arial,sans-serif\" text-anchor=\"middle\">\n<rect x=\"30\" y=\"80\" width=\"200\" height=\"130\" rx=\"14\" fill=\"#1b2250\" stroke=\"#3178c6\" stroke-width=\"2\"\/>\n<text x=\"130\" y=\"118\" font-size=\"19\" font-weight=\"700\" fill=\"#fff\">Your laptop<\/text><text x=\"130\" y=\"148\" font-size=\"15\" fill=\"#c9d1ff\">bash \/ zsh terminal<\/text><text x=\"130\" y=\"172\" font-size=\"15\" fill=\"#c9d1ff\">(WSL on Windows)<\/text>\n<rect x=\"270\" y=\"80\" width=\"200\" height=\"130\" rx=\"14\" fill=\"#1b2250\" stroke=\"#3178c6\" stroke-width=\"2\"\/>\n<text x=\"370\" y=\"118\" font-size=\"19\" font-weight=\"700\" fill=\"#fff\">CI runner<\/text><text x=\"370\" y=\"148\" font-size=\"15\" fill=\"#c9d1ff\">ubuntu-latest VM<\/text><text x=\"370\" y=\"172\" font-size=\"15\" fill=\"#c9d1ff\">runs your run: lines<\/text>\n<rect x=\"510\" y=\"80\" width=\"200\" height=\"130\" rx=\"14\" fill=\"#1b2250\" stroke=\"#7c5cff\" stroke-width=\"2\"\/>\n<text x=\"610\" y=\"118\" font-size=\"19\" font-weight=\"700\" fill=\"#fff\">Container<\/text><text x=\"610\" y=\"148\" font-size=\"15\" fill=\"#c9d1ff\">Linux image (Alpine,<\/text><text x=\"610\" y=\"172\" font-size=\"15\" fill=\"#c9d1ff\">Debian) + your app<\/text>\n<rect x=\"750\" y=\"80\" width=\"220\" height=\"130\" rx=\"14\" fill=\"#1b2250\" stroke=\"#7c5cff\" stroke-width=\"2\"\/>\n<text x=\"860\" y=\"118\" font-size=\"19\" font-weight=\"700\" fill=\"#fff\">Server \/ cluster<\/text><text x=\"860\" y=\"148\" font-size=\"15\" fill=\"#c9d1ff\">cloud VM or Kubernetes<\/text><text x=\"860\" y=\"172\" font-size=\"15\" fill=\"#c9d1ff\">node running Linux<\/text>\n<\/g>\n<path d=\"M232 145H266\" stroke=\"#7c5cff\" stroke-width=\"3\" marker-end=\"url(#ar1)\"\/><path d=\"M472 145H506\" stroke=\"#7c5cff\" stroke-width=\"3\" marker-end=\"url(#ar1)\"\/><path d=\"M712 145H746\" stroke=\"#7c5cff\" stroke-width=\"3\" marker-end=\"url(#ar1)\"\/>\n<text x=\"249\" y=\"133\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"12\" fill=\"#aab4e8\">push<\/text>\n<text x=\"489\" y=\"133\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"12\" fill=\"#aab4e8\">build<\/text>\n<text x=\"729\" y=\"133\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"12\" fill=\"#aab4e8\">deploy<\/text>\n<circle r=\"6\" fill=\"#61dafb\"><animateMotion dur=\"4s\" repeatCount=\"indefinite\" path=\"M130 235H860\"\/><\/circle>\n<path d=\"M130 235H860\" stroke=\"#2a3366\" stroke-width=\"2\" stroke-dasharray=\"6 6\"\/>\n<text x=\"500\" y=\"270\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"16\" fill=\"#c9d1ff\">The same shell skills work at every step: files, permissions, pipes, variables, processes<\/text>\n<\/svg>\n<figcaption>Figure 1: From your laptop to production, almost every step of a pipeline runs on Linux.<\/figcaption>\n<\/figure>\n<div class=\"dd-cards\">\n<div class=\"dd-card\"><b>Debug failing pipelines<\/b>Most CI errors are shell errors: a missing file, a wrong path, \u201cpermission denied\u201d or \u201ccommand not found\u201d.<\/div>\n<div class=\"dd-card\"><b>Write Dockerfiles<\/b>Every <code>RUN<\/code> line in a Dockerfile is a shell command. You will use this from Part 6.<\/div>\n<div class=\"dd-card\"><b>Look after servers<\/b>Read logs, restart services, check disk space and find what is using the CPU.<\/div>\n<div class=\"dd-card\"><b>Automate boring work<\/b>A 20-line script can replace a 20-step manual checklist, and it never forgets a step.<\/div>\n<\/div>\n\n<h2 id=\"shell-basics\">Terminal, shell and the Linux file system<\/h2>\n<p>Three words are often mixed up, so let us make them clear:<\/p>\n<ul>\n<li><strong>Terminal<\/strong> \u2013 the window you type in (Windows Terminal, macOS Terminal, the VS Code terminal).<\/li>\n<li><strong>Shell<\/strong> \u2013 the program inside the terminal that reads your command, runs it and prints the result. <strong>bash<\/strong> is the most common shell on Linux servers and CI runners; macOS uses <strong>zsh<\/strong> by default, which works almost the same for everything in this post.<\/li>\n<li><strong>Prompt<\/strong> \u2013 the <code>$<\/code> sign that says \u201cI am ready\u201d. In this series, lines that start with <code>$<\/code> are commands you type; do not type the <code>$<\/code> itself.<\/li>\n<\/ul>\n<div class=\"dd-note\"><strong>On Windows?<\/strong> Install WSL (Windows Subsystem for Linux) with <code>wsl --install<\/code> in an administrator PowerShell, restart, and you get a real Ubuntu terminal. Everything in this series works there.<\/div>\n<p>Linux has one big folder tree that starts at <code>\/<\/code> (called \u201croot\u201d). There are no drive letters like <code>C:<\/code>. These are the folders you will meet most often in DevOps work:<\/p>\n<div class=\"dd-tablewrap\"><table>\n<tr><th>Folder<\/th><th>What lives there<\/th><th>DevOps example<\/th><\/tr>\n<tr><td><code>\/<\/code><\/td><td>The top of the tree<\/td><td>Every absolute path starts here<\/td><\/tr>\n<tr><td><code>\/home\/&lt;user&gt;<\/code> (or <code>~<\/code>)<\/td><td>Your personal files<\/td><td>On a GitHub runner your code is checked out under <code>\/home\/runner\/work\/<\/code><\/td><\/tr>\n<tr><td><code>\/etc<\/code><\/td><td>System and app configuration<\/td><td><code>\/etc\/nginx\/nginx.conf<\/code>, <code>\/etc\/hosts<\/code><\/td><\/tr>\n<tr><td><code>\/var\/log<\/code><\/td><td>Log files<\/td><td><code>\/var\/log\/nginx\/access.log<\/code><\/td><\/tr>\n<tr><td><code>\/usr\/bin<\/code>, <code>\/usr\/local\/bin<\/code><\/td><td>Installed programs<\/td><td>Where <code>git<\/code>, <code>node<\/code> and <code>docker<\/code> live<\/td><\/tr>\n<tr><td><code>\/tmp<\/code><\/td><td>Temporary files, often cleared on reboot<\/td><td>Scratch files in a pipeline<\/td><\/tr>\n<tr><td><code>\/opt<\/code>, <code>\/srv<\/code><\/td><td>Extra software and served data<\/td><td>A common place to deploy an app<\/td><\/tr>\n<\/table><\/div>\n<p>A path that starts with <code>\/<\/code> is <strong>absolute<\/strong> (it works from anywhere). A path without it, like <code>logs\/app.log<\/code>, is <strong>relative<\/strong> to the folder you are in now. Many \u201cfile not found\u201d errors in CI happen because a script was run from a different folder than you expected.<\/p>\n\n<h2 id=\"files\">Essential Linux commands for DevOps: files and folders<\/h2>\n<p>Let us create a small project layout, write a config file and move things around. Click <strong>\u25b6 Run<\/strong> to see the real output of each command.<\/p>\n<div class=\"dd-vsc\" role=\"group\" aria-label=\"Code editor: files.sh\">\n<input type=\"radio\" name=\"v1\" id=\"v1f1\" class=\"f1\" checked>\n<input type=\"checkbox\" id=\"v1r1\" class=\"r1\">\n<div class=\"dd-vsc-bar\"><span class=\"dd-dots\"><i><\/i><i><\/i><i><\/i><\/span><span class=\"dd-vsc-title\">files.sh \u2014 devops-part2 \u2014 Visual Studio Code<\/span><\/div>\n<div class=\"dd-vsc-tabs\">\n<label for=\"v1f1\" class=\"t1\"><b class=\"ic sh\">$_<\/b>files.sh<\/label>\n<label for=\"v1r1\" class=\"run run1\" title=\"Show the output in the terminal\">\u25b6 Run<\/label>\n<\/div>\n<div class=\"dd-vsc-pane p1\">\n<div class=\"dd-vsc-crumbs\">devops-part2 \u203a files.sh<\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\"># Where am I? Then create a small project layout<\/span><\/span>\n<span class=\"l\"><span class=\"nb\">pwd<\/span><\/span>\n<span class=\"l\">mkdir -p myapp\/config myapp\/logs<\/span>\n<span class=\"l\"><span class=\"nb\">cd<\/span> myapp<\/span>\n<span class=\"l\">touch app.log<\/span>\n<span class=\"l\"><span class=\"nb\">echo<\/span> <span class=\"s\">\"PORT=8080\"<\/span> &gt; config\/app.env<\/span>\n<span class=\"l\"><span class=\"nb\">echo<\/span> <span class=\"s\">\"MODE=production\"<\/span> &gt;&gt; config\/app.env<\/span>\n<span class=\"l\">cat config\/app.env<\/span>\n<span class=\"l\">cp config\/app.env config\/app.env.bak<\/span>\n<span class=\"l\">mv app.log logs\/<\/span>\n<span class=\"l\">ls -R<\/span><\/code><\/pre>\n<div class=\"dd-vsc-term\"><div class=\"dd-term-head\"><span>TERMINAL<\/span><span>OUTPUT<\/span><span>PROBLEMS<\/span><span class=\"dd-term-note\">Real output (bash 5.2, Ubuntu)<\/span><\/div><pre><code><span class=\"gp\">$<\/span> pwd\n\/home\/dev\n<span class=\"gp\">$<\/span> mkdir -p myapp\/config myapp\/logs\n<span class=\"gp\">$<\/span> cd myapp\n<span class=\"gp\">$<\/span> touch app.log\n<span class=\"gp\">$<\/span> echo \"PORT=8080\" &gt; config\/app.env\n<span class=\"gp\">$<\/span> echo \"MODE=production\" &gt;&gt; config\/app.env\n<span class=\"gp\">$<\/span> cat config\/app.env\nPORT=8080\nMODE=production\n<span class=\"gp\">$<\/span> cp config\/app.env config\/app.env.bak\n<span class=\"gp\">$<\/span> mv app.log logs\/\n<span class=\"gp\">$<\/span> ls -R\n.:\nconfig\nlogs\n\n.\/config:\napp.env\napp.env.bak\n\n.\/logs:\napp.log<\/code><\/pre><\/div>\n<div class=\"dd-vsc-status\"><span>\u2387 main<\/span><span>Ln 11, Col 1<\/span><span class=\"sp\"><\/span><span>Spaces: 2<\/span><span>UTF-8<\/span><span>LF<\/span><span>Shell Script<\/span><\/div>\n<\/div>\n<\/div>\n<p class=\"dd-vsc-hint\">Click \u25b6 Run to open the terminal panel. Every line starting with <code>$<\/code> is a command; the lines below it are what the shell printed.<\/p>\n<p>Notice the difference between <code>&gt;<\/code> and <code>&gt;&gt;<\/code>: the first one creates or <em>overwrites<\/em> a file, the second one <em>adds<\/em> a line at the end. Overwriting a config file by mistake with <code>&gt;<\/code> is a classic beginner bug, so slow down when you type it.<\/p>\n<p>Here is a cheat sheet of the commands you will use most. Click a tab to switch the group:<\/p>\n<div class=\"dd-tabs\" role=\"group\" aria-label=\"Linux command cheat sheet\">\n<input type=\"radio\" name=\"ddt\" id=\"ti1\" checked><input type=\"radio\" name=\"ddt\" id=\"ti2\"><input type=\"radio\" name=\"ddt\" id=\"ti3\"><input type=\"radio\" name=\"ddt\" id=\"ti4\">\n<div class=\"labels\"><label for=\"ti1\">Files &amp; folders<\/label><label for=\"ti2\">Permissions &amp; users<\/label><label for=\"ti3\">Text &amp; logs<\/label><label for=\"ti4\">Processes &amp; network<\/label><\/div>\n<div class=\"panel p1\"><div class=\"dd-tablewrap\"><table><tr><th>Command<\/th><th>What it does<\/th><\/tr>\n<tr><td><code>pwd<\/code><\/td><td>Print the folder you are in<\/td><\/tr>\n<tr><td><code>ls -la<\/code><\/td><td>List files, including hidden ones (names starting with a dot), with details<\/td><\/tr>\n<tr><td><code>cd \/var\/log<\/code>, <code>cd ..<\/code>, <code>cd ~<\/code><\/td><td>Change folder: absolute path, one level up, your home folder<\/td><\/tr>\n<tr><td><code>mkdir -p a\/b\/c<\/code><\/td><td>Create folders, including missing parents, without errors if they exist<\/td><\/tr>\n<tr><td><code>cp -r src dest<\/code> \/ <code>mv old new<\/code><\/td><td>Copy (recursively for folders) \/ move or rename<\/td><\/tr>\n<tr><td><code>rm file<\/code> \/ <code>rm -r folder<\/code><\/td><td>Delete permanently \u2013 there is no recycle bin<\/td><\/tr>\n<tr><td><code>find . -name \"*.log\"<\/code><\/td><td>Find files by name below the current folder<\/td><\/tr><\/table><\/div><\/div>\n<div class=\"panel p2\"><div class=\"dd-tablewrap\"><table><tr><th>Command<\/th><th>What it does<\/th><\/tr>\n<tr><td><code>chmod +x deploy.sh<\/code><\/td><td>Make a script executable<\/td><\/tr>\n<tr><td><code>chmod 644 file<\/code> \/ <code>chmod 600 .env<\/code><\/td><td>Owner can write, everyone can read \/ only the owner can read and write<\/td><\/tr>\n<tr><td><code>chown app:app \/srv\/app<\/code><\/td><td>Change the owner and group of a file or folder<\/td><\/tr>\n<tr><td><code>whoami<\/code>, <code>id<\/code><\/td><td>Show your user name, user ID and groups<\/td><\/tr>\n<tr><td><code>sudo command<\/code><\/td><td>Run one command as the administrator (root)<\/td><\/tr>\n<tr><td><code>stat file<\/code><\/td><td>Show permissions, owner, size and times of a file<\/td><\/tr><\/table><\/div><\/div>\n<div class=\"panel p3\"><div class=\"dd-tablewrap\"><table><tr><th>Command<\/th><th>What it does<\/th><\/tr>\n<tr><td><code>cat<\/code>, <code>less<\/code><\/td><td>Print a file \/ scroll through a long file (press q to quit)<\/td><\/tr>\n<tr><td><code>head -n 20<\/code>, <code>tail -n 20<\/code>, <code>tail -f<\/code><\/td><td>First lines, last lines, follow a growing log live<\/td><\/tr>\n<tr><td><code>grep -i \"error\" app.log<\/code><\/td><td>Show lines that match a word (-i ignores case, -c counts, -r searches folders)<\/td><\/tr>\n<tr><td><code>awk '{print $1}'<\/code><\/td><td>Print a column (fields are split by spaces)<\/td><\/tr>\n<tr><td><code>sort | uniq -c<\/code><\/td><td>Group equal lines and count them<\/td><\/tr>\n<tr><td><code>wc -l<\/code><\/td><td>Count lines<\/td><\/tr>\n<tr><td><code>sed 's\/old\/new\/g'<\/code><\/td><td>Find and replace text in a stream or file<\/td><\/tr><\/table><\/div><\/div>\n<div class=\"panel p4\"><div class=\"dd-tablewrap\"><table><tr><th>Command<\/th><th>What it does<\/th><\/tr>\n<tr><td><code>ps aux<\/code>, <code>top<\/code> \/ <code>htop<\/code><\/td><td>List running processes \/ live CPU and memory view<\/td><\/tr>\n<tr><td><code>kill PID<\/code>, <code>kill -9 PID<\/code><\/td><td>Ask a process to stop \/ force it to stop (last resort)<\/td><\/tr>\n<tr><td><code>systemctl status|restart name<\/code><\/td><td>Check or restart a background service<\/td><\/tr>\n<tr><td><code>journalctl -u name -f<\/code><\/td><td>Follow the logs of a service<\/td><\/tr>\n<tr><td><code>curl -I https:\/\/site<\/code><\/td><td>Call a URL and show only the response headers<\/td><\/tr>\n<tr><td><code>ss -tlnp<\/code><\/td><td>Show which ports are open and which program listens<\/td><\/tr>\n<tr><td><code>df -h<\/code>, <code>free -h<\/code><\/td><td>Free disk space \/ free memory, in human-readable units<\/td><\/tr><\/table><\/div><\/div>\n<\/div>\n<div class=\"dd-warn\"><strong>Careful with <code>rm<\/code>.<\/strong> Linux has no recycle bin. <code>rm -rf<\/code> deletes everything inside a folder without asking. In scripts, never run <code>rm -rf \"$DIR\/\"<\/code> without checking that <code>$DIR<\/code> is not empty first \u2013 <code>set -u<\/code> (see below) helps protect you.<\/div>\n<\/div>\n\n\n\n<div class=\"dd-post\">\n<h2 id=\"permissions\">File permissions: read, write and execute<\/h2>\n<p>Every file on Linux has an <strong>owner<\/strong>, a <strong>group<\/strong> and a set of permissions for three kinds of people: the owner, members of the group, and everyone else (\u201cothers\u201d). Each of them can have <strong>r<\/strong> (read), <strong>w<\/strong> (write) and <strong>x<\/strong> (execute) permission. You see them as a 10-character string like <code>-rwxr-xr-x<\/code>, or as a 3-digit number like <code>755<\/code>.<\/p>\n<figure>\n<svg viewBox=\"0 0 1000 330\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" role=\"img\" aria-label=\"Diagram: how to read the permission string -rwxr-xr-x. The first character is the file type, then three groups of read, write and execute for owner, group and others, which add up to 7, 5 and 5, or 755\">\n<rect width=\"1000\" height=\"330\" fill=\"#0f1430\"\/>\n<text x=\"500\" y=\"40\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"22\" font-weight=\"700\" fill=\"#fff\">Reading -rwxr-xr-x (755)<\/text>\n<g font-family=\"Consolas,Menlo,monospace\" font-size=\"54\" font-weight=\"700\" text-anchor=\"middle\">\n<text x=\"190\" y=\"130\" fill=\"#8f9bd6\">&#8211;<\/text>\n<text x=\"300\" y=\"130\" fill=\"#89e051\">r<\/text><text x=\"350\" y=\"130\" fill=\"#febc2e\">w<\/text><text x=\"400\" y=\"130\" fill=\"#ff7b72\">x<\/text>\n<text x=\"500\" y=\"130\" fill=\"#89e051\">r<\/text><text x=\"550\" y=\"130\" fill=\"#8f9bd6\">&#8211;<\/text><text x=\"600\" y=\"130\" fill=\"#ff7b72\">x<\/text>\n<text x=\"700\" y=\"130\" fill=\"#89e051\">r<\/text><text x=\"750\" y=\"130\" fill=\"#8f9bd6\">&#8211;<\/text><text x=\"800\" y=\"130\" fill=\"#ff7b72\">x<\/text>\n<\/g>\n<g font-family=\"Segoe UI,Arial,sans-serif\" text-anchor=\"middle\">\n<rect x=\"160\" y=\"150\" width=\"60\" height=\"6\" rx=\"3\" fill=\"#8f9bd6\"\/><text x=\"190\" y=\"185\" font-size=\"15\" fill=\"#c9d1ff\">type<\/text><text x=\"190\" y=\"205\" font-size=\"13\" fill=\"#8f9bd6\">&#8211; file, d folder<\/text>\n<rect x=\"275\" y=\"150\" width=\"150\" height=\"6\" rx=\"3\" fill=\"#3178c6\"\/><text x=\"350\" y=\"185\" font-size=\"17\" font-weight=\"700\" fill=\"#fff\">owner (u)<\/text><text x=\"350\" y=\"210\" font-size=\"15\" fill=\"#c9d1ff\">4 + 2 + 1 = 7<\/text>\n<rect x=\"475\" y=\"150\" width=\"150\" height=\"6\" rx=\"3\" fill=\"#7c5cff\"\/><text x=\"550\" y=\"185\" font-size=\"17\" font-weight=\"700\" fill=\"#fff\">group (g)<\/text><text x=\"550\" y=\"210\" font-size=\"15\" fill=\"#c9d1ff\">4 + 0 + 1 = 5<\/text>\n<rect x=\"675\" y=\"150\" width=\"150\" height=\"6\" rx=\"3\" fill=\"#61dafb\"\/><text x=\"750\" y=\"185\" font-size=\"17\" font-weight=\"700\" fill=\"#fff\">others (o)<\/text><text x=\"750\" y=\"210\" font-size=\"15\" fill=\"#c9d1ff\">4 + 0 + 1 = 5<\/text>\n<rect x=\"190\" y=\"245\" width=\"620\" height=\"54\" rx=\"12\" fill=\"#1b2250\" stroke=\"#2a3366\"\/>\n<text x=\"500\" y=\"279\" font-size=\"17\" fill=\"#fff\"><tspan fill=\"#89e051\" font-weight=\"700\">r = 4<\/tspan> read    <tspan fill=\"#febc2e\" font-weight=\"700\">w = 2<\/tspan> write    <tspan fill=\"#ff7b72\" font-weight=\"700\">x = 1<\/tspan> execute (run \/ enter folder)<\/text>\n<\/g>\n<\/svg>\n<figcaption>Figure 2: Each group of three letters is one number. Add r=4, w=2 and x=1 to get 7, 5 and 5, so <code>chmod 755<\/code> gives exactly this permission.<\/figcaption>\n<\/figure>\n<p>Now let us see the most famous DevOps error and how to fix it:<\/p>\n<div class=\"dd-vsc\" role=\"group\" aria-label=\"Code editor: permissions.sh\">\n<input type=\"radio\" name=\"v2\" id=\"v2f1\" class=\"f1\" checked>\n<input type=\"checkbox\" id=\"v2r1\" class=\"r1\">\n<div class=\"dd-vsc-bar\"><span class=\"dd-dots\"><i><\/i><i><\/i><i><\/i><\/span><span class=\"dd-vsc-title\">permissions.sh \u2014 devops-part2 \u2014 Visual Studio Code<\/span><\/div>\n<div class=\"dd-vsc-tabs\">\n<label for=\"v2f1\" class=\"t1\"><b class=\"ic sh\">$_<\/b>permissions.sh<\/label>\n<label for=\"v2r1\" class=\"run run1\" title=\"Show the output in the terminal\">\u25b6 Run<\/label>\n<\/div>\n<div class=\"dd-vsc-pane p1\">\n<div class=\"dd-vsc-crumbs\">devops-part2 \u203a permissions.sh<\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\"># Create a tiny deploy script and try to run it<\/span><\/span>\n<span class=\"l\"><span class=\"nb\">printf<\/span> <span class=\"s\">'#!\/usr\/bin\/env bash\\necho \"Deploying...\"\\n'<\/span> &gt; deploy.sh<\/span>\n<span class=\"l\">stat -c <span class=\"s\">'%A %a %n'<\/span> deploy.sh<\/span>\n<span class=\"l\">.\/deploy.sh<\/span>\n<span class=\"l\"><span class=\"c\"># Give the owner execute permission, then run again<\/span><\/span>\n<span class=\"l\">chmod +x deploy.sh<\/span>\n<span class=\"l\">stat -c <span class=\"s\">'%A %a %n'<\/span> deploy.sh<\/span>\n<span class=\"l\">.\/deploy.sh<\/span>\n<span class=\"l\"><span class=\"c\"># Secrets: only the owner may read or write<\/span><\/span>\n<span class=\"l\"><span class=\"nb\">echo<\/span> <span class=\"s\">\"DB_PASSWORD=change-me\"<\/span> &gt; .env<\/span>\n<span class=\"l\">chmod <span class=\"m\">600<\/span> .env<\/span>\n<span class=\"l\">stat -c <span class=\"s\">'%A %a %n'<\/span> .env<\/span><\/code><\/pre>\n<div class=\"dd-vsc-term\"><div class=\"dd-term-head\"><span>TERMINAL<\/span><span>OUTPUT<\/span><span>PROBLEMS<\/span><span class=\"dd-term-note\">Real output (bash 5.2, GNU coreutils)<\/span><\/div><pre><code><span class=\"gp\">$<\/span> printf '#!\/usr\/bin\/env bash\\necho \"Deploying...\"\\n' &gt; deploy.sh\n<span class=\"gp\">$<\/span> stat -c '%A %a %n' deploy.sh\n-rw-r--r-- 644 deploy.sh\n<span class=\"gp\">$<\/span> .\/deploy.sh\nbash: .\/deploy.sh: Permission denied\n<span class=\"gp\">$<\/span> chmod +x deploy.sh\n<span class=\"gp\">$<\/span> stat -c '%A %a %n' deploy.sh\n-rwxr-xr-x 755 deploy.sh\n<span class=\"gp\">$<\/span> .\/deploy.sh\nDeploying...\n<span class=\"gp\">$<\/span> echo \"DB_PASSWORD=change-me\" &gt; .env\n<span class=\"gp\">$<\/span> chmod 600 .env\n<span class=\"gp\">$<\/span> stat -c '%A %a %n' .env\n-rw------- 600 .env<\/code><\/pre><\/div>\n<div class=\"dd-vsc-status\"><span>\u2387 main<\/span><span>Ln 12, Col 1<\/span><span class=\"sp\"><\/span><span>Spaces: 2<\/span><span>UTF-8<\/span><span>LF<\/span><span>Shell Script<\/span><\/div>\n<\/div>\n<\/div>\n<p class=\"dd-vsc-hint\">Click \u25b6 Run. The first <code>.\/deploy.sh<\/code> fails with \u201cPermission denied\u201d because new files are not executable; after <code>chmod +x<\/code> it works.<\/p>\n<p>Three permission numbers cover almost every case:<\/p>\n<div class=\"dd-tablewrap\"><table>\n<tr><th>Number<\/th><th>String<\/th><th>Use it for<\/th><\/tr>\n<tr><td><code>755<\/code><\/td><td><code>rwxr-xr-x<\/code><\/td><td>Scripts and folders: everyone can read and run, only the owner can change<\/td><\/tr>\n<tr><td><code>644<\/code><\/td><td><code>rw-r--r--<\/code><\/td><td>Normal files such as code and config<\/td><\/tr>\n<tr><td><code>600<\/code><\/td><td><code>rw-------<\/code><\/td><td>Secrets: <code>.env<\/code> files, SSH private keys (SSH refuses keys that others can read)<\/td><\/tr>\n<\/table><\/div>\n<div class=\"dd-tip\"><strong>Pipeline tip:<\/strong> Git stores the execute bit. Run <code>chmod +x script.sh<\/code> and commit, or use <code>git update-index --chmod=+x script.sh<\/code> on Windows. Then your CI step can run <code>.\/script.sh<\/code> without a separate chmod step.<\/div>\n<p>The special user <strong>root<\/strong> can do anything. Instead of logging in as root, you run single commands with <code>sudo<\/code>. On GitHub-hosted runners the <code>runner<\/code> user can use <code>sudo<\/code> without a password, which is handy for <code>sudo apt-get install<\/code>. Inside Docker containers you should do the opposite and run your app as a normal user \u2013 we will do that in Part 7.<\/p>\n\n<h2 id=\"pipes\">Pipes, redirection and reading logs<\/h2>\n<p>Linux tools are small and do one thing well. The <strong>pipe<\/strong> <code>|<\/code> connects them: the output of the left command becomes the input of the right command. Together with <strong>redirection<\/strong> you can turn a messy log file into a clear report in one line.<\/p>\n<div class=\"dd-tablewrap\"><table>\n<tr><th>Symbol<\/th><th>Meaning<\/th><th>Example<\/th><\/tr>\n<tr><td><code>|<\/code><\/td><td>Send output to the next command<\/td><td><code>cat app.log | grep ERROR<\/code><\/td><\/tr>\n<tr><td><code>&gt;<\/code><\/td><td>Write output to a file (overwrite)<\/td><td><code>npm test &gt; result.txt<\/code><\/td><\/tr>\n<tr><td><code>&gt;&gt;<\/code><\/td><td>Append output to a file<\/td><td><code>echo \"done\" &gt;&gt; deploy.log<\/code><\/td><\/tr>\n<tr><td><code>2&gt;<\/code><\/td><td>Write <em>errors<\/em> (stderr) to a file<\/td><td><code>build.sh 2&gt; errors.txt<\/code><\/td><\/tr>\n<tr><td><code>2&gt;&amp;1<\/code><\/td><td>Send errors to the same place as normal output<\/td><td><code>build.sh &gt; all.log 2&gt;&amp;1<\/code><\/td><\/tr>\n<\/table><\/div>\n<p>We will use this small web server log for the rest of the post (save it as <code>logs\/access.log<\/code>). The 9th column is the HTTP status code:<\/p>\n<div class=\"dd-vsc\" role=\"group\" aria-label=\"Code editor: access.log\">\n<input type=\"radio\" name=\"v7\" id=\"v7f1\" class=\"f1\" checked>\n<div class=\"dd-vsc-bar\"><span class=\"dd-dots\"><i><\/i><i><\/i><i><\/i><\/span><span class=\"dd-vsc-title\">access.log \u2014 devops-part2 \u2014 Visual Studio Code<\/span><\/div>\n<div class=\"dd-vsc-tabs\">\n<label for=\"v7f1\" class=\"t1\"><b class=\"ic sh\">$_<\/b>access.log<\/label>\n<\/div>\n<div class=\"dd-vsc-pane p1\">\n<div class=\"dd-vsc-crumbs\">devops-part2 \u203a logs \u203a access.log<\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\"># logs\/access.log - sample web server log used in this part<\/span><\/span>\n<span class=\"l\"><span class=\"m\">10<\/span>.0.0.5 - - [<span class=\"m\">28<\/span>\/Oct\/2026:10:01:12 +0530] <span class=\"s\">\"GET \/ HTTP\/1.1\"<\/span> <span class=\"m\">200<\/span> <span class=\"m\">512<\/span><\/span>\n<span class=\"l\"><span class=\"m\">10<\/span>.0.0.7 - - [<span class=\"m\">28<\/span>\/Oct\/2026:10:01:15 +0530] <span class=\"s\">\"GET \/api\/products HTTP\/1.1\"<\/span> <span class=\"m\">200<\/span> <span class=\"m\">2048<\/span><\/span>\n<span class=\"l\"><span class=\"m\">10<\/span>.0.0.5 - - [<span class=\"m\">28<\/span>\/Oct\/2026:10:01:19 +0530] <span class=\"s\">\"POST \/api\/cart HTTP\/1.1\"<\/span> <span class=\"m\">201<\/span> <span class=\"m\">128<\/span><\/span>\n<span class=\"l\"><span class=\"m\">10<\/span>.0.0.9 - - [<span class=\"m\">28<\/span>\/Oct\/2026:10:02:03 +0530] <span class=\"s\">\"GET \/api\/products HTTP\/1.1\"<\/span> <span class=\"m\">500<\/span> <span class=\"m\">64<\/span><\/span>\n<span class=\"l\"><span class=\"m\">10<\/span>.0.0.5 - - [<span class=\"m\">28<\/span>\/Oct\/2026:10:02:10 +0530] <span class=\"s\">\"GET \/api\/orders HTTP\/1.1\"<\/span> <span class=\"m\">200<\/span> <span class=\"m\">1024<\/span><\/span>\n<span class=\"l\"><span class=\"m\">10<\/span>.0.0.7 - - [<span class=\"m\">28<\/span>\/Oct\/2026:10:02:44 +0530] <span class=\"s\">\"GET \/login HTTP\/1.1\"<\/span> <span class=\"m\">200<\/span> <span class=\"m\">640<\/span><\/span>\n<span class=\"l\"><span class=\"m\">10<\/span>.0.0.12 - - [<span class=\"m\">28<\/span>\/Oct\/2026:10:03:01 +0530] <span class=\"s\">\"GET \/api\/products HTTP\/1.1\"<\/span> <span class=\"m\">502<\/span> <span class=\"m\">64<\/span><\/span>\n<span class=\"l\"><span class=\"m\">10<\/span>.0.0.5 - - [<span class=\"m\">28<\/span>\/Oct\/2026:10:03:20 +0530] <span class=\"s\">\"GET \/api\/products HTTP\/1.1\"<\/span> <span class=\"m\">200<\/span> <span class=\"m\">2048<\/span><\/span>\n<span class=\"l\"><span class=\"m\">10<\/span>.0.0.7 - - [<span class=\"m\">28<\/span>\/Oct\/2026:10:03:41 +0530] <span class=\"s\">\"GET \/missing HTTP\/1.1\"<\/span> <span class=\"m\">404<\/span> <span class=\"m\">32<\/span><\/span>\n<span class=\"l\"><span class=\"m\">10<\/span>.0.0.9 - - [<span class=\"m\">28<\/span>\/Oct\/2026:10:04:05 +0530] <span class=\"s\">\"GET \/ HTTP\/1.1\"<\/span> <span class=\"m\">200<\/span> <span class=\"m\">512<\/span><\/span><\/code><\/pre>\n<div class=\"dd-vsc-status\"><span>\u2387 main<\/span><span>Ln 11, Col 1<\/span><span class=\"sp\"><\/span><span>Spaces: 2<\/span><span>UTF-8<\/span><span>LF<\/span><span>Shell Script<\/span><\/div>\n<\/div>\n<\/div>\n<figure>\n<svg viewBox=\"0 0 1000 270\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" role=\"img\" aria-label=\"Diagram: a shell pipeline. The access log flows through awk, sort, uniq -c and sort -rn, and each pipe symbol passes one command's output to the next command's input\">\n<rect width=\"1000\" height=\"270\" fill=\"#0f1430\"\/>\n<text x=\"500\" y=\"45\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"22\" font-weight=\"700\" fill=\"#fff\">A pipe | sends one command&#8217;s output into the next<\/text>\n<rect x=\"20\" y=\"95\" width=\"176\" height=\"90\" rx=\"12\" fill=\"#1b2250\" stroke=\"#3178c6\" stroke-width=\"2\"\/><text x=\"108.0\" y=\"135\" text-anchor=\"middle\" font-family=\"Consolas,Menlo,monospace\" font-size=\"16\" font-weight=\"700\" fill=\"#fff\">access.log<\/text><text x=\"108.0\" y=\"162\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"14\" fill=\"#c9d1ff\">10 lines<\/text><text x=\"207\" y=\"148\" text-anchor=\"middle\" font-family=\"Consolas,Menlo,monospace\" font-size=\"26\" font-weight=\"700\" fill=\"#89e051\">|<\/text><rect x=\"218\" y=\"95\" width=\"176\" height=\"90\" rx=\"12\" fill=\"#1b2250\" stroke=\"#3178c6\" stroke-width=\"2\"\/><text x=\"306.0\" y=\"135\" text-anchor=\"middle\" font-family=\"Consolas,Menlo,monospace\" font-size=\"16\" font-weight=\"700\" fill=\"#fff\">awk &#8216;{print $9}&#8217;<\/text><text x=\"306.0\" y=\"162\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"14\" fill=\"#c9d1ff\">status codes<\/text><text x=\"405\" y=\"148\" text-anchor=\"middle\" font-family=\"Consolas,Menlo,monospace\" font-size=\"26\" font-weight=\"700\" fill=\"#89e051\">|<\/text><rect x=\"416\" y=\"95\" width=\"176\" height=\"90\" rx=\"12\" fill=\"#1b2250\" stroke=\"#7c5cff\" stroke-width=\"2\"\/><text x=\"504.0\" y=\"135\" text-anchor=\"middle\" font-family=\"Consolas,Menlo,monospace\" font-size=\"16\" font-weight=\"700\" fill=\"#fff\">sort<\/text><text x=\"504.0\" y=\"162\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"14\" fill=\"#c9d1ff\">group equal<\/text><text x=\"603\" y=\"148\" text-anchor=\"middle\" font-family=\"Consolas,Menlo,monospace\" font-size=\"26\" font-weight=\"700\" fill=\"#89e051\">|<\/text><rect x=\"614\" y=\"95\" width=\"176\" height=\"90\" rx=\"12\" fill=\"#1b2250\" stroke=\"#7c5cff\" stroke-width=\"2\"\/><text x=\"702.0\" y=\"135\" text-anchor=\"middle\" font-family=\"Consolas,Menlo,monospace\" font-size=\"16\" font-weight=\"700\" fill=\"#fff\">uniq -c<\/text><text x=\"702.0\" y=\"162\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"14\" fill=\"#c9d1ff\">count<\/text><text x=\"801\" y=\"148\" text-anchor=\"middle\" font-family=\"Consolas,Menlo,monospace\" font-size=\"26\" font-weight=\"700\" fill=\"#89e051\">|<\/text><rect x=\"812\" y=\"95\" width=\"176\" height=\"90\" rx=\"12\" fill=\"#1b2250\" stroke=\"#61dafb\" stroke-width=\"2\"\/><text x=\"900.0\" y=\"135\" text-anchor=\"middle\" font-family=\"Consolas,Menlo,monospace\" font-size=\"16\" font-weight=\"700\" fill=\"#fff\">sort -rn<\/text><text x=\"900.0\" y=\"162\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"14\" fill=\"#c9d1ff\">biggest first<\/text>\n<path id=\"pp\" d=\"M40 210H960\" stroke=\"#2a3366\" stroke-width=\"2\" stroke-dasharray=\"6 6\"\/>\n<circle r=\"6\" fill=\"#89e051\"><animateMotion dur=\"3.5s\" repeatCount=\"indefinite\"><mpath href=\"#pp\"\/><\/animateMotion><\/circle>\n<text x=\"500\" y=\"245\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"16\" fill=\"#c9d1ff\">Result: &#8220;6 200, 1 502, 1 500 &#8230;&#8221; \u2013 a status-code report in one line of shell<\/text>\n<\/svg>\n<figcaption>Figure 3: Small tools joined with pipes. Each tool does one job; together they turn a raw log into a report.<\/figcaption>\n<\/figure>\n<div class=\"dd-vsc\" role=\"group\" aria-label=\"Code editor: logs.sh\">\n<input type=\"radio\" name=\"v3\" id=\"v3f1\" class=\"f1\" checked>\n<input type=\"checkbox\" id=\"v3r1\" class=\"r1\">\n<div class=\"dd-vsc-bar\"><span class=\"dd-dots\"><i><\/i><i><\/i><i><\/i><\/span><span class=\"dd-vsc-title\">logs.sh \u2014 devops-part2 \u2014 Visual Studio Code<\/span><\/div>\n<div class=\"dd-vsc-tabs\">\n<label for=\"v3f1\" class=\"t1\"><b class=\"ic sh\">$_<\/b>logs.sh<\/label>\n<label for=\"v3r1\" class=\"run run1\" title=\"Show the output in the terminal\">\u25b6 Run<\/label>\n<\/div>\n<div class=\"dd-vsc-pane p1\">\n<div class=\"dd-vsc-crumbs\">devops-part2 \u203a logs.sh<\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\"># Show only server errors (status 500-599)<\/span><\/span>\n<span class=\"l\">grep -E <span class=\"s\">'\" 5[0-9]{2} '<\/span> logs\/access.log<\/span>\n<span class=\"l\"><span class=\"c\"># How many requests hit the products API?<\/span><\/span>\n<span class=\"l\">grep -c <span class=\"s\">\"\/api\/products\"<\/span> logs\/access.log<\/span>\n<span class=\"l\"><span class=\"c\"># Count requests per status code, most common first<\/span><\/span>\n<span class=\"l\">awk <span class=\"s\">'{print $9}'<\/span> logs\/access.log | sort | uniq -c | sort -rn<\/span>\n<span class=\"l\"><span class=\"c\"># The last 2 lines (use tail -f to follow a live log)<\/span><\/span>\n<span class=\"l\">tail -n <span class=\"m\">2<\/span> logs\/access.log<\/span>\n<span class=\"l\"><span class=\"c\"># Save 404s to a file, then read it<\/span><\/span>\n<span class=\"l\">grep <span class=\"s\">\" 404 \"<\/span> logs\/access.log &gt; not-found.txt<\/span>\n<span class=\"l\">cat not-found.txt<\/span><\/code><\/pre>\n<div class=\"dd-vsc-term\"><div class=\"dd-term-head\"><span>TERMINAL<\/span><span>OUTPUT<\/span><span>PROBLEMS<\/span><span class=\"dd-term-note\">Real output (GNU grep, awk, sort)<\/span><\/div><pre><code><span class=\"gp\">$<\/span> grep -E '\" 5[0-9]{2} ' logs\/access.log\n10.0.0.9 - - [28\/Oct\/2026:10:02:03 +0530] \"GET \/api\/products HTTP\/1.1\" 500 64\n10.0.0.12 - - [28\/Oct\/2026:10:03:01 +0530] \"GET \/api\/products HTTP\/1.1\" 502 64\n<span class=\"gp\">$<\/span> grep -c \"\/api\/products\" logs\/access.log\n4\n<span class=\"gp\">$<\/span> awk '{print $9}' logs\/access.log | sort | uniq -c | sort -rn\n      6 200\n      1 502\n      1 500\n      1 404\n      1 201\n<span class=\"gp\">$<\/span> tail -n 2 logs\/access.log\n10.0.0.7 - - [28\/Oct\/2026:10:03:41 +0530] \"GET \/missing HTTP\/1.1\" 404 32\n10.0.0.9 - - [28\/Oct\/2026:10:04:05 +0530] \"GET \/ HTTP\/1.1\" 200 512\n<span class=\"gp\">$<\/span> grep \" 404 \" logs\/access.log &gt; not-found.txt\n<span class=\"gp\">$<\/span> cat not-found.txt\n10.0.0.7 - - [28\/Oct\/2026:10:03:41 +0530] \"GET \/missing HTTP\/1.1\" 404 32<\/code><\/pre><\/div>\n<div class=\"dd-vsc-status\"><span>\u2387 main<\/span><span>Ln 11, Col 1<\/span><span class=\"sp\"><\/span><span>Spaces: 2<\/span><span>UTF-8<\/span><span>LF<\/span><span>Shell Script<\/span><\/div>\n<\/div>\n<\/div>\n<p class=\"dd-vsc-hint\">Click \u25b6 Run to see what each command finds in the sample log.<\/p>\n<p>The <code>awk '{print $9}' | sort | uniq -c | sort -rn<\/code> pattern is worth remembering: change <code>$9<\/code> to <code>$1<\/code> and you get the top IP addresses; change it to <code>$7<\/code> and you get the most requested URLs. On a real server you would point it at <code>\/var\/log\/nginx\/access.log<\/code>, and use <code>tail -f<\/code> to watch new requests arrive live while you test a deployment.<\/p>\n<\/div>\n\n\n\n<div class=\"dd-post\">\n<h2 id=\"env\">Environment variables and exit codes<\/h2>\n<p><strong>Environment variables<\/strong> are named values that a shell passes to every program it starts. Pipelines use them everywhere: <code>NODE_ENV=production<\/code>, <code>DATABASE_URL<\/code>, API keys from secrets, and built-in values such as <code>GITHUB_SHA<\/code> (the commit being built) and <code>CI=true<\/code>. You read a variable with <code>$NAME<\/code> or <code>${NAME}<\/code>, and you can give a default with <code>${NAME:-default}<\/code>.<\/p>\n<p>The second idea is the <strong>exit code<\/strong>. Every command ends with a number: <code>0<\/code> means success and anything from 1 to 255 means failure. <code>$?<\/code> holds the exit code of the last command. This number is how CI tools decide if a step passed \u2013 if any command in a step exits with a non-zero code, the step turns red and the pipeline stops.<\/p>\n<div class=\"dd-vsc\" role=\"group\" aria-label=\"Code editor: env.sh\">\n<input type=\"radio\" name=\"v4\" id=\"v4f1\" class=\"f1\" checked>\n<input type=\"checkbox\" id=\"v4r1\" class=\"r1\">\n<div class=\"dd-vsc-bar\"><span class=\"dd-dots\"><i><\/i><i><\/i><i><\/i><\/span><span class=\"dd-vsc-title\">env.sh \u2014 devops-part2 \u2014 Visual Studio Code<\/span><\/div>\n<div class=\"dd-vsc-tabs\">\n<label for=\"v4f1\" class=\"t1\"><b class=\"ic sh\">$_<\/b>env.sh<\/label>\n<label for=\"v4r1\" class=\"run run1\" title=\"Show the output in the terminal\">\u25b6 Run<\/label>\n<\/div>\n<div class=\"dd-vsc-pane p1\">\n<div class=\"dd-vsc-crumbs\">devops-part2 \u203a env.sh<\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\"># Set a variable for this shell and every program it starts<\/span><\/span>\n<span class=\"l\"><span class=\"nb\">export<\/span> <span class=\"n\">APP_ENV<\/span>=staging<\/span>\n<span class=\"l\"><span class=\"nb\">echo<\/span> <span class=\"s\">\"Deploying to <\/span><span class=\"n\">$APP_ENV<\/span><span class=\"s\">\"<\/span><\/span>\n<span class=\"l\"><span class=\"c\"># Use a default when a variable is not set<\/span><\/span>\n<span class=\"l\"><span class=\"nb\">echo<\/span> <span class=\"s\">\"Port is ${<\/span><span class=\"n\">PORT<\/span><span class=\"k\">:-<\/span><span class=\"n\">3000<\/span><span class=\"s\">}\"<\/span><\/span>\n<span class=\"l\"><span class=\"c\"># Every command returns an exit code: 0 = success<\/span><\/span>\n<span class=\"l\">ls \/does-not-exist; <span class=\"nb\">echo<\/span> <span class=\"s\">\"exit code: <\/span><span class=\"n\">$?<\/span><span class=\"s\">\"<\/span><\/span>\n<span class=\"l\"><span class=\"c\"># &amp;&amp; runs the next command only on success, || only on failure<\/span><\/span>\n<span class=\"l\">mkdir -p build &amp;&amp; <span class=\"nb\">echo<\/span> <span class=\"s\">\"build folder ready\"<\/span><\/span>\n<span class=\"l\"><span class=\"nb\">false<\/span> || <span class=\"nb\">echo<\/span> <span class=\"s\">\"previous command failed, running fallback\"<\/span><\/span><\/code><\/pre>\n<div class=\"dd-vsc-term\"><div class=\"dd-term-head\"><span>TERMINAL<\/span><span>OUTPUT<\/span><span>PROBLEMS<\/span><span class=\"dd-term-note\">Real output (bash 5.2)<\/span><\/div><pre><code><span class=\"gp\">$<\/span> export APP_ENV=staging\n<span class=\"gp\">$<\/span> echo \"Deploying to $APP_ENV\"\nDeploying to staging\n<span class=\"gp\">$<\/span> echo \"Port is ${PORT:-3000}\"\nPort is 3000\n<span class=\"gp\">$<\/span> ls \/does-not-exist; echo \"exit code: $?\"\nls: cannot access '\/does-not-exist': No such file or directory\nexit code: 2\n<span class=\"gp\">$<\/span> mkdir -p build &amp;&amp; echo \"build folder ready\"\nbuild folder ready\n<span class=\"gp\">$<\/span> false || echo \"previous command failed, running fallback\"\nprevious command failed, running fallback<\/code><\/pre><\/div>\n<div class=\"dd-vsc-status\"><span>\u2387 main<\/span><span>Ln 10, Col 1<\/span><span class=\"sp\"><\/span><span>Spaces: 2<\/span><span>UTF-8<\/span><span>LF<\/span><span>Shell Script<\/span><\/div>\n<\/div>\n<\/div>\n<p class=\"dd-vsc-hint\">Click \u25b6 Run. Notice <code>exit code: 2<\/code> after the failing <code>ls<\/code>, and how <code>&amp;&amp;<\/code> and <code>||<\/code> choose what runs next.<\/p>\n<div class=\"dd-note\"><strong>Two useful rules:<\/strong> variables you set without <code>export<\/code> are only visible in the current shell, not in programs it starts. And each <code>run:<\/code> step in GitHub Actions starts a <em>new<\/em> shell, so a plain <code>export<\/code> does not carry over to the next step \u2013 write <code>echo \"NAME=value\" &gt;&gt; \"$GITHUB_ENV\"<\/code> instead. We will go deeper into configuration and secrets in Part 11.<\/div>\n\n<h2 id=\"processes\">Processes and services<\/h2>\n<p>A <strong>process<\/strong> is a running program. Each one has a number called a <strong>PID<\/strong>. You need a few commands to see what is running, run things in the background and stop them:<\/p>\n<div class=\"dd-vsc\" role=\"group\" aria-label=\"Code editor: processes.sh\">\n<input type=\"radio\" name=\"v5\" id=\"v5f1\" class=\"f1\" checked>\n<input type=\"checkbox\" id=\"v5r1\" class=\"r1\">\n<div class=\"dd-vsc-bar\"><span class=\"dd-dots\"><i><\/i><i><\/i><i><\/i><\/span><span class=\"dd-vsc-title\">processes.sh \u2014 devops-part2 \u2014 Visual Studio Code<\/span><\/div>\n<div class=\"dd-vsc-tabs\">\n<label for=\"v5f1\" class=\"t1\"><b class=\"ic sh\">$_<\/b>processes.sh<\/label>\n<label for=\"v5r1\" class=\"run run1\" title=\"Show the output in the terminal\">\u25b6 Run<\/label>\n<\/div>\n<div class=\"dd-vsc-pane p1\">\n<div class=\"dd-vsc-crumbs\">devops-part2 \u203a processes.sh<\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\"># List processes (PID = process ID, COMMAND = program name)<\/span><\/span>\n<span class=\"l\">ps -eo pid,comm | head -3<\/span>\n<span class=\"l\"><span class=\"c\"># Start a long command in the background with &amp;<\/span><\/span>\n<span class=\"l\">sleep <span class=\"m\">300<\/span> &amp;<\/span>\n<span class=\"l\"><span class=\"c\"># See background jobs of this shell, then stop job 1<\/span><\/span>\n<span class=\"l\"><span class=\"nb\">jobs<\/span><\/span>\n<span class=\"l\"><span class=\"nb\">kill<\/span> %1<\/span><\/code><\/pre>\n<div class=\"dd-vsc-term\"><div class=\"dd-term-head\"><span>TERMINAL<\/span><span>OUTPUT<\/span><span>PROBLEMS<\/span><span class=\"dd-term-note\">Example output from a cloud sandbox \u2013 PIDs and names differ on your machine<\/span><\/div><pre><code><span class=\"gp\">$<\/span> ps -eo pid,comm | head -3\n  PID COMMAND\n    1 process_api\n    2 kthreadd\n<span class=\"gp\">$<\/span> sleep 300 &amp;\n[1] 381\n<span class=\"gp\">$<\/span> jobs\n[1]+  Running                 sleep 300 &amp;\n<span class=\"gp\">$<\/span> kill %1\n[1]+  Terminated              sleep 300<\/code><\/pre><\/div>\n<div class=\"dd-vsc-status\"><span>\u2387 main<\/span><span>Ln 7, Col 1<\/span><span class=\"sp\"><\/span><span>Spaces: 2<\/span><span>UTF-8<\/span><span>LF<\/span><span>Shell Script<\/span><\/div>\n<\/div>\n<\/div>\n<p class=\"dd-vsc-hint\">Click \u25b6 Run to see the example output. Use <code>kill PID<\/code> first and keep <code>kill -9<\/code> as a last resort, because it gives the program no chance to clean up.<\/p>\n<p>On servers, long-running programs such as web servers and databases run as <strong>services<\/strong>, managed by <strong>systemd<\/strong>. You will not need these inside a CI runner, but you will need them the first time you log in to a server to check why a site is down:<\/p>\n<div class=\"dd-vsc dd-vsc-termonly\" role=\"group\" aria-label=\"Terminal: server \u2014 bash\">\n<div class=\"dd-vsc-bar\"><span class=\"dd-dots\"><i><\/i><i><\/i><i><\/i><\/span><span class=\"dd-vsc-title\">server \u2014 bash<\/span><\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\"># On a Linux server that uses systemd (Ubuntu, Debian, RHEL...)<\/span><\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> sudo systemctl status nginx      # is the web server running?<\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> sudo systemctl restart nginx     # restart after a config change<\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> sudo journalctl -u nginx -n 50   # last 50 log lines of the service<\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> ss -tlnp                          # which programs listen on which ports<\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> df -h &amp;&amp; free -h                  # disk space and memory<\/span><\/code><\/pre>\n<\/div>\n<\/div>\n\n\n\n<div class=\"dd-post\">\n<h2 id=\"scripts\">Your first DevOps shell scripts<\/h2>\n<p>A <strong>shell script<\/strong> is a text file full of commands that run from top to bottom. Let us write two scripts that real teams use in pipelines:<\/p>\n<ol>\n<li><strong>log-report.sh<\/strong> \u2013 reads an access log, prints a summary and <em>fails<\/em> (exit code 1) if too many requests were server errors. You could run it after a canary deployment to decide whether to continue.<\/li>\n<li><strong>healthcheck.sh<\/strong> \u2013 waits until a URL answers with HTTP 200, trying a few times. You run it right after a deploy, or before end-to-end tests.<\/li>\n<\/ol>\n<p>The third tab shows a GitHub Actions workflow that runs both scripts on every push, just like the pipeline you built in Part 1.<\/p>\n<div class=\"dd-vsc\" role=\"group\" aria-label=\"Code editor: log-report.sh, healthcheck.sh, scripts.yml\">\n<input type=\"radio\" name=\"v6\" id=\"v6f1\" class=\"f1\" checked>\n<input type=\"radio\" name=\"v6\" id=\"v6f2\" class=\"f2\">\n<input type=\"radio\" name=\"v6\" id=\"v6f3\" class=\"f3\">\n<input type=\"checkbox\" id=\"v6r1\" class=\"r1\">\n<input type=\"checkbox\" id=\"v6r2\" class=\"r2\">\n<div class=\"dd-vsc-bar\"><span class=\"dd-dots\"><i><\/i><i><\/i><i><\/i><\/span><span class=\"dd-vsc-title\">3 files \u2014 devops-part2 \u2014 Visual Studio Code<\/span><\/div>\n<div class=\"dd-vsc-tabs\">\n<label for=\"v6f1\" class=\"t1\"><b class=\"ic sh\">$_<\/b>log-report.sh<\/label>\n<label for=\"v6f2\" class=\"t2\"><b class=\"ic sh\">$_<\/b>healthcheck.sh<\/label>\n<label for=\"v6f3\" class=\"t3\"><b class=\"ic yml\">YML<\/b>scripts.yml<\/label>\n<label for=\"v6r1\" class=\"run run1\" title=\"Show the output in the terminal\">\u25b6 Run<\/label>\n<label for=\"v6r2\" class=\"run run2\" title=\"Show the output in the terminal\">\u25b6 Run<\/label>\n<\/div>\n<div class=\"dd-vsc-pane p1\">\n<div class=\"dd-vsc-crumbs\">devops-part2 \u203a log-report.sh<\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\">#!\/usr\/bin\/env bash<\/span><\/span>\n<span class=\"l\"><span class=\"c\"># log-report.sh - summarise a web server access log and fail on too many errors.<\/span><\/span>\n<span class=\"l\"><span class=\"c\"># Usage: .\/log-report.sh logs\/access.log<\/span><\/span>\n<span class=\"l\"><span class=\"nb\">set<\/span> -euo pipefail<\/span>\n<span class=\"l\"><\/span>\n<span class=\"l\"><span class=\"n\">LOG_FILE<\/span>=<span class=\"s\">\"${<\/span><span class=\"n\">1<\/span>:?Usage: <span class=\"n\">$0<\/span> &lt;access-log-file&gt;<span class=\"s\">}\"<\/span><\/span>\n<span class=\"l\"><span class=\"n\">MAX_ERROR_PCT<\/span>=<span class=\"s\">\"${<\/span><span class=\"n\">MAX_ERROR_PCT<\/span><span class=\"k\">:-<\/span><span class=\"n\">25<\/span><span class=\"s\">}\"<\/span>   <span class=\"c\"># override with an environment variable<\/span><\/span>\n<span class=\"l\"><\/span>\n<span class=\"l\"><span class=\"k\">if<\/span> [[ ! -f <span class=\"s\">\"<\/span><span class=\"n\">$LOG_FILE<\/span><span class=\"s\">\"<\/span> ]]; <span class=\"k\">then<\/span><\/span>\n<span class=\"l\">  <span class=\"nb\">echo<\/span> <span class=\"s\">\"ERROR: file not found: <\/span><span class=\"n\">$LOG_FILE<\/span><span class=\"s\">\"<\/span> &gt;&amp;<span class=\"m\">2<\/span><\/span>\n<span class=\"l\">  <span class=\"nb\">exit<\/span> <span class=\"m\">2<\/span><\/span>\n<span class=\"l\"><span class=\"k\">fi<\/span><\/span>\n<span class=\"l\"><\/span>\n<span class=\"l\"><span class=\"n\">total<\/span>=<span class=\"k\">$(<\/span>wc -l &lt; <span class=\"s\">\"<\/span><span class=\"n\">$LOG_FILE<\/span><span class=\"s\">\"<\/span><span class=\"k\">)<\/span><\/span>\n<span class=\"l\"><span class=\"n\">errors<\/span>=<span class=\"k\">$(<\/span>awk <span class=\"s\">'$9 &gt;= 500'<\/span> <span class=\"s\">\"<\/span><span class=\"n\">$LOG_FILE<\/span><span class=\"s\">\"<\/span> | wc -l<span class=\"k\">)<\/span><\/span>\n<span class=\"l\"><span class=\"n\">error_pct<\/span>=<span class=\"k\">$((<\/span> <span class=\"n\">errors<\/span> * <span class=\"m\">100<\/span> \/ <span class=\"n\">total<\/span> <span class=\"k\">))<\/span><\/span>\n<span class=\"l\"><\/span>\n<span class=\"l\"><span class=\"nb\">echo<\/span> <span class=\"s\">\"Requests : <\/span><span class=\"n\">$total<\/span><span class=\"s\">\"<\/span><\/span>\n<span class=\"l\"><span class=\"nb\">echo<\/span> <span class=\"s\">\"5xx errors: <\/span><span class=\"n\">$errors<\/span><span class=\"s\"> (<\/span><span class=\"n\">$error_pct<\/span><span class=\"s\">%)\"<\/span><\/span>\n<span class=\"l\"><span class=\"nb\">echo<\/span> <span class=\"s\">\"Top IPs:\"<\/span><\/span>\n<span class=\"l\">awk <span class=\"s\">'{print $1}'<\/span> <span class=\"s\">\"<\/span><span class=\"n\">$LOG_FILE<\/span><span class=\"s\">\"<\/span> | sort | uniq -c | sort -rn | head -3<\/span>\n<span class=\"l\"><\/span>\n<span class=\"l\"><span class=\"k\">if<\/span> (( error_pct &gt; MAX_ERROR_PCT )); <span class=\"k\">then<\/span><\/span>\n<span class=\"l\">  <span class=\"nb\">echo<\/span> <span class=\"s\">\"FAIL: error rate <\/span><span class=\"n\">$error_pct<\/span><span class=\"s\">% is above <\/span><span class=\"n\">$MAX_ERROR_PCT<\/span><span class=\"s\">%\"<\/span> &gt;&amp;<span class=\"m\">2<\/span><\/span>\n<span class=\"l\">  <span class=\"nb\">exit<\/span> <span class=\"m\">1<\/span><\/span>\n<span class=\"l\"><span class=\"k\">fi<\/span><\/span>\n<span class=\"l\"><span class=\"nb\">echo<\/span> <span class=\"s\">\"OK: error rate is within the limit\"<\/span><\/span><\/code><\/pre>\n<div class=\"dd-vsc-term\"><div class=\"dd-term-head\"><span>TERMINAL<\/span><span>OUTPUT<\/span><span>PROBLEMS<\/span><span class=\"dd-term-note\">Real output (bash 5.2)<\/span><\/div><pre><code><span class=\"gp\">$<\/span> .\/log-report.sh logs\/access.log\nRequests : 10\n5xx errors: 2 (20%)\nTop IPs:\n      4 10.0.0.5\n      3 10.0.0.7\n      2 10.0.0.9\nOK: error rate is within the limit\n<span class=\"gp\">$<\/span> MAX_ERROR_PCT=10 .\/log-report.sh logs\/access.log; echo \"exit code: $?\"\nRequests : 10\n5xx errors: 2 (20%)\nTop IPs:\n      4 10.0.0.5\n      3 10.0.0.7\n      2 10.0.0.9\nFAIL: error rate 20% is above 10%\nexit code: 1<\/code><\/pre><\/div>\n<div class=\"dd-vsc-status\"><span>\u2387 main<\/span><span>Ln 27, Col 1<\/span><span class=\"sp\"><\/span><span>Spaces: 2<\/span><span>UTF-8<\/span><span>LF<\/span><span>Shell Script<\/span><\/div>\n<\/div>\n<div class=\"dd-vsc-pane p2\">\n<div class=\"dd-vsc-crumbs\">devops-part2 \u203a healthcheck.sh<\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\">#!\/usr\/bin\/env bash<\/span><\/span>\n<span class=\"l\"><span class=\"c\"># healthcheck.sh - wait until a URL answers with HTTP 200, or give up.<\/span><\/span>\n<span class=\"l\"><span class=\"c\"># Usage: .\/healthcheck.sh http:\/\/localhost:8080\/ [retries] [delay-seconds]<\/span><\/span>\n<span class=\"l\"><span class=\"nb\">set<\/span> -euo pipefail<\/span>\n<span class=\"l\"><\/span>\n<span class=\"l\"><span class=\"n\">url<\/span>=<span class=\"s\">\"${<\/span><span class=\"n\">1<\/span>:?Usage: <span class=\"n\">$0<\/span> &lt;url&gt; [retries] [delay]<span class=\"s\">}\"<\/span><\/span>\n<span class=\"l\"><span class=\"n\">retries<\/span>=<span class=\"s\">\"${<\/span><span class=\"n\">2<\/span><span class=\"k\">:-<\/span><span class=\"n\">5<\/span><span class=\"s\">}\"<\/span><\/span>\n<span class=\"l\"><span class=\"n\">delay<\/span>=<span class=\"s\">\"${<\/span><span class=\"n\">3<\/span><span class=\"k\">:-<\/span><span class=\"n\">2<\/span><span class=\"s\">}\"<\/span><\/span>\n<span class=\"l\"><\/span>\n<span class=\"l\">check() {<\/span>\n<span class=\"l\">  <span class=\"c\"># -s silent, -o discard body, -w print only the status code<\/span><\/span>\n<span class=\"l\">  curl -s -o \/dev\/null -w <span class=\"s\">'%{http_code}'<\/span> --max-time <span class=\"m\">3<\/span> <span class=\"s\">\"<\/span><span class=\"n\">$1<\/span><span class=\"s\">\"<\/span> || <span class=\"nb\">true<\/span><\/span>\n<span class=\"l\">}<\/span>\n<span class=\"l\"><\/span>\n<span class=\"l\"><span class=\"k\">for<\/span> (( <span class=\"n\">i<\/span> = <span class=\"m\">1<\/span>; i &lt;= retries; i++ )); <span class=\"k\">do<\/span><\/span>\n<span class=\"l\">  <span class=\"n\">code<\/span>=<span class=\"k\">$(<\/span>check <span class=\"s\">\"<\/span><span class=\"n\">$url<\/span><span class=\"s\">\"<\/span><span class=\"k\">)<\/span><\/span>\n<span class=\"l\">  <span class=\"nb\">echo<\/span> <span class=\"s\">\"[<\/span><span class=\"n\">$i<\/span><span class=\"s\">\/<\/span><span class=\"n\">$retries<\/span><span class=\"s\">] <\/span><span class=\"n\">$url<\/span><span class=\"s\"> -&gt; HTTP <\/span><span class=\"n\">$code<\/span><span class=\"s\">\"<\/span><\/span>\n<span class=\"l\">  <span class=\"k\">if<\/span> [[ <span class=\"s\">\"<\/span><span class=\"n\">$code<\/span><span class=\"s\">\"<\/span> == <span class=\"s\">\"200\"<\/span> ]]; <span class=\"k\">then<\/span><\/span>\n<span class=\"l\">    <span class=\"nb\">echo<\/span> <span class=\"s\">\"Service is healthy\"<\/span><\/span>\n<span class=\"l\">    <span class=\"nb\">exit<\/span> <span class=\"m\">0<\/span><\/span>\n<span class=\"l\">  <span class=\"k\">fi<\/span><\/span>\n<span class=\"l\">  sleep <span class=\"s\">\"<\/span><span class=\"n\">$delay<\/span><span class=\"s\">\"<\/span><\/span>\n<span class=\"l\"><span class=\"k\">done<\/span><\/span>\n<span class=\"l\"><\/span>\n<span class=\"l\"><span class=\"nb\">echo<\/span> <span class=\"s\">\"Service did not become healthy after <\/span><span class=\"n\">$retries<\/span><span class=\"s\"> tries\"<\/span> &gt;&amp;<span class=\"m\">2<\/span><\/span>\n<span class=\"l\"><span class=\"nb\">exit<\/span> <span class=\"m\">1<\/span><\/span><\/code><\/pre>\n<div class=\"dd-vsc-term\"><div class=\"dd-term-head\"><span>TERMINAL<\/span><span>OUTPUT<\/span><span>PROBLEMS<\/span><span class=\"dd-term-note\">Real output (bash 5.2, curl)<\/span><\/div><pre><code># Terminal 1: python3 -m http.server 8080   (started a few seconds late)\n<span class=\"gp\">$<\/span> .\/healthcheck.sh http:\/\/localhost:8080\/ 5 2\n[1\/5] http:\/\/localhost:8080\/ -&gt; HTTP 000\n[2\/5] http:\/\/localhost:8080\/ -&gt; HTTP 000\n[3\/5] http:\/\/localhost:8080\/ -&gt; HTTP 200\nService is healthy\n<span class=\"gp\">$<\/span> .\/healthcheck.sh http:\/\/localhost:9999\/ 3 1; echo \"exit code: $?\"\n[1\/3] http:\/\/localhost:9999\/ -&gt; HTTP 000\n[2\/3] http:\/\/localhost:9999\/ -&gt; HTTP 000\n[3\/3] http:\/\/localhost:9999\/ -&gt; HTTP 000\nService did not become healthy after 3 tries\nexit code: 1<\/code><\/pre><\/div>\n<div class=\"dd-vsc-status\"><span>\u2387 main<\/span><span>Ln 26, Col 1<\/span><span class=\"sp\"><\/span><span>Spaces: 2<\/span><span>UTF-8<\/span><span>LF<\/span><span>Shell Script<\/span><\/div>\n<\/div>\n<div class=\"dd-vsc-pane p3\">\n<div class=\"dd-vsc-crumbs\">devops-part2 \u203a .github \u203a workflows \u203a scripts.yml<\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\"># .github\/workflows\/scripts.yml - run our shell scripts on every push<\/span><\/span>\n<span class=\"l\"><span class=\"n\">name<\/span>: Script checks<\/span>\n<span class=\"l\"><span class=\"n\">on<\/span>:<\/span>\n<span class=\"l\">  <span class=\"n\">push<\/span>:<\/span>\n<span class=\"l\">    <span class=\"n\">branches<\/span>: [<span class=\"n\">main<\/span>]<\/span>\n<span class=\"l\">  <span class=\"n\">pull_request<\/span>:<\/span>\n<span class=\"l\"><\/span>\n<span class=\"l\"><span class=\"n\">jobs<\/span>:<\/span>\n<span class=\"l\">  <span class=\"n\">checks<\/span>:<\/span>\n<span class=\"l\">    <span class=\"n\">runs-on<\/span>: ubuntu-latest        <span class=\"c\"># a fresh Linux machine for every run<\/span><\/span>\n<span class=\"l\">    <span class=\"n\">steps<\/span>:<\/span>\n<span class=\"l\">      - <span class=\"n\">uses<\/span>: actions\/checkout@v6<\/span>\n<span class=\"l\"><\/span>\n<span class=\"l\">      - <span class=\"n\">name<\/span>: Lint the shell scripts<\/span>\n<span class=\"l\">        <span class=\"n\">run<\/span>: shellcheck *.sh      <span class=\"c\"># ShellCheck is preinstalled on Ubuntu runners<\/span><\/span>\n<span class=\"l\"><\/span>\n<span class=\"l\">      - <span class=\"n\">name<\/span>: Fail if the error rate is too high<\/span>\n<span class=\"l\">        <span class=\"n\">env<\/span>:<\/span>\n<span class=\"l\">          <span class=\"n\">MAX_ERROR_PCT<\/span>: 25<\/span>\n<span class=\"l\">        <span class=\"n\">run<\/span>: .\/log-report.sh logs\/access.log<\/span>\n<span class=\"l\"><\/span>\n<span class=\"l\">      - <span class=\"n\">name<\/span>: Start a test web server in the background<\/span>\n<span class=\"l\">        <span class=\"n\">run<\/span>: python3 -m http.server 8080 &amp;<\/span>\n<span class=\"l\"><\/span>\n<span class=\"l\">      - <span class=\"n\">name<\/span>: Wait until it answers<\/span>\n<span class=\"l\">        <span class=\"n\">run<\/span>: .\/healthcheck.sh http:\/\/localhost:8080\/ 10 2<\/span><\/code><\/pre>\n<div class=\"dd-vsc-status\"><span>\u2387 main<\/span><span>Ln 26, Col 1<\/span><span class=\"sp\"><\/span><span>Spaces: 2<\/span><span>UTF-8<\/span><span>LF<\/span><span>YAML<\/span><\/div>\n<\/div>\n<\/div>\n<p class=\"dd-vsc-hint\">Click the tabs to switch files, then \u25b6 Run on each script to see real output, including the failing cases with exit code 1.<\/p>\n<p>Let us walk through the important lines of <code>log-report.sh<\/code>:<\/p>\n<ul>\n<li><code>#!\/usr\/bin\/env bash<\/code> \u2013 the <strong>shebang<\/strong>. It tells Linux to run the file with bash, wherever bash is installed.<\/li>\n<li><code>\"${1:?Usage: ...}\"<\/code> \u2013 <code>$1<\/code> is the first argument. <code>:?<\/code> stops the script with a helpful message if it is missing.<\/li>\n<li><code>\"${MAX_ERROR_PCT:-25}\"<\/code> \u2013 read a setting from an environment variable, with a default. The pipeline sets it in the <code>env:<\/code> block.<\/li>\n<li><code>[[ ! -f \"$LOG_FILE\" ]]<\/code> \u2013 a test: \u201cis this not a regular file?\u201d. <code>&gt;&amp;2<\/code> sends the error message to stderr.<\/li>\n<li><code>$(...)<\/code> \u2013 <strong>command substitution<\/strong>: run a command and store its output in a variable.<\/li>\n<li><code>$(( ... ))<\/code> and <code>(( ... ))<\/code> \u2013 whole-number maths and number comparisons.<\/li>\n<li><code>exit 1<\/code> \u2013 a non-zero exit code makes the CI step fail. This is how a script \u201cblocks\u201d a bad release.<\/li>\n<\/ul>\n<p>Create the files, make them executable and commit them:<\/p>\n<div class=\"dd-vsc dd-vsc-termonly\" role=\"group\" aria-label=\"Terminal: devops-part2 \u2014 bash\">\n<div class=\"dd-vsc-bar\"><span class=\"dd-dots\"><i><\/i><i><\/i><i><\/i><\/span><span class=\"dd-vsc-title\">devops-part2 \u2014 bash<\/span><\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\"># Make the scripts executable once (Git remembers the x bit)<\/span><\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> chmod +x log-report.sh healthcheck.sh<\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> git add . &amp;&amp; git commit -m \"Add log report and health check scripts\"<\/span>\n<span class=\"l\"><span class=\"c\"># Check them for common mistakes before pushing<\/span><\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> shellcheck *.sh<\/span><\/code><\/pre>\n<\/div>\n\n<h2 id=\"safe-scripts\">Writing safe scripts: set -euo pipefail, quoting and ShellCheck<\/h2>\n<p>By default bash is very forgiving: if a command fails, it just continues with the next line. That is dangerous in a deploy script. Put this line at the top of every script:<\/p>\n<div class=\"dd-tablewrap\"><table>\n<tr><th>Option<\/th><th>What it does<\/th><th>Why it matters<\/th><\/tr>\n<tr><td><code>set -e<\/code><\/td><td>Exit as soon as a command fails<\/td><td>A failed build does not go on to deploy<\/td><\/tr>\n<tr><td><code>set -u<\/code><\/td><td>Treat unset variables as an error<\/td><td>A typo like <code>$DIRR<\/code> stops the script instead of becoming an empty string<\/td><\/tr>\n<tr><td><code>set -o pipefail<\/code><\/td><td>A pipeline fails if <em>any<\/em> part fails<\/td><td>Without it, <code>broken-cmd | sort<\/code> looks successful because <code>sort<\/code> worked<\/td><\/tr>\n<\/table><\/div>\n<p><strong>Always quote variables:<\/strong> write <code>\"$FILE\"<\/code>, not <code>$FILE<\/code>. Without quotes, a file name with a space (<code>my report.txt<\/code>) is split into two words and your command breaks in strange ways.<\/p>\n<p><strong>Use ShellCheck:<\/strong> <a href=\"https:\/\/www.shellcheck.net\/\" rel=\"noopener\" target=\"_blank\">ShellCheck<\/a> is a free linter that finds bugs in shell scripts, such as missing quotes or wrong tests. Both scripts in this post pass <code>shellcheck<\/code> with no warnings, and our workflow runs it on every push. The <a href=\"https:\/\/www.gnu.org\/software\/bash\/manual\/bash.html\" rel=\"noopener\" target=\"_blank\">GNU Bash Reference Manual<\/a> is the official place to look up any bash feature.<\/p>\n<div class=\"dd-tip\"><strong>When to stop using bash:<\/strong> shell scripts are perfect for gluing commands together (up to about 100 lines). When you need complex data structures, JSON handling or lots of logic, switch to Python or Node.js. Many teams keep small bash scripts for CI steps and use a real language for anything bigger.<\/div>\n\n<h2 id=\"errors\">Common errors and how to fix them<\/h2>\n<div class=\"dd-tablewrap\"><table>\n<tr><th>Error message<\/th><th>Why it happens<\/th><th>Fix<\/th><\/tr>\n<tr><td><code>Permission denied<\/code><\/td><td>The script is not executable, or you cannot read\/write that path<\/td><td><code>chmod +x script.sh<\/code> and commit it; check owners with <code>ls -l<\/code>; use <code>sudo<\/code> only for system paths<\/td><\/tr>\n<tr><td><code>command not found<\/code><\/td><td>The tool is not installed, or not in <code>$PATH<\/code>, or you forgot <code>.\/<\/code><\/td><td>Install it (<code>sudo apt-get install -y jq<\/code>); run local scripts as <code>.\/script.sh<\/code>; check with <code>which tool<\/code><\/td><\/tr>\n<tr><td><code>No such file or directory<\/code><\/td><td>Wrong relative path; the script ran from another folder<\/td><td>Print <code>pwd<\/code>; use paths relative to the repo root; in scripts use <code>cd \"$(dirname \"$0\")\"<\/code><\/td><\/tr>\n<tr><td><code>\/usr\/bin\/env: 'bash\\r': No such file or directory<\/code><\/td><td>The file has Windows line endings (CRLF)<\/td><td>Run <code>sed -i 's\/\\r$\/\/' script.sh<\/code> and add <code>*.sh text eol=lf<\/code> to <code>.gitattributes<\/code><\/td><\/tr>\n<tr><td><code>unbound variable<\/code><\/td><td><code>set -u<\/code> caught a variable that was never set<\/td><td>Set it, or give a default: <code>${NAME:-default}<\/code><\/td><\/tr>\n<tr><td>CI step \u201cpasses\u201d although a command failed<\/td><td>No <code>set -e<\/code>, or the failure was hidden inside a pipe<\/td><td>Add <code>set -euo pipefail<\/code> at the top of the script<\/td><\/tr>\n<tr><td><code>No space left on device<\/code><\/td><td>The disk is full (often old logs or Docker images)<\/td><td>Check with <code>df -h<\/code> and <code>du -sh \/var\/log\/*<\/code>; clean with <code>docker system prune<\/code><\/td><\/tr>\n<\/table><\/div>\n\n<h2 id=\"best-practices\">Best practices for Linux and shell in DevOps<\/h2>\n<ul>\n<li><strong>Keep scripts in the repository<\/strong> next to the code, so the pipeline and your laptop run exactly the same commands.<\/li>\n<li><strong>Start every script with<\/strong> <code>#!\/usr\/bin\/env bash<\/code> and <code>set -euo pipefail<\/code>.<\/li>\n<li><strong>Quote every variable<\/strong> and run ShellCheck in CI.<\/li>\n<li><strong>Make scripts safe to run twice<\/strong> (idempotent): use <code>mkdir -p<\/code>, check before you delete, and do not append the same line twice.<\/li>\n<li><strong>Print clear messages<\/strong> and send errors to stderr (<code>&gt;&amp;2<\/code>), so CI logs are easy to read.<\/li>\n<li><strong>Never hard-code secrets<\/strong> in scripts. Read them from environment variables that come from your CI secret store.<\/li>\n<li><strong>Give secret files 600 permissions<\/strong> and never commit <code>.env<\/code> files.<\/li>\n<li><strong>Use long option names in shared scripts<\/strong> (<code>--silent<\/code> instead of <code>-s<\/code>) when it helps teammates read them.<\/li>\n<\/ul>\n<\/div>\n\n\n\n<div class=\"dd-post\">\n<h2 id=\"practice\">Practice task<\/h2>\n<div class=\"dd-note\">\n<p><strong>Build a tiny \u201crelease gate\u201d script.<\/strong> Use the sample log from this post.<\/p>\n<ol>\n<li>Copy <code>log-report.sh<\/code> and add a second check: fail if there are more than 2 requests with status <code>404<\/code>. Use an environment variable <code>MAX_404<\/code> with a default of 2.<\/li>\n<li>Print the top 3 most requested URLs (hint: the 7th column, <code>$7<\/code>).<\/li>\n<li>Add one more line to <code>logs\/access.log<\/code> with a 404, run the script again and check that the exit code (<code>echo $?<\/code>) changes.<\/li>\n<li>Run <code>shellcheck<\/code> on your script and fix every warning.<\/li>\n<li><em>Bonus:<\/em> push it to GitHub with the <code>scripts.yml<\/code> workflow and watch the pipeline fail and then pass as you change <code>MAX_404<\/code>.<\/li>\n<\/ol>\n<\/div>\n<p>Want more pipeline practice right now? Our <a href=\"https:\/\/devdojo.co.in\/index.php\/2026\/10\/06\/github-actions-ci-cd-nodejs\/\">GitHub Actions CI\/CD for Node.js guide<\/a> shows a full Node.js workflow, and the <a href=\"https:\/\/devdojo.co.in\/index.php\/2026\/10\/10\/docker-compose-tutorial\/\">Docker Compose tutorial<\/a> uses many of the commands from this post.<\/p>\n\n<h2 id=\"faq\">Frequently asked questions<\/h2>\n<details><summary>How much Linux do I need to know for DevOps?<\/summary><p>For a beginner DevOps role you need the basics in this post: navigating files, permissions, pipes and text tools, environment variables, processes, services and simple scripts. Deeper topics such as networking, storage and kernel tuning come later with experience.<\/p><\/details>\n<details><summary>Should I learn bash or zsh?<\/summary><p>Learn bash. It is the default shell on most Linux servers and CI runners, and almost everything you learn works the same in zsh, which macOS uses by default. Write your scripts for bash with <code>#!\/usr\/bin\/env bash<\/code>.<\/p><\/details>\n<details><summary>Which Linux distribution should a DevOps beginner use?<\/summary><p>Ubuntu is the easiest start: it is the default on GitHub Actions runners, very common in the cloud and has lots of tutorials. Later you will also meet Debian and Alpine in Docker images, and Red Hat-style systems (RHEL, Rocky Linux, Amazon Linux) in companies.<\/p><\/details>\n<details><summary>What does exit code 127 or 126 mean?<\/summary><p><code>127<\/code> means \u201ccommand not found\u201d (the program is not installed or not in your PATH). <code>126<\/code> means the file was found but could not be executed, usually because it has no execute permission. <code>1<\/code> is a general error and <code>130<\/code> means you pressed Ctrl+C.<\/p><\/details>\n<details><summary>Can I practise Linux on Windows?<\/summary><p>Yes. Install WSL with <code>wsl --install<\/code> to get a full Ubuntu terminal inside Windows. You can also use Git Bash for simple commands, or a free cloud shell from AWS, Google Cloud or Azure.<\/p><\/details>\n\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>You now have the <strong>Linux for DevOps<\/strong> basics that every later part of this series builds on. You can move around the file system, read and change permissions, join tools with pipes to analyse logs, use environment variables and exit codes, manage processes and services, and write safe shell scripts that make a pipeline pass or fail for the right reasons. The best way to make these skills stick is to use the terminal every day \u2013 try doing your next small task without the mouse.<\/p>\n<p>Later in the series these skills will come back again and again: in Dockerfiles, in <a href=\"https:\/\/devdojo.co.in\/index.php\/2026\/10\/18\/kubernetes-tutorial\/\">Kubernetes<\/a> debugging, in Terraform and Ansible, and in every pipeline step you write.<\/p>\n\n<div class=\"dd-next\"><strong>Next part \u2013 DevOps CI\/CD Part 3: Git for Teams: Branches, Pull Requests and Trunk-Based Development.<\/strong> Every pipeline starts with a Git event. We will learn how teams branch, review and merge code safely, and which Git workflow works best with CI\/CD.<\/div>\n\n<div class=\"dd-cta\">\n<h3>New posts every day on DevDojo<\/h3>\n<p>Follow the full <strong>DevOps CI\/CD series<\/strong> \u2013 30 parts from \u201cwhat is DevOps\u201d to a complete pipeline from commit to production. Bookmark <a href=\"https:\/\/devdojo.co.in\/\">devdojo.co.in<\/a> and tell us in the comments which Linux command you use the most.<\/p>\n<\/div>\n<\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>Learn Linux for DevOps: essential shell commands, file permissions, pipes, environment variables, exit codes and safe bash scripts you can run in CI\/CD.<\/p>\n","protected":false},"author":1,"featured_media":250,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[57],"tags":[138,61,123,137,140,121,139],"class_list":["post-251","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","tag-bash","tag-devops","tag-devops-for-beginners","tag-linux","tag-linux-commands","tag-series-devops-ci-cd","tag-shell-scripting"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.4 - aioseo.com -->\n\t<meta name=\"description\" content=\"Learn Linux for DevOps: essential shell commands, file permissions, pipes, environment variables, exit codes and safe bash scripts you can run in CI\/CD.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Lokendra\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/devdojo.co.in\/index.php\/2026\/10\/30\/devops-series-part-2-linux-shell-basics\/\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.4\" \/>\n\t\t<meta property=\"og:locale\" content=\"en_US\" \/>\n\t\t<meta property=\"og:site_name\" content=\"DevDojo - Empowering Developers\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"Linux for DevOps: Shell Basics Every Engineer Needs\" \/>\n\t\t<meta property=\"og:description\" content=\"Learn Linux for DevOps: essential shell commands, file permissions, pipes, environment variables, exit codes and safe bash scripts you can run in CI\/CD.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/devdojo.co.in\/index.php\/2026\/10\/30\/devops-series-part-2-linux-shell-basics\/\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2026-10-30T12:28:20+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2026-10-30T12:28:21+00:00\" \/>\n\t\t<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n\t\t<meta name=\"twitter:title\" content=\"Linux for DevOps: Shell Basics Every Engineer Needs\" \/>\n\t\t<meta name=\"twitter:description\" content=\"Learn Linux for DevOps: essential shell commands, file permissions, pipes, environment variables, exit codes and safe bash scripts you can run in CI\/CD.\" \/>\n\t\t<script type=\"application\/ld+json\" class=\"aioseo-schema\">\n\t\t\t{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"BlogPosting\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/30\\\/devops-series-part-2-linux-shell-basics\\\/#blogposting\",\"name\":\"Linux for DevOps: Shell Basics Every Engineer Needs\",\"headline\":\"DevOps CI\\\/CD Part 2: Linux and Shell Basics Every DevOps Engineer Needs\",\"author\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/author\\\/lokendra\\\/#author\"},\"publisher\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#organization\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/wp-content\\\/uploads\\\/2026\\\/10\\\/devops-series-part-2-linux-shell-basics.jpg\",\"width\":1200,\"height\":630,\"caption\":\"DevOps CI\\\/CD Part 2 banner: Linux and shell basics for DevOps \\u2013 files, permissions, pipes, processes, environment variables and scripts\"},\"datePublished\":\"2026-10-30T12:28:20+00:00\",\"dateModified\":\"2026-10-30T12:28:21+00:00\",\"inLanguage\":\"en-US\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/30\\\/devops-series-part-2-linux-shell-basics\\\/#webpage\"},\"isPartOf\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/30\\\/devops-series-part-2-linux-shell-basics\\\/#webpage\"},\"articleSection\":\"DevOps, Bash, DevOps, DevOps for Beginners, Linux, Linux Commands, Series: DevOps CI\\\/CD, Shell Scripting\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/30\\\/devops-series-part-2-linux-shell-basics\\\/#breadcrumblist\",\"itemListElement\":[{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#listItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/devdojo.co.in\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/devops\\\/#listItem\",\"name\":\"DevOps\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/devops\\\/#listItem\",\"position\":2,\"name\":\"DevOps\",\"item\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/devops\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/30\\\/devops-series-part-2-linux-shell-basics\\\/#listItem\",\"name\":\"DevOps CI\\\/CD Part 2: Linux and Shell Basics Every DevOps Engineer Needs\"},\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#listItem\",\"name\":\"Home\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/30\\\/devops-series-part-2-linux-shell-basics\\\/#listItem\",\"position\":3,\"name\":\"DevOps CI\\\/CD Part 2: Linux and Shell Basics Every DevOps Engineer Needs\",\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/devops\\\/#listItem\",\"name\":\"DevOps\"}}]},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#organization\",\"name\":\"DevDojo\",\"description\":\"Empowering Developers\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/author\\\/lokendra\\\/#author\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/author\\\/lokendra\\\/\",\"name\":\"Lokendra\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/30\\\/devops-series-part-2-linux-shell-basics\\\/#authorImage\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/99f102f0cc9080237ce54b91304fd1b3b6fa29925cbaeba93cd796872e58b45c?s=96&d=mm&r=g\",\"width\":96,\"height\":96,\"caption\":\"Lokendra\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/30\\\/devops-series-part-2-linux-shell-basics\\\/#webpage\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/30\\\/devops-series-part-2-linux-shell-basics\\\/\",\"name\":\"Linux for DevOps: Shell Basics Every Engineer Needs\",\"description\":\"Learn Linux for DevOps: essential shell commands, file permissions, pipes, environment variables, exit codes and safe bash scripts you can run in CI\\\/CD.\",\"inLanguage\":\"en-US\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#website\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/30\\\/devops-series-part-2-linux-shell-basics\\\/#breadcrumblist\"},\"author\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/author\\\/lokendra\\\/#author\"},\"creator\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/author\\\/lokendra\\\/#author\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/wp-content\\\/uploads\\\/2026\\\/10\\\/devops-series-part-2-linux-shell-basics.jpg\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/30\\\/devops-series-part-2-linux-shell-basics\\\/#mainImage\",\"width\":1200,\"height\":630,\"caption\":\"DevOps CI\\\/CD Part 2 banner: Linux and shell basics for DevOps \\u2013 files, permissions, pipes, processes, environment variables and scripts\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/30\\\/devops-series-part-2-linux-shell-basics\\\/#mainImage\"},\"datePublished\":\"2026-10-30T12:28:20+00:00\",\"dateModified\":\"2026-10-30T12:28:21+00:00\"},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#website\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/\",\"name\":\"DevDojo\",\"description\":\"Empowering Developers\",\"inLanguage\":\"en-US\",\"publisher\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#organization\"}}]}\n\t\t<\/script>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"Linux for DevOps: Shell Basics Every Engineer Needs","description":"Learn Linux for DevOps: essential shell commands, file permissions, pipes, environment variables, exit codes and safe bash scripts you can run in CI\/CD.","canonical_url":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/30\/devops-series-part-2-linux-shell-basics\/","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"BlogPosting","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/30\/devops-series-part-2-linux-shell-basics\/#blogposting","name":"Linux for DevOps: Shell Basics Every Engineer Needs","headline":"DevOps CI\/CD Part 2: Linux and Shell Basics Every DevOps Engineer Needs","author":{"@id":"https:\/\/devdojo.co.in\/index.php\/author\/lokendra\/#author"},"publisher":{"@id":"https:\/\/devdojo.co.in\/#organization"},"image":{"@type":"ImageObject","url":"https:\/\/devdojo.co.in\/wp-content\/uploads\/2026\/10\/devops-series-part-2-linux-shell-basics.jpg","width":1200,"height":630,"caption":"DevOps CI\/CD Part 2 banner: Linux and shell basics for DevOps \u2013 files, permissions, pipes, processes, environment variables and scripts"},"datePublished":"2026-10-30T12:28:20+00:00","dateModified":"2026-10-30T12:28:21+00:00","inLanguage":"en-US","mainEntityOfPage":{"@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/30\/devops-series-part-2-linux-shell-basics\/#webpage"},"isPartOf":{"@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/30\/devops-series-part-2-linux-shell-basics\/#webpage"},"articleSection":"DevOps, Bash, DevOps, DevOps for Beginners, Linux, Linux Commands, Series: DevOps CI\/CD, Shell Scripting"},{"@type":"BreadcrumbList","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/30\/devops-series-part-2-linux-shell-basics\/#breadcrumblist","itemListElement":[{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/#listItem","position":1,"name":"Home","item":"https:\/\/devdojo.co.in\/","nextItem":{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/category\/devops\/#listItem","name":"DevOps"}},{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/category\/devops\/#listItem","position":2,"name":"DevOps","item":"https:\/\/devdojo.co.in\/index.php\/category\/devops\/","nextItem":{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/30\/devops-series-part-2-linux-shell-basics\/#listItem","name":"DevOps CI\/CD Part 2: Linux and Shell Basics Every DevOps Engineer Needs"},"previousItem":{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/#listItem","name":"Home"}},{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/30\/devops-series-part-2-linux-shell-basics\/#listItem","position":3,"name":"DevOps CI\/CD Part 2: Linux and Shell Basics Every DevOps Engineer Needs","previousItem":{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/category\/devops\/#listItem","name":"DevOps"}}]},{"@type":"Organization","@id":"https:\/\/devdojo.co.in\/#organization","name":"DevDojo","description":"Empowering Developers","url":"https:\/\/devdojo.co.in\/"},{"@type":"Person","@id":"https:\/\/devdojo.co.in\/index.php\/author\/lokendra\/#author","url":"https:\/\/devdojo.co.in\/index.php\/author\/lokendra\/","name":"Lokendra","image":{"@type":"ImageObject","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/30\/devops-series-part-2-linux-shell-basics\/#authorImage","url":"https:\/\/secure.gravatar.com\/avatar\/99f102f0cc9080237ce54b91304fd1b3b6fa29925cbaeba93cd796872e58b45c?s=96&d=mm&r=g","width":96,"height":96,"caption":"Lokendra"}},{"@type":"WebPage","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/30\/devops-series-part-2-linux-shell-basics\/#webpage","url":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/30\/devops-series-part-2-linux-shell-basics\/","name":"Linux for DevOps: Shell Basics Every Engineer Needs","description":"Learn Linux for DevOps: essential shell commands, file permissions, pipes, environment variables, exit codes and safe bash scripts you can run in CI\/CD.","inLanguage":"en-US","isPartOf":{"@id":"https:\/\/devdojo.co.in\/#website"},"breadcrumb":{"@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/30\/devops-series-part-2-linux-shell-basics\/#breadcrumblist"},"author":{"@id":"https:\/\/devdojo.co.in\/index.php\/author\/lokendra\/#author"},"creator":{"@id":"https:\/\/devdojo.co.in\/index.php\/author\/lokendra\/#author"},"image":{"@type":"ImageObject","url":"https:\/\/devdojo.co.in\/wp-content\/uploads\/2026\/10\/devops-series-part-2-linux-shell-basics.jpg","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/30\/devops-series-part-2-linux-shell-basics\/#mainImage","width":1200,"height":630,"caption":"DevOps CI\/CD Part 2 banner: Linux and shell basics for DevOps \u2013 files, permissions, pipes, processes, environment variables and scripts"},"primaryImageOfPage":{"@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/30\/devops-series-part-2-linux-shell-basics\/#mainImage"},"datePublished":"2026-10-30T12:28:20+00:00","dateModified":"2026-10-30T12:28:21+00:00"},{"@type":"WebSite","@id":"https:\/\/devdojo.co.in\/#website","url":"https:\/\/devdojo.co.in\/","name":"DevDojo","description":"Empowering Developers","inLanguage":"en-US","publisher":{"@id":"https:\/\/devdojo.co.in\/#organization"}}]},"og:locale":"en_US","og:site_name":"DevDojo - Empowering Developers","og:type":"article","og:title":"Linux for DevOps: Shell Basics Every Engineer Needs","og:description":"Learn Linux for DevOps: essential shell commands, file permissions, pipes, environment variables, exit codes and safe bash scripts you can run in CI\/CD.","og:url":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/30\/devops-series-part-2-linux-shell-basics\/","article:published_time":"2026-10-30T12:28:20+00:00","article:modified_time":"2026-10-30T12:28:21+00:00","twitter:card":"summary_large_image","twitter:title":"Linux for DevOps: Shell Basics Every Engineer Needs","twitter:description":"Learn Linux for DevOps: essential shell commands, file permissions, pipes, environment variables, exit codes and safe bash scripts you can run in CI\/CD."},"aioseo_meta_data":{"post_id":"251","title":"Linux for DevOps: Shell Basics Every Engineer Needs","description":"Learn Linux for DevOps: essential shell commands, file permissions, pipes, environment variables, exit codes and safe bash scripts you can run in CI\/CD.","keywords":null,"keyphrases":{"focus":{"keyphrase":"Linux for DevOps","score":69},"additional":[]},"primary_term":null,"canonical_url":null,"og_title":"Linux for DevOps: Shell Basics Every Engineer Needs","og_description":"Learn Linux for DevOps: essential shell commands, file permissions, pipes, environment variables, exit codes and safe bash scripts you can run in CI\/CD.","og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":"","og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"BlogPosting","isEnabled":true},"graphs":[]},"schema_type":"default","schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":"-1","robots_max_videopreview":"-1","robots_max_imagepreview":"large","priority":0,"frequency":"default","local_seo":null,"breadcrumb_settings":null,"limit_modified_date":false,"ai":{"faqs":[],"keyPoints":[],"schemas":[],"titles":[],"descriptions":[],"socialPosts":{"email":{"subject":"","preview":"","content":""},"linkedin":[],"twitter":[],"facebook":[],"instagram":[]}},"created":"2026-10-28 13:15:50","updated":"2026-10-30 12:28:21","focus_keyword":"Linux for DevOps","additional_keywords":null,"truseo_locale":null,"seo_analyzer_scan_date":null},"aioseo_breadcrumb":"<div class=\"aioseo-breadcrumbs\"><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/devdojo.co.in\/\" title=\"Home\">Home<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">\u00bb<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/devdojo.co.in\/index.php\/category\/devops\/\" title=\"DevOps\">DevOps<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">\u00bb<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\tDevOps CI\/CD Part 2: Linux and Shell Basics Every DevOps Engineer Needs\n\t\t<\/span><\/div>","aioseo_breadcrumb_json":[{"label":"Home","link":"https:\/\/devdojo.co.in\/"},{"label":"DevOps","link":"https:\/\/devdojo.co.in\/index.php\/category\/devops\/"},{"label":"DevOps CI\/CD Part 2: Linux and Shell Basics Every DevOps Engineer Needs","link":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/30\/devops-series-part-2-linux-shell-basics\/"}],"_links":{"self":[{"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/posts\/251","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/comments?post=251"}],"version-history":[{"count":1,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/posts\/251\/revisions"}],"predecessor-version":[{"id":255,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/posts\/251\/revisions\/255"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/media\/250"}],"wp:attachment":[{"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/media?parent=251"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/categories?post=251"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/tags?post=251"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}