From 6e1e8effd3e595ad342d11644b072725a218c1eb Mon Sep 17 00:00:00 2001 From: Ahmed Date: Tue, 2 Jun 2026 00:21:16 +0300 Subject: init: moved to gumx.cc --- .gitignore | 3 + CONTRIBUTING.md | 223 ++++ LICENSE.md | 29 + LICENSES/CC-BY-SA-4.0 | 428 +++++++ LICENSES/MIT | 21 + PROGRESS.md | 542 ++++++++ README.md | 38 + build.sh | 215 ++++ build/mo | 2037 ++++++++++++++++++++++++++++++ build/templates/home.html | 12 + build/templates/page.html | 12 + build/templates/partials/.gitkeep | 0 build/templates/partials/footer.html | 16 + build/templates/partials/head.html | 11 + build/templates/partials/header.html | 6 + linux-command-line/shell-basics/index.md | 327 +++++ 16 files changed, 3920 insertions(+) create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE.md create mode 100644 LICENSES/CC-BY-SA-4.0 create mode 100644 LICENSES/MIT create mode 100644 PROGRESS.md create mode 100644 README.md create mode 100644 build.sh create mode 100755 build/mo create mode 100644 build/templates/home.html create mode 100644 build/templates/page.html create mode 100644 build/templates/partials/.gitkeep create mode 100644 build/templates/partials/footer.html create mode 100644 build/templates/partials/head.html create mode 100644 build/templates/partials/header.html create mode 100644 linux-command-line/shell-basics/index.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c1c3231 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.worknotes/ +output/ + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7173170 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,223 @@ +--- +title: contributing guide +--- + +# contributing to ubergeek + +ubergeek is a community study guide. Contributions are welcome — fixes, +improvements, new exercises, better explanations, and sample projects. + +The primary repository lives on **sourcehut** and is mirrored to GitHub and +GitLab. You can contribute from any platform. + +--- + +## Ways to Contribute + +- **Fix errors** — typos, incorrect information, broken examples +- **Improve explanations** — clearer wording, better analogies, added context +- **Add exercises and projects** — practice problems, mini-projects, challenge sets +- **Add sample code** — working examples in any of the covered languages +- **Add diagrams and images** — ASCII art, schematics, architecture diagrams +- **Review existing content** — flag sections that are confusing or incomplete +- **Suggest new topics or modules** — open a discussion first + +--- + +## Repository Structure + +``` +topic-name/ +├── index.md # Topic overview +├── images/ # Diagrams, schematics, screenshots (.png, .svg, .jpg) +├── samples/ # Runnable code and project files +│ └── project-name/ +│ ├── src/ +│ ├── Makefile +│ └── README.md +└── module-name/ + └── index.md # Module content +``` + +- Content goes in `index.md` files using CommonMark markdown +- Images go in the topic-level `images/` directory +- Sample code and project files go in the topic-level `samples/` directory +- Every sample project should include a README explaining how to build and run it +- Reference images using relative paths: `![alt](images/filename.png)` + +--- + +## Content Style Guide + +### Tone +- Direct and practical — teach by showing, not lecturing +- Assume intelligence, not prior knowledge +- Use "you" not "the student" or "one" + +### Structure +- Every page starts with frontmatter (`title` required) +- Open with a short paragraph explaining what and why +- Use code blocks liberally — real, runnable examples +- End modules with exercises (drill, challenges, mini-project) +- End with key takeaways and a "Next" link + +### Code +- All code must be correct and runnable +- Include comments only where the logic is not obvious +- Show output where it helps understanding +- For multi-language topics, show C, Python, Rust, C++, and Bash where applicable + +### Software Installation +- When a module requires extra software, link to the official site and include + installation steps for Arch Linux (primary), Debian/Ubuntu, and Fedora +- Use system package managers where possible +- Note any version requirements + +### Images +- Prefer ASCII diagrams for simple cases +- Store image files in `topic/images/` with descriptive names +- Reference using relative paths: `![description](images/filename.png)` +- Always include `alt` text + +### Naming +- Directories: `kebab-case` (e.g., `text-processing`, `pointers-and-memory`) +- Files: `kebab-case.md` for content, descriptive names for images and samples +- No spaces, no uppercase in file/directory names + +--- + +## Contributing via Sourcehut (primary) + +The canonical repository is on +[sr.ht](https://git.sr.ht/~gumxcc/ubergeek). Contributions are accepted via +email patches. + +### Setup + +```bash +# Clone the repo +git clone https://git.sr.ht/~gumxcc/ubergeek +cd ubergeek + +# Configure git send-email (one-time setup) +git config sendemail.to ~gumxcc/ubergeek@lists.sr.ht +git config format.subjectPrefix "PATCH ubergeek" +``` + +### Submitting a Patch + +```bash +# Create a branch and make your changes +git checkout -b fix-typo-shell-basics +# ... edit files ... +git add -A +git commit -m "fix: correct permissions example in shell basics" + +# Send patch via email +git send-email --to=~gumxcc/ubergeek@lists.sr.ht HEAD~1 +``` + +For multi-commit changes: + +```bash +git send-email --to=~gumxcc/ubergeek@lists.sr.ht origin/main..HEAD +``` + +### Discussion + +Use the mailing list for questions, suggestions, and reviews: +`~gumxcc/ubergeek@lists.sr.ht` + +See [git-send-email.io](https://git-send-email.io) for a tutorial on setting +up `git send-email`. + +--- + +## Contributing via GitHub / GitLab (mirrors) + +Pull requests (GitHub) and merge requests (GitLab) are both accepted. + +```bash +# Fork the repo on GitHub or GitLab, then clone your fork +git clone https://github.com/gumxcc/ubergeek.git # or gitlab.com +cd ubergeek + +# Create a branch +git checkout -b fix-typo-shell-basics + +# Make changes, commit, push +git add -A +git commit -m "fix: correct permissions example in shell basics" +git push origin fix-typo-shell-basics + +# Open a pull/merge request from the web UI +``` + +### PR/MR Guidelines +- Keep PRs focused — one fix or one module per PR +- Write a clear description of what you changed and why +- If adding a new module, open an issue first to discuss scope + +--- + +## Sample Code Guidelines + +When adding to `samples/`: + +- **Must compile/run out of the box** — include a Makefile, requirements.txt, + Cargo.toml, or whatever the project needs +- **Include a README.md** — what it does, how to build, how to run, expected output +- **Keep it minimal** — demonstrate the concept, nothing more +- **Pin dependencies** where practical — don't rely on "latest" +- **Test on Linux** — that's the primary platform for this guide + +### KiCad / Hardware Projects + +- Include the full KiCad project directory (`.kicad_pro`, `.kicad_sch`, `.kicad_pcb`) +- Add a README.md with a description and BOM +- Export a schematic PDF and place it in `images/` for people without KiCad +- Use KiCad's built-in libraries where possible + +--- + +## Commit Messages + +``` +: + + +``` + +Types: +- `content` — new or updated study material +- `fix` — corrections to existing content +- `exercises` — new or updated exercises +- `samples` — new or updated sample code/projects +- `images` — new or updated diagrams/images +- `meta` — PROGRESS.md, CONTRIBUTING.md, index.md, study-plan.md +- `infra` — CI, build scripts, tooling + +Examples: +``` +content: add shell basics module for linux command line +fix: correct voltage divider formula in electronics +exercises: add pipeline challenges to text processing +samples: add bare-metal blinky project for STM32F4 +``` + +--- + +## Review Process + +1. All contributions are reviewed before merging +2. Content accuracy is the top priority +3. Style and formatting can be fixed post-merge — don't let it block good content +4. Disagreements are resolved by discussion, not authority + +--- + +## License + +By contributing, you agree that your contributions will be licensed under +CC BY-SA 4.0, the same license as the rest of the project. Code samples in +`samples/` directories are additionally available under the MIT license. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..c78c9c0 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,29 @@ +# License + +This project uses a dual-license model. + +## Content — CC BY-SA 4.0 + +All written content, documentation, exercises, images, and diagrams are +licensed under the +[Creative Commons Attribution-ShareAlike 4.0 International](LICENSES/CC-BY-SA-4.0) +license. + +You are free to share and adapt the material for any purpose, including +commercially, as long as you give credit and distribute derivative works under +the same license. + +## Code Samples — MIT + +All code in `samples/` directories throughout the repository is licensed under +the [MIT License](LICENSES/MIT). + +You are free to use, modify, and distribute the code with no copyleft +restrictions. + +## Which license applies? + +| Path | License | +|------|---------| +| `*/samples/**` | MIT | +| Everything else (`.md` files, images, diagrams) | CC BY-SA 4.0 | diff --git a/LICENSES/CC-BY-SA-4.0 b/LICENSES/CC-BY-SA-4.0 new file mode 100644 index 0000000..2d58298 --- /dev/null +++ b/LICENSES/CC-BY-SA-4.0 @@ -0,0 +1,428 @@ +Attribution-ShareAlike 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-ShareAlike 4.0 International Public +License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-ShareAlike 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + l. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + m. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + including for purposes of Section 3(b); and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. + diff --git a/LICENSES/MIT b/LICENSES/MIT new file mode 100644 index 0000000..dd99b59 --- /dev/null +++ b/LICENSES/MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 gumxcc and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/PROGRESS.md b/PROGRESS.md new file mode 100644 index 0000000..abb0cfb --- /dev/null +++ b/PROGRESS.md @@ -0,0 +1,542 @@ +--- +title: ubergeek study guide — progress tracker +--- + +# ubergeek study guide + +Master progress tracker and study plan. Single source of truth. + +**Legend:** +- `[ ]` Module Name — not started +- `[ ]` *wip* Module Name — in progress +- `[x]` [Module Name](path/to/module/) — complete (linked) +- *skip* — skippable for fast-track learners + +--- + +## Study Path + +Phases are sequential but topics within a phase can be studied in parallel. +Prerequisites are noted — you can jump ahead if you already know the prereqs. + +| Phase | Focus | Topics | Prereqs | +|-------|-------|--------|---------| +| 1 | Toolkit & Literacy | Command Line, Git, Technical Writing, Programming Fundamentals, C, Python, Bash, Math | None | +| 2 | Core Theory | Computer Science, Electronics, Make & Dev Automation | Phase 1 | +| 3 | Systems & Design | Operating Systems, PCB Design, Web Tech, Project Management | Phase 2 | +| 4 | Security & Infra | Cyber Security, Ansible, Automation | Phases 2-3 | +| 5 | Embedded World | Embedded Systems, PCB Prototyping, Digital Fabrication | Phases 2-3 | +| 6 | Deep Systems | Kernel Development, Embedded OS/RTOS, IoT | Phases 3-5 | +| 7 | Intelligence & Integration | Computer Vision/Edge AI, Robotics, Rust, C++, Advanced Languages | Phases 5-6 | +| -- | Side Quest (anytime) | Global Geek Culture | None | + +**Fast-track:** If you have prior knowledge, check the prereqs list in each topic and skip what you know. Modules marked *skip* are optional/enrichment. + +--- + +## Phase 1 — Toolkit & Literacy + +### 1.1 Linux Command Line Tools +`path: linux-command-line/` +> Prereqs: None + +- [ ] Shell Basics — navigation, file ops, permissions, pipes, redirection, man pages +- [ ] File Management — find, locate, tar, rsync, ln, file, stat, du, df +- [ ] Text Processing — grep, sed, awk, cut, sort, uniq, tr, wc, jq, yq +- [ ] Process Management — ps, top/htop, kill, nice, nohup, jobs, bg/fg, screen/tmux +- [ ] Networking — ssh, curl, wget, netstat/ss, ip, dig, nmap, nc, scp/sftp +- [ ] System Administration — systemctl, journalctl, cron, users/groups, disk mgmt, mount +- [ ] Advanced Shell — parameter expansion, process substitution, subshells, traps, here-docs +- [ ] Power Tools — fzf, ripgrep, fd, bat, eza, delta, zoxide, direnv +- [ ] *skip* Customization — shell config, aliases, functions, prompt, dotfiles management +- [ ] Exercises & Projects — system monitor, backup script, log analyzer, file organizer + +### 1.2 Git +`path: git/` +> Prereqs: Command Line basics + +- [ ] Fundamentals — init, add, commit, status, log, diff, .gitignore +- [ ] Branching & Merging — branches, merge, fast-forward, merge conflicts +- [ ] Remote Collaboration — remotes, push, pull, fetch, clone, PRs, patches (email workflow) +- [ ] Advanced Operations — rebase, cherry-pick, bisect, reflog, stash, worktrees +- [ ] Internals — objects (blob/tree/commit/tag), refs, packfiles, how Git works +- [ ] Workflows & Strategies — feature branches, Git Flow, trunk-based, monorepo, submodules +- [ ] Hooks & Automation — pre-commit, commit-msg, CI integration, custom hooks +- [ ] *skip* Sourcehut-Specific — sr.ht workflows, mailing list patches, builds.sr.ht +- [ ] Exercises & Projects — contribute a patch, manage a multi-branch project, write custom hooks + +### 1.3 Technical Writing & Research Methodologies +`path: technical-writing/` +> Prereqs: None + +- [ ] Writing Fundamentals — clarity, structure, audience analysis, active voice, style guides +- [ ] Technical Documentation — API docs, user guides, READMEs, changelogs, man pages +- [ ] Research Methodology — literature review, hypothesis, experimental design, data collection +- [ ] Data Presentation — tables, charts, statistical visualization, LaTeX/typst basics +- [ ] *skip* Academic Writing — papers, citations, abstracts, peer review process +- [ ] Documentation Tools — Markdown, AsciiDoc, Sphinx, Doxygen, mdBook +- [ ] Exercises & Projects — write a project README, document an API, create a lab report + +### 1.4 Programming Fundamentals +`path: programming/fundamentals/` +> Prereqs: Command Line basics + +- [ ] Core Concepts — variables, types, expressions, control flow (all languages compared) +- [ ] Functions & Modularity — functions, scope, recursion, modules/packages +- [ ] Data Structures (language-level) — arrays, strings, hash maps, structs/classes +- [ ] I/O & File Handling — stdin/stdout, file read/write, serialization (JSON, TOML, CSV) +- [ ] Error Handling — exceptions, return codes, Result/Option types (cross-language comparison) +- [ ] Memory Models — stack vs heap, value vs reference, ownership overview +- [ ] Build & Run — compilers, interpreters, REPLs, build systems, package managers +- [ ] Testing Basics — unit tests, assertions, test frameworks per language +- [ ] Exercises & Projects — CLI calculator, file processor, multi-language Rosetta Stone set + +### 1.5 C +`path: programming/c/` +> Prereqs: Programming Fundamentals + +- [ ] C Basics — syntax, types, printf/scanf, compilation with gcc/clang +- [ ] Pointers & Memory — pointer arithmetic, malloc/free, arrays vs pointers, void pointers +- [ ] Strings & Arrays — C strings, buffer safety, multi-dimensional arrays +- [ ] Structs & Unions — struct layout, padding/alignment, bit fields, tagged unions +- [ ] Preprocessor — macros, conditional compilation, include guards, X-macros +- [ ] Standard Library — stdio, stdlib, string.h, math.h, time.h, errno +- [ ] File I/O & System Calls — fopen/fread, open/read/write, mmap, ioctl +- [ ] Build Systems — Makefiles for C, static/shared libraries, linking, pkg-config +- [ ] Debugging & Profiling — gdb, valgrind, sanitizers (ASan/UBSan), perf +- [ ] Advanced C — function pointers, callbacks, opaque types, C11/C17/C23 features +- [ ] Exercises & Projects — memory allocator, linked list library, simple shell, file parser + +### 1.6 Python +`path: programming/python/` +> Prereqs: Programming Fundamentals + +- [ ] Python Basics — syntax, types, REPL, scripts, pip, virtual environments (venv, uv) +- [ ] Data Structures — lists, tuples, dicts, sets, comprehensions, slicing, unpacking +- [ ] Functions & Modules — def, *args/**kwargs, lambda, imports, packages, \_\_name\_\_ +- [ ] OOP — classes, inheritance, dunder methods, properties, dataclasses, protocols +- [ ] Error Handling — exceptions, try/except/finally, custom exceptions, context managers +- [ ] File I/O & Data — file ops, pathlib, JSON, CSV, TOML, regex, struct/binary +- [ ] Standard Library — os, sys, subprocess, collections, itertools, functools, logging, argparse +- [ ] Testing — pytest, unittest, mocking, fixtures, parametrize, coverage +- [ ] Scientific Stack Intro — NumPy basics, Matplotlib, pandas intro, Jupyter notebooks +- [ ] Exercises & Projects — CLI tool, web scraper, data pipeline, test suite for a library + +### 1.7 Bash Scripting +`path: programming/bash/` +> Prereqs: Command Line Tools + +- [ ] Script Basics — shebang, variables, quoting, exit codes, set options (-euo pipefail) +- [ ] Control Flow — if/elif/else, case, for, while, until, select +- [ ] Functions — definition, local variables, return values, passing arguments +- [ ] Text Processing in Scripts — grep/sed/awk integration, regex, here-docs/here-strings +- [ ] Process & Job Control — subshells, command substitution, pipe chains, xargs, parallel +- [ ] Advanced Patterns — signal handling (trap), getopts, associative arrays, nameref +- [ ] Script Packaging — argument parsing, help messages, config files, logging, color output +- [ ] Exercises & Projects — deployment script, system health checker, interactive menu tool + +### 1.8 Mathematics Foundations +`path: mathematics/` +> Prereqs: None (high school math assumed) + +- [ ] Logic & Proofs — propositional logic, predicate logic, proof techniques, set theory +- [ ] Number Systems — integers, rationals, reals, complex numbers, modular arithmetic +- [ ] Linear Algebra — vectors, matrices, systems of equations, determinants, eigenvalues +- [ ] Calculus I — limits, derivatives, integrals, fundamental theorem +- [ ] Calculus II — techniques of integration, sequences, series, Taylor/Maclaurin +- [ ] Multivariable Calculus — partial derivatives, gradients, multiple integrals, vector fields +- [ ] Differential Equations — first/second order ODEs, Laplace transforms, systems of ODEs +- [ ] Probability & Statistics — combinatorics, probability theory, distributions, inference, Bayes +- [ ] Discrete Mathematics — graph theory, combinatorics, recurrences, number theory basics +- [ ] *skip* Numerical Methods — root finding, interpolation, numerical integration, optimization +- [ ] *skip* Transform Methods — Fourier series/transform, Z-transform, DFT/FFT +- [ ] Exercises & Projects — problem sets per module, computational exercises (Python/Julia) + +--- + +## Phase 2 — Core Theory + +### 2.1 Computer Science +`path: computer-science/` +> Prereqs: Programming Fundamentals, Math (discrete math, logic) + +- [ ] Data Structures — arrays, linked lists, stacks, queues, trees, heaps, graphs, hash tables +- [ ] Algorithms — sorting, searching, graph algorithms (BFS/DFS/Dijkstra), recursion +- [ ] Algorithm Design — divide & conquer, dynamic programming, greedy, backtracking +- [ ] Complexity Theory — Big-O/Omega/Theta, amortized analysis, P vs NP, reductions +- [ ] Computer Architecture — CPU design, instruction sets, pipelining, caches, memory hierarchy +- [ ] Networking — OSI model, TCP/IP, DNS, HTTP/HTTPS, sockets, routing +- [ ] Databases — relational model, SQL, normalization, indexing, transactions, NoSQL overview +- [ ] *skip* Compilers — lexing, parsing, ASTs, semantic analysis, code generation +- [ ] *skip* Distributed Systems — CAP theorem, consensus, replication, consistency models +- [ ] Exercises & Projects — implement core data structures (C + Python + Rust), solve algorithm sets + +### 2.2 Electronics +`path: electronics/` +> Prereqs: Math (calculus, linear algebra, complex numbers) + +- [ ] Circuit Fundamentals — voltage, current, resistance, power, Ohm's/Kirchhoff's laws +- [ ] DC Circuit Analysis — node/mesh analysis, Thevenin/Norton, superposition +- [ ] AC Circuits — phasors, impedance, resonance, frequency response, Bode plots +- [ ] Passive Components — resistors, capacitors, inductors, transformers, practical specs +- [ ] Diodes & Rectifiers — PN junction, diode types, rectifier circuits, voltage regulators +- [ ] Transistors (BJT) — operation, biasing, small-signal models, amplifier configurations +- [ ] Transistors (MOSFET) — operation, biasing, CMOS logic, switching applications +- [ ] Op-Amps — ideal model, inverting/non-inverting, integrator/differentiator, active filters +- [ ] Digital Electronics — logic gates, Boolean algebra, combinational circuits, sequential (flip-flops, counters) +- [ ] Sensors & Measurement — common sensors (temp, light, force, IMU), signal conditioning, ADC/DAC +- [ ] Power Electronics — linear regulators, switching converters (buck/boost), battery charging +- [ ] *skip* RF Basics — transmission lines, impedance matching, modulation, antenna fundamentals +- [ ] Exercises & Projects — voltage divider lab, amplifier design, logic circuit build, sensor interface + +### 2.3 Make & Dev Automation +`path: make-and-automation/` +> Prereqs: Command Line, Git, Programming (C or any compiled language) + +- [ ] Make Basics — targets, prerequisites, recipes, variables, automatic variables, phony targets +- [ ] Advanced Make — pattern rules, functions, conditional directives, auto-dependencies, recursive vs non-recursive +- [ ] CMake — CMakeLists.txt, targets, find_package, generator expressions, modern CMake idioms +- [ ] *skip* Meson — build definitions, dependencies, cross-compilation, wraps +- [ ] Task Runners — Justfile, Taskfile, npm scripts, Python invoke, cargo-make +- [ ] Dev Containers — Dockerfiles for dev, devcontainer spec, reproducible environments +- [ ] *skip* Documentation Generation — Doxygen, Sphinx autodoc, mdBook, man page generation +- [ ] Exercises & Projects — multi-target C Makefile, CMake project with libraries, polyglot build system + +--- + +## Phase 3 — Systems & Design + +### 3.1 Operating Systems +`path: operating-systems/` +> Prereqs: CS (architecture, data structures), C, Math (basic) + +- [ ] Introduction — OS role, history, types, system calls, kernel vs userspace +- [ ] Process Management — processes, threads, context switching, scheduling algorithms +- [ ] Synchronization — race conditions, mutexes, semaphores, deadlocks, condition variables +- [ ] Memory Management — virtual memory, paging, segmentation, page replacement, TLB +- [ ] File Systems — VFS, inodes, ext4, FAT, journaling, directory structures +- [ ] I/O & Devices — device drivers model, block vs character, DMA, interrupt handling +- [ ] IPC — pipes, shared memory, message queues, signals, sockets +- [ ] Networking in OS — socket API, TCP/IP stack, network drivers, netfilter +- [ ] Security & Protection — access control, capabilities, namespaces, cgroups, sandboxing +- [ ] *skip* Virtualization — hypervisors (type 1/2), containers, paravirtualization, KVM +- [ ] Exercises & Projects — system call tracer, simple scheduler simulation, xv6 labs + +### 3.2 PCB Design +`path: pcb-design/` +> Prereqs: Electronics (all fundamentals) + +- [ ] Fundamentals — schematic capture workflow, EDA tools overview (KiCad focus), libraries +- [ ] Component Selection — reading datasheets, footprints, sourcing, lifecycle management +- [ ] Schematic Design — hierarchical sheets, power symbols, net labels, design review checklist +- [ ] PCB Layout Basics — board outline, stackup, component placement strategy, routing basics +- [ ] Routing — trace width, via types, differential pairs, length matching, copper pours +- [ ] Power Distribution — PDN design, decoupling strategy, thermal management, power planes +- [ ] Signal Integrity — impedance control, crosstalk, EMI/EMC basics, ground planes +- [ ] *skip* High-Speed Design — controlled impedance, return paths, high-speed routing rules +- [ ] Design for Manufacturing — DFM/DFA rules, panelization, assembly drawings, BOM management +- [ ] Exercises & Projects — LED breakout board, sensor board (2-layer), power supply board (4-layer) + +### 3.3 Web Technologies +`path: web-technologies/` +> Prereqs: CS (networking, databases), Programming (Python or JS) + +- [ ] HTML & CSS — semantic HTML5, CSS layout (flexbox, grid), responsive design, accessibility +- [ ] JavaScript — ES6+, DOM manipulation, events, async/await, modules, fetch API +- [ ] Backend Fundamentals — HTTP protocol, REST API design, authentication/authorization +- [ ] Server-Side Development — Python (Flask/FastAPI), Node.js (Express), API development +- [ ] Databases for Web — SQL (PostgreSQL), ORM concepts, migrations, Redis caching +- [ ] *skip* Frontend Frameworks — component architecture, state management, SPA vs MPA concepts +- [ ] DevOps for Web — Docker, reverse proxies (nginx), TLS, CI/CD, deployment strategies +- [ ] Security — XSS, CSRF, CSP, SQL injection prevention, CORS, HTTPS +- [ ] Exercises & Projects — static site, REST API with database, full-stack CRUD app + +### 3.4 Project Management +`path: project-management/` +> Prereqs: Technical Writing + +- [ ] Fundamentals — project lifecycle, scope, stakeholders, constraints triangle +- [ ] Methodologies — Agile, Scrum, Kanban, Waterfall, hybrid approaches +- [ ] Planning — work breakdown structure, estimation, scheduling, Gantt charts, risk management +- [ ] Execution & Monitoring — KPIs, burndown charts, stand-ups, retrospectives +- [ ] Tools & Systems — issue trackers, Kanban boards, time tracking, documentation wikis +- [ ] *skip* Team Dynamics — communication, conflict resolution, remote collaboration +- [ ] Engineering-Specific — hardware project management, firmware release cycles, versioning +- [ ] Exercises & Projects — plan a personal project end-to-end, set up a Kanban workflow + +--- + +## Phase 4 — Security & Infrastructure + +### 4.1 Cyber Security +`path: cyber-security/` +> Prereqs: CS (networking, OS concepts), Command Line, Programming + +- [ ] Fundamentals — CIA triad, threat modeling, attack surfaces, defense in depth +- [ ] Network Security — firewalls, IDS/IPS, VPN, packet analysis, network scanning +- [ ] System Hardening — access control, SELinux/AppArmor, audit logging, patching, CIS benchmarks +- [ ] Cryptography — symmetric/asymmetric encryption, hashing, TLS/SSL, PKI, key management +- [ ] Application Security — OWASP top 10, input validation, secure coding practices, code auditing +- [ ] Embedded Security — secure boot, firmware encryption, hardware attacks, side-channel basics +- [ ] *skip* Penetration Testing — methodology, recon, exploitation, post-exploitation, reporting +- [ ] *skip* Incident Response — detection, containment, forensics, recovery, lessons learned +- [ ] Exercises & Projects — harden a Linux server, set up TLS, CTF challenges, vulnerability assessment + +### 4.2 Ansible +`path: ansible/` +> Prereqs: Command Line (advanced), SSH, Linux administration + +- [ ] Introduction — IaC concepts, Ansible architecture, installation, configuration +- [ ] Core Concepts — inventory, modules, ad-hoc commands, YAML syntax +- [ ] Playbooks — plays, tasks, handlers, variables, facts, conditionals, loops +- [ ] Roles & Organization — role structure, Galaxy, collections, reuse patterns, project layout +- [ ] Advanced — Jinja2 templates, filters, lookups, custom modules, vault (secrets) +- [ ] *skip* Cloud & Containers — cloud provisioning, Docker management, Kubernetes basics +- [ ] Testing — Molecule, ansible-lint, CI integration, idempotency testing +- [ ] Exercises & Projects — server setup playbook, multi-tier deployment, homelab automation + +### 4.3 Automation +`path: automation/` +> Prereqs: Command Line, Bash, Git, Make basics + +- [ ] Principles — DRY, idempotency, reproducibility, when to automate (ROI) +- [ ] Shell Automation — cron, systemd timers, inotify/file watching, at +- [ ] CI/CD — concepts, GitHub Actions, sourcehut builds, pipeline design, artifacts +- [ ] Testing Automation — unit/integration/e2e strategies, fuzzing, property-based testing +- [ ] Infrastructure Automation — Terraform concepts, Ansible integration, GitOps basics +- [ ] *skip* Monitoring & Alerting — Prometheus, Grafana, alerting rules, log aggregation +- [ ] Workflow Automation — pre-commit hooks, linters, formatters, changelog generation +- [ ] Exercises & Projects — CI pipeline for a project, pre-commit setup, automated backup system + +--- + +## Phase 5 — Embedded World + +### 5.1 Embedded Systems +`path: embedded-systems/` +> Prereqs: Electronics, C, OS concepts + +- [ ] Introduction — embedded vs general-purpose, architectures (ARM Cortex-M, RISC-V, AVR), dev boards +- [ ] Microcontroller Fundamentals — CPU architecture, memory map, registers, clock system, startup code +- [ ] GPIO & Basic I/O — digital I/O, pull-ups/downs, debouncing, LED/button interfacing +- [ ] Interrupts — interrupt vectors, ISRs, priority, NVIC (ARM), critical sections +- [ ] Timers & PWM — timer modes, input capture, output compare, PWM generation, frequency measurement +- [ ] Serial Communication — UART (theory + implementation), SPI, I2C, protocol debugging +- [ ] ADC & DAC — conversion principles, sampling, resolution, DMA-driven ADC, signal conditioning +- [ ] DMA — DMA controller, channels, circular mode, memory-to-peripheral, performance gains +- [ ] Memory — flash programming, linker scripts, bootloader concepts, EEPROM, external memory +- [ ] Low Power Design — sleep modes, wakeup sources, power budgets, current measurement +- [ ] Communication Protocols — CAN bus, Modbus, 1-Wire, wireless overview (BLE, WiFi, LoRa) +- [ ] HAL vs Bare-Metal — vendor HALs (STM32 HAL/LL), CMSIS, register-level programming +- [ ] Debugging & Tools — JTAG/SWD, OpenOCD, GDB for embedded, logic analyzer, oscilloscope +- [ ] Exercises & Projects — blinky to bare-metal, UART logger, SPI sensor driver, motor controller + +### 5.2 PCB Prototyping +`path: pcb-prototyping/` +> Prereqs: PCB Design, Electronics + +- [ ] Breadboarding & Perfboard — prototyping techniques, Manhattan style, wire wrapping +- [ ] PCB Milling — desktop CNC (3018, Bantam), FlatCAM, isolation routing, drill files +- [ ] Chemical Etching — toner transfer, UV photoresist, etchant chemistry, safety +- [ ] Professional Fabrication — Gerber/drill generation, fab house selection (JLCPCB, PCBWay), ordering +- [ ] Assembly — hand soldering (through-hole + SMD), reflow (hot plate, oven), stencils +- [ ] Testing & Debugging — continuity testing, power-on sequence, functional testing, rework +- [ ] *skip* Rapid Iteration — version control for hardware, ECOs, change management +- [ ] Exercises & Projects — etch a simple board, order and assemble a PCB, full prototype cycle + +### 5.3 Digital Fabrication +`path: digital-fabrication/` +> Prereqs: Electronics basics, CAD basics + +- [ ] Overview — fab labs, maker culture, digital fabrication workflow +- [ ] 3D Printing — FDM, SLA/DLP, materials, slicing (Cura/PrusaSlicer), design for 3D printing +- [ ] Laser Cutting — materials, kerf compensation, design for laser, power/speed settings, safety +- [ ] *skip* CNC Machining — milling basics, G-code, CAM (FreeCAD Path), materials, work-holding +- [ ] CAD for Fabrication — FreeCAD, OpenSCAD, parametric design, export formats (STL, DXF, STEP) +- [ ] Enclosure Design — designing for electronics, standoffs, cable routing, thermal considerations +- [ ] *skip* Mold Making & Casting — silicone molds, resin casting, urethane, finishing +- [ ] Exercises & Projects — 3D print an enclosure, laser-cut a panel, design a complete housing + +--- + +## Phase 6 — Deep Systems + +### 6.1 Kernel Development +`path: kernel-development/` +> Prereqs: OS, C (advanced), Computer Architecture + +- [ ] Introduction — kernel role, monolithic vs micro, Linux source tree navigation +- [ ] Build & Boot — kernel configuration, cross-compiling, QEMU/VM testing, boot process +- [ ] Kernel Modules — module structure, Makefiles, loading/unloading, module parameters, licensing +- [ ] Character Devices — file_operations, major/minor numbers, ioctl, sysfs, procfs +- [ ] Kernel Memory — kmalloc/kfree, vmalloc, slab allocator, page allocator, GFP flags +- [ ] Concurrency — spinlocks, mutexes, RCU, atomic operations, per-CPU data, completion +- [ ] Interrupts & Timers — top/bottom halves, tasklets, workqueues, hrtimers, softirqs +- [ ] Device Model — platform devices, device tree, driver model, buses, probe/remove +- [ ] *skip* Networking — net_device, socket buffers (skb), netfilter hooks, packet flow +- [ ] *skip* File Systems — VFS interface, implementing a simple filesystem, block layer +- [ ] Debugging — printk/dynamic debug, ftrace, kprobes, KGDB, crash dump analysis, KASAN +- [ ] Exercises & Projects — hello-world module, char device driver, device tree overlay, simple driver + +### 6.2 Embedded OS & RTOS +`path: embedded-os/` +> Prereqs: Embedded Systems, OS, C + +- [ ] RTOS Concepts — real-time constraints, hard/soft RT, scheduling (rate monotonic, EDF), WCET +- [ ] FreeRTOS — tasks, queues, semaphores/mutexes, timers, memory management, portability +- [ ] Zephyr — architecture, devicetree, Kconfig, kernel primitives, networking stack, west tool +- [ ] Embedded Linux — Buildroot, Yocto/OpenEmbedded, cross-toolchains, root filesystem, device tree +- [ ] Bootloaders — U-Boot, MCUboot, boot stages, secure boot chain +- [ ] Bare-Metal vs RTOS vs Linux — decision framework, resource constraints, trade-offs +- [ ] Inter-Task Communication — message queues, event groups, mailboxes, shared memory, priority inversion +- [ ] *skip* OTA Updates — firmware update strategies, A/B partitions, rollback, delta updates +- [ ] Exercises & Projects — FreeRTOS multitask app, Zephyr sensor driver, Buildroot custom image + +### 6.3 Internet of Things +`path: iot/` +> Prereqs: Embedded Systems, Web Tech (backend), Networking + +- [ ] IoT Architecture — edge/fog/cloud, reference architectures, design patterns +- [ ] Connectivity — WiFi, BLE, LoRa/LoRaWAN, Zigbee, Thread, protocol comparison +- [ ] Messaging Protocols — MQTT (deep dive), CoAP, HTTP for IoT, WebSockets +- [ ] IoT Platforms — Home Assistant, Node-RED, Thingsboard, AWS IoT Core overview +- [ ] Data Pipeline — collection, time-series databases (InfluxDB), processing, dashboards (Grafana) +- [ ] Edge Computing — local processing, gateway design, edge inference, offline operation +- [ ] Security & Privacy — device identity, secure communication, firmware signing, threat landscape +- [ ] *skip* Standards — Matter, Thread, OPC-UA, industrial IoT +- [ ] Exercises & Projects — MQTT sensor network, LoRa node + gateway, home automation system + +--- + +## Phase 7 — Intelligence & Integration + +### 7.1 Computer Vision & Edge AI +`path: computer-vision/` +> Prereqs: Math (linear algebra, calculus, probability), Python, Embedded basics + +- [ ] Image Fundamentals — digital images, color spaces, histograms, geometric transforms +- [ ] Image Processing — convolution, filtering, morphology, edge detection, thresholding +- [ ] Feature Detection — corners (Harris, Shi-Tomasi), descriptors (SIFT/ORB), feature matching +- [ ] Classical CV — template matching, object detection (Haar/HOG), tracking, optical flow +- [ ] Deep Learning Foundations — neural networks, backprop, CNNs, training loop, PyTorch/TensorFlow +- [ ] Object Detection & Segmentation — YOLO, SSD, U-Net, Mask R-CNN, transfer learning +- [ ] Model Optimization — quantization, pruning, knowledge distillation, ONNX, TFLite +- [ ] Edge Deployment — Coral TPU, Jetson, OpenMV, RPi, NPU/VPU, inference frameworks +- [ ] *skip* Advanced Topics — 3D vision, depth estimation, visual SLAM, generative models +- [ ] Exercises & Projects — OpenCV pipeline, train a classifier, deploy on edge device, visual inspection + +### 7.2 Robotics +`path: robotics/` +> Prereqs: Math (linear algebra, calculus, diff eq), Electronics, Embedded, Programming (C++/Python) + +- [ ] Foundations — robot types, coordinate frames, homogeneous transforms, DH convention +- [ ] Kinematics — forward/inverse kinematics, Jacobian, velocity kinematics, workspace analysis +- [ ] Dynamics — Newton-Euler, Lagrangian mechanics, torque computation, gravity compensation +- [ ] Actuators & Sensors — DC/stepper/servo motors, encoders, IMUs, LIDAR, depth cameras +- [ ] Control Systems — PID control, state-space, trajectory generation, motion profiles +- [ ] Motion Planning — path planning (A*, RRT, PRM), obstacle avoidance, configuration space +- [ ] *skip* SLAM & Localization — EKF/particle filter, lidar SLAM, visual SLAM, sensor fusion +- [ ] ROS 2 — architecture, nodes, topics, services, actions, launch files, URDF, Gazebo +- [ ] *skip* Mobile Robots — wheeled (differential, omnidirectional), legged basics, drone basics +- [ ] Exercises & Projects — PID line follower, robotic arm control, ROS 2 navigation stack + +### 7.3 Rust +`path: programming/rust/` +> Prereqs: Programming Fundamentals, C (recommended) + +- [ ] Getting Started — cargo, crate structure, hello world, rustup, editions +- [ ] Ownership & Borrowing — ownership rules, references, lifetimes, borrow checker +- [ ] Type System — structs, enums, pattern matching, generics, traits, trait objects +- [ ] Error Handling — Result, Option, ? operator, custom errors, anyhow/thiserror +- [ ] Collections & Iterators — Vec, HashMap, iterator adaptors, closures, functional patterns +- [ ] Concurrency — threads, channels, Arc/Mutex, Send/Sync, async/await, tokio basics +- [ ] Unsafe & FFI — unsafe blocks, raw pointers, extern "C", bindgen, calling C from Rust +- [ ] Ecosystem — serde, clap, reqwest, embedded Rust (no_std), testing, benchmarking +- [ ] Exercises & Projects — CLI tool, concurrent web scraper, embedded Rust blinky, C FFI wrapper + +### 7.4 C++ +`path: programming/cpp/` +> Prereqs: C, Programming Fundamentals + +- [ ] C++ Basics — classes, constructors/destructors, namespaces, references, streams +- [ ] OOP — inheritance, polymorphism, virtual functions, abstract classes, RTTI +- [ ] Templates — function/class templates, template specialization, variadic templates, SFINAE basics +- [ ] STL — containers, iterators, algorithms, string, functional +- [ ] Modern C++ (17/20/23) — auto, structured bindings, ranges, concepts, modules, coroutines overview +- [ ] Memory Management — RAII, smart pointers (unique/shared/weak), move semantics, rule of 5 +- [ ] Concurrency — std::thread, mutex, condition_variable, atomic, futures, async +- [ ] *skip* Advanced — type erasure, CRTP, policy-based design, compile-time computation +- [ ] Exercises & Projects — matrix library, concurrent data structure, embedded C++ on MCU + +### 7.5 Advanced Languages +`path: programming/advanced-languages/` +> Prereqs: Programming Fundamentals, specific domain knowledge per language + +- [ ] Python Advanced — decorators, generators, metaclasses, asyncio, C extensions, type hints +- [ ] Lua — tables, metatables, coroutines, embedding in C, LuaJIT FFI, game scripting +- [ ] Julia — multiple dispatch, type system, macros, performance, scientific computing, GPU +- [ ] R — data frames, tidyverse, ggplot2, statistical modeling, R Markdown +- [ ] Cross-Language — FFI patterns, polyglot projects, language interop, choosing the right tool +- [ ] Exercises & Projects — Python C extension, Lua-embedded app, Julia scientific notebook, R analysis + +--- + +## Side Quest — Anytime + +### S.1 Global Geek Culture +`path: geek-culture/` +> Prereqs: None — read alongside any phase + +- [ ] Hacker Culture Origins — MIT AI Lab, Tech Model Railroad Club, PDP-1, hacker ethic, "Hackers" (Levy) +- [ ] Phone Phreaking & Early Hacking — Captain Crunch, blue boxes, 2600 magazine, WarGames, Legion of Doom +- [ ] The Jargon File & Hacker Lore — Jargon File, INTERCAL, RFC humor, koans, AI koans, quines, IOCCC +- [ ] Text File Underground — textfiles.com, e-zines (Phrack, LoD/H Technical Journal), BBS culture, ANSI art +- [ ] The Geek Code & .sig Culture — Geek Code blocks, ASCII art sigs, fortune files, Plan files, finger protocol +- [ ] Usenet & Early Internet Culture — flame wars, FAQs, killfiles, alt.* hierarchy, trolling as art, Eternal September +- [ ] Cryptography Wars & Cypherpunks — cypherpunk mailing list, PGP, Clipper chip, Zimmermann, remailers +- [ ] Demoscene — demo groups, size coding (4k/64k intros), Amiga/Atari scene, Revision/Assembly parties, pouet.net +- [ ] Open Source Movement — GNU Manifesto, FSF, Cathedral vs Bazaar, Linux, copyleft vs permissive, forking culture +- [ ] Warez & Underground Scenes — warez groups, NFOs, cracktros, courier culture, The Scene +- [ ] Internet Mysteries & Challenges — Cicada 3301, Webdriver Torso, Markovian Parallax Denigrate, crypto puzzles, ARGs +- [ ] MUDs, MOOs & Virtual Worlds — text adventures, LambdaMOO, online community origins, digital identity +- [ ] Hacker Conferences & Community — DEF CON, CCC, 2600 meetings, hackerspaces, CTF culture +- [ ] Maker & Hardware Hacking — hackerspaces, CCC hardware, Arduino/RPi, OSHW, right to repair, modding +- [ ] *skip* Cyberpunk & Sci-Fi Canon — Gibson, Stephenson, Dick, Doctorow, technology predictions +- [ ] *skip* Global Perspectives — CCC vs DEF CON culture, Shenzhen, Global South tech, internet freedom + +--- + +## Summary + +| # | Topic | Modules | Status | +|---|-------|---------|--------| +| 1.1 | Linux Command Line | 10 | Not started | +| 1.2 | Git | 9 | Not started | +| 1.3 | Technical Writing | 7 | Not started | +| 1.4 | Programming Fundamentals | 9 | Not started | +| 1.5 | C | 11 | Not started | +| 1.6 | Python | 10 | Not started | +| 1.7 | Bash Scripting | 8 | Not started | +| 1.8 | Mathematics | 12 | Not started | +| 2.1 | Computer Science | 10 | Not started | +| 2.2 | Electronics | 13 | Not started | +| 2.3 | Make & Dev Automation | 8 | Not started | +| 3.1 | Operating Systems | 11 | Not started | +| 3.2 | PCB Design | 10 | Not started | +| 3.3 | Web Technologies | 9 | Not started | +| 3.4 | Project Management | 8 | Not started | +| 4.1 | Cyber Security | 9 | Not started | +| 4.2 | Ansible | 8 | Not started | +| 4.3 | Automation | 8 | Not started | +| 5.1 | Embedded Systems | 14 | Not started | +| 5.2 | PCB Prototyping | 8 | Not started | +| 5.3 | Digital Fabrication | 8 | Not started | +| 6.1 | Kernel Development | 12 | Not started | +| 6.2 | Embedded OS & RTOS | 9 | Not started | +| 6.3 | IoT | 9 | Not started | +| 7.1 | Computer Vision & Edge AI | 10 | Not started | +| 7.2 | Robotics | 10 | Not started | +| 7.3 | Rust | 9 | Not started | +| 7.4 | C++ | 9 | Not started | +| 7.5 | Advanced Languages | 6 | Not started | +| S.1 | Geek Culture | 16 | Not started | diff --git a/README.md b/README.md new file mode 100644 index 0000000..a25b9ca --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# ubergeek + +A comprehensive, self-paced study guide covering electronics, embedded +systems, programming, computer science, cybersecurity, and more. Built as a +sourcehut wiki. + +## Topics + +30 topics across 7 phases — from command line basics to robotics and edge AI. +See [PROGRESS.md](PROGRESS.md) for the full topic list and study roadmap. + +## Structure + +Each topic follows the same layout: + +``` +topic-name/ +├── index.md # overview and module list +├── images/ # diagrams, schematics, screenshots +├── samples/ # runnable code and project files +└── module-name/ + └── index.md # module content +``` + +## Links + +- **Sourcehut (primary):** https://git.sr.ht/~gumxcc/ubergeek +- **Wiki:** https://man.sr.ht/~gumxcc/ubergeek/ + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on submitting patches, +pull requests, or merge requests. + +## License + +Content is [CC BY-SA 4.0](LICENSES/CC-BY-SA-4.0). Code samples are +[MIT](LICENSES/MIT). See [LICENSE.md](LICENSE.md) for details. diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..2973c2f --- /dev/null +++ b/build.sh @@ -0,0 +1,215 @@ +#!/bin/bash +# build.sh - ubergeek static site generator +# Converts the wiki markdown structure into HTML using lowdown + mo. + +[[ "$(basename "${0}")" == "build.sh" ]] || { echo "Do not source build.sh"; exit 1; } + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [[ ! -f "${SCRIPT_DIR}/build/mo" ]]; then + echo "[error] build/mo not found. Run: curl -sL https://raw.githubusercontent.com/tests-always-included/mo/master/mo -o build/mo && chmod +x build/mo" >&2 + exit 1 +fi +. "${SCRIPT_DIR}/build/mo" + +FORCE=false +while [[ $# -gt 0 ]]; do + case "$1" in + -f|--force) FORCE=true ;; + *) echo "[error] Unknown option: $1" >&2; exit 1 ;; + esac + shift +done + +TMPDIR_BUILD="$(mktemp -d)" +cleanup() { rm -rf "${TMPDIR_BUILD}"; } +trap cleanup EXIT INT TERM + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +get_frontmatter() { + local key="$1" file="$2" + lowdown -X "${key}" "${file}" 2>/dev/null || true +} + +check_has_code() { + local html="$1" + if echo "${html}" | grep -qE '
 "${find_list}"
+
+    while IFS= read -r -d '' node; do
+        if [[ -d "${node}" ]]; then
+            local subdir_title
+            subdir_title="$(get_frontmatter title "${node}/index.md" 2>/dev/null || true)"
+            [[ -z "${subdir_title}" ]] && subdir_title="$(basename "${node}")"
+            printf -- '- [%s](/%s)\n' "${subdir_title}" "${node}" >> "${list_file}"
+            process_dir "${node}"
+
+        elif [[ "${node}" == *.md && "$(basename "${node}")" != "index.md" ]]; then
+            local slug="${node%.md}"
+            local title date lang
+            title="$(get_frontmatter title "${node}")"
+            [[ -z "${title}" ]] && title="$(basename "${slug}")"
+            date="$(get_frontmatter date "${node}")"
+            lang="$(get_frontmatter lang "${node}")"
+
+            local out_html="output/${slug}/index.html"
+            local content
+            content="$(lowdown "${node}")"
+            check_has_code "${content}"
+            build_breadcrumbs "${slug}"
+
+            export PAGE_TITLE="${title}"
+            export CONTENT="${content}"
+            export BREADCRUMBS
+            export HEADER_EXTRA="${date}"
+            export META_DATE="${date}"
+            export META_DESCRIPTION=""
+            export HAS_CODE
+            export LANG="${lang:-en}"
+            export DIR=""
+
+            render_page "build/templates/page.html" "${out_html}"
+            printf -- '- [%s](/%s)\n' "${title}" "${slug}" >> "${list_file}"
+            echo "[built] ${out_html}"
+        fi
+    done < "${find_list}"
+
+    # Build this directory's index page
+    local index_src="${dir}/index.md"
+    local index_md="${TMPDIR_BUILD}/${dir//\//_}.index.md"
+    local dir_title
+
+    if [[ -f "${index_src}" ]]; then
+        dir_title="$(get_frontmatter title "${index_src}")"
+        cp "${index_src}" "${index_md}"
+    else
+        dir_title="$(basename "${dir}")"
+        printf '# %s\n\n' "${dir_title}" > "${index_md}"
+    fi
+    printf '\n\n' >> "${index_md}"
+    cat "${list_file}" >> "${index_md}"
+
+    local out_html="output/${dir}/index.html"
+    local content
+    content="$(lowdown "${index_md}")"
+    check_has_code "${content}"
+    build_breadcrumbs "${dir}"
+
+    export PAGE_TITLE="${dir_title}"
+    export CONTENT="${content}"
+    export BREADCRUMBS
+    export HEADER_EXTRA=""
+    export META_DATE=""
+    export META_DESCRIPTION=""
+    export HAS_CODE
+    export LANG="en"
+    export DIR=""
+
+    render_page "build/templates/page.html" "${out_html}"
+    echo "[built] output/${dir}/index.html"
+}
+
+# ---------------------------------------------------------------------------
+# Main build
+# ---------------------------------------------------------------------------
+
+mkdir -p output
+
+# Discover top-level topic directories (exclude hidden, build, output)
+topics_list="${TMPDIR_BUILD}/topics.list.md"
+: > "${topics_list}"
+
+find_list="${TMPDIR_BUILD}/toplevel.find.list"
+find . -mindepth 1 -maxdepth 1 -type d \
+    -not -name '.*' -not -name 'build' -not -name 'output' \
+    -print0 | sort -z > "${find_list}"
+
+while IFS= read -r -d '' topic; do
+    topic="${topic#./}"
+    topic_title="$(get_frontmatter title "${topic}/index.md" 2>/dev/null || true)"
+    [[ -z "${topic_title}" ]] && topic_title="${topic}"
+    printf -- '- [%s](/%s)\n' "${topic_title}" "${topic}" >> "${topics_list}"
+    process_dir "${topic}"
+done < "${find_list}"
+
+# Home page — README.md + topic listing
+home_md="${TMPDIR_BUILD}/home.md"
+cp README.md "${home_md}"
+printf '\n\n## topics:\n\n' >> "${home_md}"
+cat "${topics_list}" >> "${home_md}"
+
+content="$(lowdown "${home_md}")"
+check_has_code "${content}"
+
+export PAGE_TITLE=""
+export CONTENT="${content}"
+export BREADCRUMBS=""
+export HEADER_EXTRA=""
+export META_DATE=""
+export META_DESCRIPTION=""
+export HAS_CODE
+export LANG="en"
+export DIR=""
+
+render_page "build/templates/home.html" "output/index.html"
+echo "[built] output/index.html"
+
+# License page
+if [[ -f "LICENSE.md" ]]; then
+    content="$(lowdown LICENSE.md)"
+    check_has_code "${content}"
+    export PAGE_TITLE="license"
+    export CONTENT="${content}"
+    export BREADCRUMBS=" / license"
+    export HEADER_EXTRA=""
+    export META_DATE=""
+    export META_DESCRIPTION=""
+    export HAS_CODE
+    export LANG="en"
+    export DIR=""
+    render_page "build/templates/page.html" "output/license/index.html"
+    echo "[built] output/license/index.html"
+fi
+
+echo ""
+echo "[done] Build complete. Output in output/"
diff --git a/build/mo b/build/mo
new file mode 100755
index 0000000..8247a2d
--- /dev/null
+++ b/build/mo
@@ -0,0 +1,2037 @@
+#!/usr/bin/env bash
+#
+#/ Mo is a mustache template rendering software written in bash.  It inserts
+#/ environment variables into templates.
+#/
+#/ Simply put, mo will change {{VARIABLE}} into the value of that
+#/ environment variable.  You can use {{#VARIABLE}}content{{/VARIABLE}} to
+#/ conditionally display content or iterate over the values of an array.
+#/
+#/ Learn more about mustache templates at https://mustache.github.io/
+#/
+#/ Simple usage:
+#/
+#/    mo [OPTIONS] filenames...
+#/
+#/ Options:
+#/
+#/    --allow-function-arguments
+#/          Permit functions to be called with additional arguments. Otherwise,
+#/          the only way to get access to the arguments is to use the
+#/          MO_FUNCTION_ARGS environment variable.
+#/    -d, --debug
+#/          Enable debug logging to stderr.
+#/    -u, --fail-not-set
+#/          Fail upon expansion of an unset variable. Will silently ignore by
+#/          default. Alternately, set MO_FAIL_ON_UNSET to a non-empty value.
+#/    -x, --fail-on-function
+#/          Fail when a function returns a non-zero status code instead of
+#/          silently ignoring it. Alternately, set MO_FAIL_ON_FUNCTION to a
+#/          non-empty value.
+#/    -f, --fail-on-file
+#/          Fail when a file (from command-line or partial) does not exist.
+#/          Alternately, set MO_FAIL_ON_FILE to a non-empty value.
+#/    -e, --false
+#/          Treat the string "false" as empty for conditionals. Alternately,
+#/          set MO_FALSE_IS_EMPTY to a non-empty value.
+#/    -h, --help
+#/          This message.
+#/    -s=FILE, --source=FILE
+#/          Load FILE into the environment before processing templates.
+#/          Can be used multiple times. The file must be a valid shell script
+#/          and should only contain variable assignments.
+#/    -o=DELIM, --open=DELIM
+#/          Set the opening delimiter. Default is "{{".
+#/    -c=DELIM, --close=DELIM
+#/          Set the closing delimiter. Default is "}}".
+#/    --    Indicate the end of options. All arguments after this will be
+#/          treated as filenames only. Use when filenames may start with
+#/          hyphens.
+#/
+#/ Mo uses the following environment variables:
+#/
+#/ MO_ALLOW_FUNCTION_ARGUMENTS - When set to a non-empty value, this allows
+#/     functions referenced in templates to receive additional options and
+#/     arguments.
+#/ MO_CLOSE_DELIMITER - The string used when closing a tag. Defaults to "}}".
+#/     Used internally.
+#/ MO_CLOSE_DELIMITER_DEFAULT - The default value of MO_CLOSE_DELIMITER. Used
+#/     when resetting the close delimiter, such as when parsing a partial.
+#/ MO_CURRENT - Variable name to use for ".".
+#/ MO_DEBUG - When set to a non-empty value, additional debug information is
+#/     written to stderr.
+#/ MO_FUNCTION_ARGS - Arguments passed to the function.
+#/ MO_FAIL_ON_FILE - If a filename from the command-line is missing or a
+#/     partial does not exist, abort with an error.
+#/ MO_FAIL_ON_FUNCTION - If a function returns a non-zero status code, abort
+#/     with an error.
+#/ MO_FAIL_ON_UNSET - When set to a non-empty value, expansion of an unset env
+#/     variable will be aborted with an error.
+#/ MO_FALSE_IS_EMPTY - When set to a non-empty value, the string "false" will
+#/     be treated as an empty value for the purposes of conditionals.
+#/ MO_OPEN_DELIMITER - The string used when opening a tag. Defaults to "{{".
+#/     Used internally.
+#/ MO_OPEN_DELIMITER_DEFAULT - The default value of MO_OPEN_DELIMITER. Used
+#/     when resetting the open delimiter, such as when parsing a partial.
+#/ MO_ORIGINAL_COMMAND - Used to find the `mo` program in order to generate a
+#/     help message.
+#/ MO_PARSED - Content that has made it through the template engine.
+#/ MO_STANDALONE_CONTENT - The unparsed content that preceeded the current tag.
+#/     When a standalone tag is encountered, this is checked to see if it only
+#/     contains whitespace. If this and the whitespace condition after a tag is
+#/     met, then this will be reset to $'\n'.
+#/ MO_UNPARSED - Template content yet to make it through the parser.
+#/
+#/ Mo is under a MIT style licence with an additional non-advertising clause.
+#/ See LICENSE.md for the full text.
+#/
+#/ This is open source!  Please feel free to contribute.
+#/
+#/ https://github.com/tests-always-included/mo
+
+#: Disable these warnings for the entire file
+#:
+#: VAR_NAME was modified in a subshell. That change might be lost.
+# shellcheck disable=SC2031
+#:
+#: Modification of VAR_NAME is local (to subshell caused by (..) group).
+# shellcheck disable=SC2030
+
+# Public: Template parser function.  Writes templates to stdout.
+#
+# $0 - Name of the mo file, used for getting the help message.
+# $@ - Filenames to parse.
+#
+# Returns nothing.
+mo() (
+    local moSource moFiles moDoubleHyphens moParsed moContent
+
+    #: This function executes in a subshell; IFS is reset at the end.
+    IFS=$' \n\t'
+
+    #: Enable a strict mode. This is also reset at the end.
+    set -eEu -o pipefail
+    moFiles=()
+    moDoubleHyphens=false
+    MO_OPEN_DELIMITER_DEFAULT="{{"
+    MO_CLOSE_DELIMITER_DEFAULT="}}"
+    MO_FUNCTION_CACHE_HIT=()
+    MO_FUNCTION_CACHE_MISS=()
+
+    if [[ $# -gt 0 ]]; then
+        for arg in "$@"; do
+            if $moDoubleHyphens; then
+                #: After we encounter two hyphens together, all the rest
+                #: of the arguments are files.
+                moFiles=(${moFiles[@]+"${moFiles[@]}"} "$arg")
+            else
+                case "$arg" in
+                    -h|--h|--he|--hel|--help|-\?)
+                        mo::usage "$0"
+                        exit 0
+                        ;;
+
+                    --allow-function-arguments)
+                        MO_ALLOW_FUNCTION_ARGUMENTS=true
+                        ;;
+
+                    -u | --fail-not-set)
+                        MO_FAIL_ON_UNSET=true
+                        ;;
+
+                    -x | --fail-on-function)
+                        MO_FAIL_ON_FUNCTION=true
+                        ;;
+
+                    -p | --fail-on-file)
+                        MO_FAIL_ON_FILE=true
+                        ;;
+
+                    -e | --false)
+                        MO_FALSE_IS_EMPTY=true
+                        ;;
+
+                    -s=* | --source=*)
+                        if [[ "$arg" == --source=* ]]; then
+                            moSource="${arg#--source=}"
+                        else
+                            moSource="${arg#-s=}"
+                        fi
+
+                        if [[ -e "$moSource" ]]; then
+                            # shellcheck disable=SC1090
+                            . "$moSource"
+                        else
+                            echo "No such file: $moSource" >&2
+                            exit 1
+                        fi
+                        ;;
+
+                    -o=* | --open=*)
+                        if [[ "$arg" == --open=* ]]; then
+                            MO_OPEN_DELIMITER_DEFAULT="${arg#--open=}"
+                        else
+                            MO_OPEN_DELIMITER_DEFAULT="${arg#-o=}"
+                        fi
+                        ;;
+
+                    -c=* | --close=*)
+                        if [[ "$arg" == --close=* ]]; then
+                            MO_CLOSE_DELIMITER_DEFAULT="${arg#--close=}"
+                        else
+                            MO_CLOSE_DELIMITER_DEFAULT="${arg#-c=}"
+                        fi
+                        ;;
+
+                    -d | --debug)
+                        MO_DEBUG=true
+                        ;;
+
+                    --)
+                        #: Set a flag indicating we've encountered double hyphens
+                        moDoubleHyphens=true
+                        ;;
+
+                    -*)
+                        mo::error "Unknown option: $arg (See --help for options)"
+                        ;;
+
+                    *)
+                        #: Every arg that is not a flag or a option should be a file
+                        moFiles=(${moFiles[@]+"${moFiles[@]}"} "$arg")
+                        ;;
+                esac
+            fi
+        done
+    fi
+
+    mo::debug "Debug enabled"
+    MO_OPEN_DELIMITER="$MO_OPEN_DELIMITER_DEFAULT"
+    MO_CLOSE_DELIMITER="$MO_CLOSE_DELIMITER_DEFAULT"
+    mo::content moContent ${moFiles[@]+"${moFiles[@]}"} || return 1
+    mo::parse moParsed "$moContent"
+    echo -n "$moParsed"
+)
+
+
+# Internal: Show a debug message
+#
+# $1 - The debug message to show
+#
+# Returns nothing.
+mo::debug() {
+    if [[ -n "${MO_DEBUG:-}" ]]; then
+        echo "DEBUG ${FUNCNAME[1]:-?} - $1" >&2
+    fi
+}
+
+
+# Internal: Show a debug message and internal state information
+#
+# No arguments
+#
+# Returns nothing.
+mo::debugShowState() {
+    if [[ -z "${MO_DEBUG:-}" ]]; then
+        return
+    fi
+   
+    local moState moTemp moIndex moDots
+
+    mo::escape moTemp "$MO_OPEN_DELIMITER"
+    moState="open: $moTemp"
+    mo::escape moTemp "$MO_CLOSE_DELIMITER"
+    moState="$moState  close: $moTemp"
+    mo::escape moTemp "$MO_STANDALONE_CONTENT"
+    moState="$moState  standalone: $moTemp"
+    mo::escape moTemp "$MO_CURRENT"
+    moState="$moState  current: $moTemp"
+    moIndex=$((${#MO_PARSED} - 20))
+    moDots=...
+
+    if [[ "$moIndex" -lt 0 ]]; then
+        moIndex=0
+        moDots=
+    fi
+
+    mo::escape moTemp "${MO_PARSED:$moIndex}"
+    moState="$moState  parsed: $moDots$moTemp"
+
+    moDots=...
+    
+    if [[ "${#MO_UNPARSED}" -le 20 ]]; then
+        moDots=
+    fi
+
+    mo::escape moTemp "${MO_UNPARSED:0:20}$moDots"
+    moState="$moState  unparsed: $moTemp"
+
+    echo "DEBUG ${FUNCNAME[1]:-?} - $moState" >&2
+}
+
+# Internal: Show an error message and exit
+#
+# $1 - The error message to show
+# $2 - Error code
+#
+# Returns nothing. Exits the program.
+mo::error() {
+    echo "ERROR: $1" >&2
+    exit "${2:-1}"
+}
+
+
+# Internal: Show an error message with a snippet of context and exit
+#
+# $1 - The error message to show
+# $2 - The starting point
+# $3 - Error code
+#
+# Returns nothing. Exits the program.
+mo::errorNear() {
+    local moEscaped
+
+    mo::escape moEscaped "${2:0:40}"
+    echo "ERROR: $1" >&2
+    echo "ERROR STARTS NEAR: $moEscaped"
+    exit "${3:-1}"
+}
+
+
+# Internal: Displays the usage for mo.  Pulls this from the file that
+# contained the `mo` function.  Can only work when the right filename
+# comes is the one argument, and that only happens when `mo` is called
+# with `$0` set to this file.
+#
+# $1 - Filename that has the help message
+#
+# Returns nothing.
+mo::usage() {
+    while read -r line; do
+        if [[ "${line:0:2}" == "#/" ]]; then
+            echo "${line:3}"
+        fi
+    done < "$MO_ORIGINAL_COMMAND"
+    echo ""
+    echo "MO_VERSION=$MO_VERSION"
+}
+
+
+# Internal: Fetches the content to parse into MO_UNPARSED.  Can be a list of
+# partials for files or the content from stdin.
+#
+# $1 - Destination variable name
+# $2-@ - File names (optional), read from stdin otherwise
+#
+# Returns nothing.
+mo::content() {
+    local moTarget moContent moFilename
+
+    moTarget=$1
+    shift
+    moContent=""
+
+    if [[ "${#@}" -gt 0 ]]; then
+        for moFilename in "$@"; do
+            mo::debug "Using template to load content from file: $moFilename"
+            #: This is so relative paths work from inside template files
+            moContent="$moContent$MO_OPEN_DELIMITER>$moFilename$MO_CLOSE_DELIMITER"
+        done
+    else
+        mo::debug "Will read content from stdin"
+        mo::contentFile moContent || return 1
+    fi
+    
+    local "$moTarget" && mo::indirect "$moTarget" "$moContent"
+}
+
+
+# Internal: Read a file into MO_UNPARSED.
+#
+# $1 - Destination variable name.
+# $2 - Filename to load - if empty, defaults to /dev/stdin
+#
+# Returns nothing.
+mo::contentFile() {
+    local moFile moResult moContent
+
+    #: The subshell removes any trailing newlines.  We forcibly add
+    #: a dot to the content to preserve all newlines. Reading from
+    #: stdin with a `read` loop does not work as expected, so `cat`
+    #: needs to stay.
+    moFile=${2:-/dev/stdin}
+
+    if [[ -e "$moFile" ]]; then
+        mo::debug "Loading content: $moFile"
+        moContent=$(
+            set +Ee
+            cat -- "$moFile"
+            moResult=$?
+            echo -n '.'
+            exit "$moResult"
+        ) || return 1
+        moContent=${moContent%.}  #: Remove last dot
+    elif [[ -n "${MO_FAIL_ON_FILE-}" ]]; then
+        mo::error "No such file: $moFile"
+    else
+        mo::debug "File does not exist: $moFile"
+        moContent=""
+    fi
+
+    local "$1" && mo::indirect "$1" "$moContent"
+}
+
+
+# Internal: Send a variable up to the parent of the caller of this function.
+#
+# $1 - Variable name
+# $2 - Value
+#
+# Examples
+#
+#   callFunc () {
+#       local "$1" && mo::indirect "$1" "the value"
+#   }
+#   callFunc dest
+#   echo "$dest"  # writes "the value"
+#
+# Returns nothing.
+mo::indirect() {
+    unset -v "$1"
+    printf -v "$1" '%s' "$2"
+}
+
+
+# Internal: Send an array as a variable up to caller of a function
+#
+# $1 - Variable name
+# $2-@ - Array elements
+#
+# Examples
+#
+#   callFunc () {
+#       local myArray=(one two three)
+#       local "$1" && mo::indirectArray "$1" "${myArray[@]}"
+#   }
+#   callFunc dest
+#   echo "${dest[@]}" # writes "one two three"
+#
+# Returns nothing.
+mo::indirectArray() {
+    unset -v "$1"
+
+    #: IFS must be set to a string containing space or unset in order for
+    #: the array slicing to work regardless of the current IFS setting on
+    #: bash 3.  This is detailed further at
+    #: https://github.com/fidian/gg-core/pull/7
+    eval "$(printf "IFS= %s=(\"\${@:2}\") IFS=%q" "$1" "$IFS")"
+}
+
+
+# Internal: Trim leading characters from MO_UNPARSED
+#
+# Returns nothing.
+mo::trimUnparsed() {
+    local moI moC
+
+    moI=0
+    moC=${MO_UNPARSED:0:1}
+
+    while [[ "$moC" == " " || "$moC" == $'\r' || "$moC" == $'\n' || "$moC" == $'\t' ]]; do
+        moI=$((moI + 1))
+        moC=${MO_UNPARSED:$moI:1}
+    done
+
+    if [[ "$moI" != 0 ]]; then
+        MO_UNPARSED=${MO_UNPARSED:$moI}
+    fi
+}
+
+
+# Internal: Remove whitespace and content after whitespace
+#
+# $1 - Name of the destination variable
+# $2 - The string to chomp
+#
+# Returns nothing.
+mo::chomp() {
+    local moTemp moR moN moT
+
+    moR=$'\r'
+    moN=$'\n'
+    moT=$'\t'
+    moTemp=${2%% *}
+    moTemp=${moTemp%%"$moR"*}
+    moTemp=${moTemp%%"$moN"*}
+    moTemp=${moTemp%%"$moT"*}
+
+    local "$1" && mo::indirect "$1" "$moTemp"
+}
+
+
+# Public: Parses text, interpolates mustache tags. Utilizes the current value
+# of MO_OPEN_DELIMITER, MO_CLOSE_DELIMITER, and MO_STANDALONE_CONTENT. Those
+# three variables shouldn't be changed by user-defined functions.
+#
+# $1 - Destination variable name - where to store the finished content
+# $2 - Content to parse
+# $3 - Preserve standalone status/content - truthy if not empty. When set to a
+#      value, that becomes the standalone content value
+#
+# Returns nothing.
+mo::parse() {
+    local moOldParsed moOldStandaloneContent moOldUnparsed moResult
+
+    #: The standalone content is a trick to make the standalone tag detection
+    #: possible. When it's set to content with a newline and if the tag supports
+    #: it, the standalone content check happens. This check ensures only
+    #: whitespace is after the last newline up to the tag, and only whitespace
+    #: is after the tag up to the next newline. If that is the case, remove
+    #: whitespace and the trailing newline. By setting this to $'\n', we're
+    #: saying we are at the beginning of content.
+    mo::debug "Starting parse of ${#2} bytes"
+    moOldParsed=${MO_PARSED:-}
+    moOldUnparsed=${MO_UNPARSED:-}
+    MO_PARSED=""
+    MO_UNPARSED="$2"
+
+    if [[ -z "${3:-}" ]]; then
+        moOldStandaloneContent=${MO_STANDALONE_CONTENT:-}
+        MO_STANDALONE_CONTENT=$'\n'
+    else
+        MO_STANDALONE_CONTENT=$3
+    fi
+
+    MO_CURRENT=${MO_CURRENT:-}
+    mo::parseInternal
+    moResult="$MO_PARSED$MO_UNPARSED"
+    MO_PARSED=$moOldParsed
+    MO_UNPARSED=$moOldUnparsed
+
+    if [[ -z "${3:-}" ]]; then
+        MO_STANDALONE_CONTENT=$moOldStandaloneContent
+    fi
+
+    local "$1" && mo::indirect "$1" "$moResult"
+}
+
+
+# Internal: Parse MO_UNPARSED, writing content to MO_PARSED. Interpolates
+# mustache tags.
+#
+# No arguments
+#
+# Returns nothing.
+mo::parseInternal() {
+    local moChunk
+
+    mo::debug "Starting parse"
+
+    while [[ -n "$MO_UNPARSED" ]]; do
+        mo::debugShowState
+        moChunk=${MO_UNPARSED%%"$MO_OPEN_DELIMITER"*}
+        MO_PARSED="$MO_PARSED$moChunk"
+        MO_STANDALONE_CONTENT="$MO_STANDALONE_CONTENT$moChunk"
+        MO_UNPARSED=${MO_UNPARSED:${#moChunk}}
+
+        if [[ -n "$MO_UNPARSED" ]]; then
+            MO_UNPARSED=${MO_UNPARSED:${#MO_OPEN_DELIMITER}}
+            mo::trimUnparsed
+
+            case "$MO_UNPARSED" in
+                '#'*)
+                    #: Loop, if/then, or pass content through function
+                    mo::parseBlock false
+                    ;;
+
+                '^'*)
+                    #: Display section if named thing does not exist
+                    mo::parseBlock true
+                    ;;
+
+                '>'*)
+                    #: Load partial - get name of file relative to cwd
+                    mo::parsePartial
+                    ;;
+
+                '/'*)
+                    #: Closing tag
+                    mo::errorNear "Unbalanced close tag" "$MO_UNPARSED"
+                    ;;
+
+                '!'*)
+                    #: Comment - ignore the tag content entirely
+                    mo::parseComment
+                    ;;
+
+                '='*)
+                    #: Change delimiters
+                    #: Any two non-whitespace sequences separated by whitespace.
+                    mo::parseDelimiter
+                    ;;
+
+                '&'*)
+                    #: Unescaped - mo doesn't escape/unescape
+                    MO_UNPARSED=${MO_UNPARSED#&}
+                    mo::trimUnparsed
+                    mo::parseValue
+                    ;;
+
+                *)
+                    #: Normal environment variable, string, subexpression,
+                    #: current value, key, or function call
+                    mo::parseValue
+                    ;;
+            esac
+        fi
+    done
+}
+
+
+# Internal: Handle parsing a block
+#
+# $1 - Invert condition ("true" or "false")
+#
+# Returns nothing
+mo::parseBlock() {
+    local moInvertBlock moTokens moTokensString
+
+    moInvertBlock=$1
+    MO_UNPARSED=${MO_UNPARSED:1}
+    mo::tokenizeTagContents moTokens "$MO_CLOSE_DELIMITER"
+    MO_UNPARSED=${MO_UNPARSED#"$MO_CLOSE_DELIMITER"}
+    mo::tokensToString moTokensString "${moTokens[@]:1}"
+    mo::debug "Parsing block: $moTokensString"
+
+    if mo::standaloneCheck; then
+        mo::standaloneProcess
+    fi
+
+    if [[ "${moTokens[1]}" == "NAME" ]] && mo::isFunction "${moTokens[2]}"; then
+        mo::parseBlockFunction "$moInvertBlock" "$moTokensString" "${moTokens[@]:1}"
+    elif [[ "${moTokens[1]}" == "NAME" ]] && mo::isArray "${moTokens[2]}"; then
+        mo::parseBlockArray "$moInvertBlock" "$moTokensString" "${moTokens[@]:1}"
+    else
+        mo::parseBlockValue "$moInvertBlock" "$moTokensString" "${moTokens[@]:1}"
+    fi
+}
+
+
+# Internal: Handle parsing a block whose first argument is a function
+#
+# $1 - Invert condition ("true" or "false")
+# $2-@ - The parsed tokens from inside the block tags
+#
+# Returns nothing
+mo::parseBlockFunction() {
+    local moTarget moInvertBlock moTokens moTemp moUnparsed moTokensString
+
+    moInvertBlock=$1
+    moTokensString=$2
+    shift 2
+    moTokens=(${@+"$@"})
+    mo::debug "Parsing block function: $moTokensString"
+    mo::getContentUntilClose moTemp "$moTokensString"
+    #: Pass unparsed content to the function.
+    #: Keep the updated delimiters if they changed.
+
+    if [[ "$moInvertBlock" != "true" ]]; then
+        mo::evaluateFunction moResult "$moTemp" "${moTokens[@]:1}"
+        MO_PARSED="$MO_PARSED$moResult"
+    fi
+
+    mo::debug "Done parsing block function: $moTokensString"
+}
+
+
+# Internal: Handle parsing a block whose first argument is an array
+#
+# $1 - Invert condition ("true" or "false")
+# $2-@ - The parsed tokens from inside the block tags
+#
+# Returns nothing
+mo::parseBlockArray() {
+    local moInvertBlock moTokens moResult moArrayName moArrayIndexes moArrayIndex moTemp moUnparsed moOpenDelimiterBefore moCloseDelimiterBefore moOpenDelimiterAfter moCloseDelimiterAfter moParsed moTokensString moCurrent
+
+    moInvertBlock=$1
+    moTokensString=$2
+    shift 2
+    moTokens=(${@+"$@"})
+    mo::debug "Parsing block array: $moTokensString"
+    moOpenDelimiterBefore=$MO_OPEN_DELIMITER
+    moCloseDelimiterBefore=$MO_CLOSE_DELIMITER
+    mo::getContentUntilClose moTemp "$moTokensString"
+    moOpenDelimiterAfter=$MO_OPEN_DELIMITER
+    moCloseDelimiterAfter=$MO_CLOSE_DELIMITER
+    moArrayName=${moTokens[1]}
+    eval "moArrayIndexes=(\"\${!${moArrayName}[@]}\")"
+    
+    if [[ "${#moArrayIndexes[@]}" -lt 1 ]]; then
+        #: No elements
+        if [[ "$moInvertBlock" == "true" ]]; then
+            #: Restore the delimiter before parsing
+            MO_OPEN_DELIMITER=$moOpenDelimiterBefore
+            MO_CLOSE_DELIMITER=$moCloseDelimiterBefore
+            moCurrent=$MO_CURRENT
+            MO_CURRENT=$moArrayName
+            mo::parse moParsed "$moTemp" "blockArrayInvert$MO_STANDALONE_CONTENT"
+            MO_CURRENT=$moCurrent
+            MO_PARSED="$MO_PARSED$moParsed"
+        fi
+    else
+        if [[ "$moInvertBlock" != "true" ]]; then
+            #: Process for each element in the array
+            moUnparsed=$MO_UNPARSED
+
+            for moArrayIndex in "${moArrayIndexes[@]}"; do
+                #: Restore the delimiter before parsing
+                MO_OPEN_DELIMITER=$moOpenDelimiterBefore
+                MO_CLOSE_DELIMITER=$moCloseDelimiterBefore
+                moCurrent=$MO_CURRENT
+                MO_CURRENT=$moArrayName.$moArrayIndex
+                mo::debug "Iterate over array using element: $MO_CURRENT"
+                mo::parse moParsed "$moTemp" "blockArray$MO_STANDALONE_CONTENT"
+                MO_CURRENT=$moCurrent
+                MO_PARSED="$MO_PARSED$moParsed"
+            done
+
+            MO_UNPARSED=$moUnparsed
+        fi
+    fi
+
+    MO_OPEN_DELIMITER=$moOpenDelimiterAfter
+    MO_CLOSE_DELIMITER=$moCloseDelimiterAfter
+    mo::debug "Done parsing block array: $moTokensString"
+}
+
+
+# Internal: Handle parsing a block whose first argument is a value
+#
+# $1 - Invert condition ("true" or "false")
+# $2-@ - The parsed tokens from inside the block tags
+#
+# Returns nothing
+mo::parseBlockValue() {
+    local moInvertBlock moTokens moResult moUnparsed moOpenDelimiterBefore moOpenDelimiterAfter moCloseDelimiterBefore moCloseDelimiterAfter moParsed moTemp moTokensString moCurrent
+
+    moInvertBlock=$1
+    moTokensString=$2
+    shift 2
+    moTokens=(${@+"$@"})
+    mo::debug "Parsing block value: $moTokensString"
+    moOpenDelimiterBefore=$MO_OPEN_DELIMITER
+    moCloseDelimiterBefore=$MO_CLOSE_DELIMITER
+    mo::getContentUntilClose moTemp "$moTokensString"
+    moOpenDelimiterAfter=$MO_OPEN_DELIMITER
+    moCloseDelimiterAfter=$MO_CLOSE_DELIMITER
+
+    #: Variable, value, or list of mixed things
+    mo::evaluateListOfSingles moResult "${moTokens[@]}"
+
+    if mo::isTruthy "$moResult" "$moInvertBlock"; then
+        mo::debug "Block is truthy: $moResult"
+        #: Restore the delimiter before parsing
+        MO_OPEN_DELIMITER=$moOpenDelimiterBefore
+        MO_CLOSE_DELIMITER=$moCloseDelimiterBefore
+        moCurrent=$MO_CURRENT
+        MO_CURRENT=${moTokens[1]}
+        mo::parse moParsed "$moTemp" "blockValue$MO_STANDALONE_CONTENT"
+        MO_PARSED="$MO_PARSED$moParsed"
+        MO_CURRENT=$moCurrent
+    fi
+
+    MO_OPEN_DELIMITER=$moOpenDelimiterAfter
+    MO_CLOSE_DELIMITER=$moCloseDelimiterAfter
+    mo::debug "Done parsing block value: $moTokensString"
+}
+
+
+# Internal: Handle parsing a partial
+#
+# No arguments.
+#
+# Indentation will be applied to the entire partial's contents before parsing.
+# This indentation is based on the whitespace that ends the previously parsed
+# content.
+#
+# Returns nothing
+mo::parsePartial() {
+    local moFilename moResult moIndentation moN moR moTemp moT
+
+    MO_UNPARSED=${MO_UNPARSED:1}
+    mo::trimUnparsed
+    mo::chomp moFilename "${MO_UNPARSED%%"$MO_CLOSE_DELIMITER"*}"
+    MO_UNPARSED="${MO_UNPARSED#*"$MO_CLOSE_DELIMITER"}"
+    moIndentation=""
+
+    if mo::standaloneCheck; then
+        moN=$'\n'
+        moR=$'\r'
+        moT=$'\t'
+        moIndentation="$moN${MO_PARSED//"$moR"/"$moN"}"
+        moIndentation=${moIndentation##*"$moN"}
+        moTemp=${moIndentation// }
+        moTemp=${moTemp//"$moT"}
+
+        if [[ -n "$moTemp" ]]; then
+            moIndentation=
+        fi
+
+        mo::debug "Adding indentation to partial: '$moIndentation'"
+        mo::standaloneProcess
+    fi
+
+    mo::debug "Parsing partial: $moFilename"
+
+    #: Execute in subshell to preserve current cwd and environment
+    moResult=$(
+        #: It would be nice to remove `dirname` and use a function instead,
+        #: but that is difficult when only given filenames.
+        cd "$(dirname -- "$moFilename")" || exit 1
+        echo "$(
+            local moPartialContent moPartialParsed
+
+            if ! mo::contentFile moPartialContent "${moFilename##*/}"; then
+                exit 1
+            fi
+
+            #: Reset delimiters before parsing
+            mo::indentLines moPartialContent "$moIndentation" "$moPartialContent"
+            MO_OPEN_DELIMITER="$MO_OPEN_DELIMITER_DEFAULT"
+            MO_CLOSE_DELIMITER="$MO_CLOSE_DELIMITER_DEFAULT"
+            mo::parse moPartialParsed "$moPartialContent"
+
+            #: Fix bash handling of subshells and keep trailing whitespace.
+            echo -n "$moPartialParsed."
+        )" || exit 1
+    ) || exit 1
+
+    if [[ -z "$moResult" ]]; then
+        mo::debug "Error detected when trying to read the file"
+        exit 1
+    fi
+
+    MO_PARSED="$MO_PARSED${moResult%.}"
+}
+
+
+# Internal: Handle parsing a comment
+#
+# No arguments.
+#
+# Returns nothing
+mo::parseComment() {
+    local moContent moContent
+
+    MO_UNPARSED=${MO_UNPARSED#*"$MO_CLOSE_DELIMITER"}
+    mo::debug "Parsing comment"
+
+    if mo::standaloneCheck; then
+        mo::standaloneProcess
+    fi
+}
+
+
+# Internal: Handle parsing the change of delimiters
+#
+# No arguments.
+#
+# Returns nothing
+mo::parseDelimiter() {
+    local moContent moOpen moClose
+
+    MO_UNPARSED=${MO_UNPARSED:1}
+    mo::trimUnparsed
+    mo::chomp moOpen "$MO_UNPARSED"
+    MO_UNPARSED=${MO_UNPARSED:${#moOpen}}
+    mo::trimUnparsed
+    mo::chomp moClose "${MO_UNPARSED%%="$MO_CLOSE_DELIMITER"*}"
+    MO_UNPARSED=${MO_UNPARSED#*="$MO_CLOSE_DELIMITER"}
+    mo::debug "Parsing delimiters: $moOpen $moClose"
+
+    if mo::standaloneCheck; then
+        mo::standaloneProcess
+    fi
+
+    MO_OPEN_DELIMITER="$moOpen"
+    MO_CLOSE_DELIMITER="$moClose"
+}
+
+
+# Internal: Handle parsing value or function call
+#
+# No arguments.
+#
+# Returns nothing
+mo::parseValue() {
+    local moUnparsedOriginal moTokens
+
+    moUnparsedOriginal=$MO_UNPARSED
+    mo::tokenizeTagContents moTokens "$MO_CLOSE_DELIMITER"
+    mo::evaluate moResult "${moTokens[@]:1}"
+    MO_PARSED="$MO_PARSED$moResult"
+
+    if [[ "${MO_UNPARSED:0:${#MO_CLOSE_DELIMITER}}" != "$MO_CLOSE_DELIMITER" ]]; then
+        mo::errorNear "Did not find closing tag" "$moUnparsedOriginal"
+    fi
+
+    if mo::standaloneCheck; then
+        mo::standaloneProcess
+    fi
+
+    MO_UNPARSED=${MO_UNPARSED:${#MO_CLOSE_DELIMITER}}
+}
+
+
+# Internal: Determine if the given name is a defined function.
+#
+# $1 - Function name to check
+#
+# Be extremely careful.  Even if strict mode is enabled, it is not honored
+# in newer versions of Bash.  Any errors that crop up here will not be
+# caught automatically.
+#
+# Examples
+#
+#   moo () {
+#       echo "This is a function"
+#   }
+#   if mo::isFunction moo; then
+#       echo "moo is a defined function"
+#   fi
+#
+# Returns 0 if the name is a function, 1 otherwise.
+mo::isFunction() {
+    local moFunctionName
+
+    # Need to test for the array length, otherwise Mac will report an
+    # unbound variable
+    if [[ "${#MO_FUNCTION_CACHE_HIT[@]}" -gt 0 ]]; then
+        for moFunctionName in "${MO_FUNCTION_CACHE_HIT[@]}"; do
+            if [[ "$moFunctionName" == "$1" ]]; then
+                return 0
+            fi
+        done
+    fi
+
+    if [[ "${#MO_FUNCTION_CACHE_MISS[@]}" -gt 0 ]]; then
+        for moFunctionName in "${MO_FUNCTION_CACHE_MISS[@]}"; do
+            if [[ "$moFunctionName" == "$1" ]]; then
+                return 1
+            fi
+        done
+    fi
+
+    if declare -F "$1" &> /dev/null; then
+        if [[ "${#MO_FUNCTION_CACHE_HIT[@]}" -gt 0 ]]; then
+            MO_FUNCTION_CACHE_HIT=( ${MO_FUNCTION_CACHE_HIT[@]+"${MO_FUNCTION_CACHE_HIT[@]}"} "$1" )
+        else
+            MO_FUNCTION_CACHE_HIT=( "$1" )
+        fi
+
+        return 0
+    fi
+
+    if [[ "${#MO_FUNCTION_CACHE_MISS[@]}" -gt 0 ]]; then
+        MO_FUNCTION_CACHE_MISS=( ${MO_FUNCTION_CACHE_MISS[@]+"${MO_FUNCTION_CACHE_MISS[@]}"} "$1" )
+    else
+        MO_FUNCTION_CACHE_MISS=( "$1" )
+    fi
+
+    return 1
+}
+
+
+# Internal: Determine if a given environment variable exists and if it is
+# an array.
+#
+# $1 - Name of environment variable
+#
+# Be extremely careful.  Even if strict mode is enabled, it is not honored
+# in newer versions of Bash.  Any errors that crop up here will not be
+# caught automatically.
+#
+# Examples
+#
+#   var=(abc)
+#   if moIsArray var; then
+#      echo "This is an array"
+#      echo "Make sure you don't accidentally use \$var"
+#   fi
+#
+# Returns 0 if the name is not empty, 1 otherwise.
+mo::isArray() {
+    #: Namespace this variable so we don't conflict with what we're testing.
+    local moTestResult
+
+    moTestResult=$(declare -p "$1" 2>/dev/null) || return 1
+    [[ "${moTestResult:0:10}" == "declare -a" ]] && return 0
+    [[ "${moTestResult:0:10}" == "declare -A" ]] && return 0
+
+    return 1
+}
+
+
+# Internal: Determine if an array index exists.
+#
+# $1 - Variable name to check
+# $2 - The index to check
+#
+# Has to check if the variable is an array and if the index is valid for that
+# type of array.
+#
+# Returns true (0) if everything was ok, 1 if there's any condition that fails.
+mo::isArrayIndexValid() {
+    local moDeclare moTest
+
+    moDeclare=$(declare -p "$1")
+    moTest=""
+
+    if [[ "${moDeclare:0:10}" == "declare -a" ]]; then
+        #: Numerically indexed array - must check if the index looks like a
+        #: number because using a string to index a numerically indexed array
+        #: will appear like it worked.
+        if [[ "$2" == "0" ]] || [[ "$2" =~ ^[1-9][0-9]*$ ]]; then
+            #: Index looks like a number
+            eval "moTest=\"\${$1[$2]+ok}\""
+        fi
+    elif [[ "${moDeclare:0:10}" == "declare -A" ]]; then
+        #: Associative array
+        eval "moTest=\"\${$1[$2]+ok}\""
+    fi
+
+    if [[ -n "$moTest" ]]; then
+        return 0;
+    fi
+
+    return 1
+}
+
+
+# Internal: Determine if a variable is assigned, even if it is assigned an empty
+# value.
+#
+# $1 - Variable name to check.
+#
+# Can not use logic like this in case invalid variable names are passed.
+#     [[ "${!1-a}" == "${!1-b}" ]]
+#
+# Using logic like this gives false positives. Also, this is not supported on
+# Bash 3.2 and the script parsing will error before any commands are executed.
+#     [[ -v "$a" ]]
+#
+# Declaring a variable is not the same as assigning the variable.
+#     export x
+#     declare -p x   # Output: declare -x x
+#                    # Bash 3.2 returns error code 1 and outputs:
+#                    # bash: declare: x: not found
+#     export y=""
+#     declare -p y   # Output: declare -x y=""
+#     unset z
+#     declare -p z   # Error code 1 and output: bash: declare: z: not found
+#
+# Returns true (0) if the variable is set, 1 if the variable is unset.
+MO_VAR_TEST="ok"
+
+# This must not use `[[` because otherwise Bash 3.2 will stop execution and
+# always write to stderr without the ability to capture it.
+if test -v "MO_VAR_TEST" &> /dev/null; then
+    mo::debug "Using declare -p and [[ -v ]] for variable checks"
+    # More recent Bash
+    mo::isVarSet() {
+        # Do not convert this to [[, otherwise Bash 3.2 will fail to parse the
+        # script.
+        if declare -p "$1" &> /dev/null && test -v "$1"; then
+            return 0
+        fi
+
+        return 1
+    }
+else
+    mo::debug "Using declare -p for variable checks"
+    # Bash 3.2
+    mo::isVarSet() {
+        # If the variable is exported and not assigned, declare -p will error.
+        if declare -p "$1" &> /dev/null; then
+            return 0
+        fi
+
+        return 1
+    }
+fi
+unset MO_VAR_TEST
+
+
+# Internal: Determine if a value is considered truthy.
+#
+# $1 - The value to test
+# $2 - Invert the value, either "true" or "false"
+#
+# Returns true (0) if truthy, 1 otherwise.
+mo::isTruthy() {
+    local moTruthy
+
+    moTruthy=true
+
+    if [[ -z "${1-}" ]]; then
+        moTruthy=false
+    elif [[ -n "${MO_FALSE_IS_EMPTY-}" ]] && [[ "${1-}" == "false" ]]; then
+        moTruthy=false
+    fi
+
+    #: XOR the results
+    #: moTruthy  inverse  desiredResult
+    #: true      false    true
+    #: true      true     false
+    #: false     false    false
+    #: false     true     true
+    if [[ "$moTruthy" == "$2" ]]; then
+        mo::debug "Value is falsy, test result: $moTruthy inverse: $2"
+        return 1
+    fi
+
+    mo::debug "Value is truthy, test result: $moTruthy inverse: $2"
+    return 0
+}
+
+
+# Internal: Convert token list to values
+#
+# $1 - Destination variable name
+# $2-@ - Tokens to convert
+#
+# Sample call:
+#
+#     mo::evaluate dest NAME username VALUE abc123 PAREN 2
+#
+# Returns nothing.
+mo::evaluate() {
+    local moTarget moStack moValue moType moIndex moCombined moResult
+
+    moTarget=$1
+    shift
+
+    #: Phase 1 - remove all command tokens (PAREN, BRACE)
+    moStack=()
+
+    while [[ $# -gt 0 ]]; do
+        case "$1" in
+            PAREN|BRACE)
+                moType=$1
+                moValue=$2
+                mo::debug "Combining $moValue tokens"
+                moIndex=$((${#moStack[@]} - (2 * moValue)))
+                mo::evaluateListOfSingles moCombined "${moStack[@]:$moIndex}"
+
+                if [[ "$moType" == "PAREN" ]]; then
+                    moStack=("${moStack[@]:0:$moIndex}" NAME "$moCombined")
+                else
+                    moStack=("${moStack[@]:0:$moIndex}" VALUE "$moCombined")
+                fi
+                ;;
+
+            *)
+                moStack=(${moStack[@]+"${moStack[@]}"} "$1" "$2")
+                ;;
+        esac
+
+        shift 2
+    done
+
+    #: Phase 2 - check if this is a function or if we should just concatenate values
+    if [[ "${moStack[0]:-}" == "NAME" ]] && mo::isFunction "${moStack[1]}"; then
+        #: Special case - if the first argument is a function, then the rest are
+        #: passed to the function.
+        mo::debug "Evaluating function: ${moStack[1]}"
+        mo::evaluateFunction moResult "" "${moStack[@]:1}"
+    else
+        #: Concatenate
+        mo::debug "Concatenating ${#moStack[@]} stack items"
+        mo::evaluateListOfSingles moResult ${moStack[@]+"${moStack[@]}"}
+    fi
+
+    local "$moTarget" && mo::indirect "$moTarget" "$moResult"
+}
+
+
+# Internal: Convert an argument list to individual values.
+#
+# $1 - Destination variable name
+# $2-@ - A list of argument types and argument name/value.
+#
+# This assumes each value is separate from the rest. In contrast, mo::evaluate
+# will pass all arguments to a function if the first value is a function.
+#
+# Sample call:
+#
+#     mo::evaluateListOfSingles dest NAME username VALUE abc123
+#
+# Returns nothing.
+mo::evaluateListOfSingles() {
+    local moResult moTarget moTemp
+
+    moTarget=$1
+    shift
+    moResult=""
+
+    while [[ $# -gt 1 ]]; do
+        mo::evaluateSingle moTemp "$1" "$2"
+        moResult="$moResult$moTemp"
+        shift 2
+    done
+
+    mo::debug "Evaluated list of singles: $moResult"
+
+    local "$moTarget" && mo::indirect "$moTarget" "$moResult"
+}
+
+
+# Internal: Evaluate a single argument
+#
+# $1 - Name of variable for result
+# $2 - Type of argument, either NAME or VALUE
+# $3 - Argument
+#
+# Returns nothing
+mo::evaluateSingle() {
+    local moResult moType moArg
+
+    moType=$2
+    moArg=$3
+    mo::debug "Evaluating $moType: $moArg ($MO_CURRENT)"
+
+    if [[ "$moType" == "VALUE" ]]; then
+        moResult=$moArg
+    elif [[ "$moArg" == "." ]]; then
+        mo::evaluateVariable moResult ""
+    elif [[ "$moArg" == "@key" ]]; then
+        mo::evaluateKey moResult
+    elif mo::isFunction "$moArg"; then
+        mo::evaluateFunction moResult "" "$moArg"
+    else
+        mo::evaluateVariable moResult "$moArg"
+    fi
+
+    local "$1" && mo::indirect "$1" "$moResult"
+}
+
+
+# Internal: Return the value for @key based on current's name
+#
+# $1 - Name of variable for result
+#
+# Returns nothing
+mo::evaluateKey() {
+    local moResult
+
+    if [[ "$MO_CURRENT" == *.* ]]; then
+        moResult="${MO_CURRENT#*.}"
+    else
+        moResult="${MO_CURRENT}"
+    fi
+
+    local "$1" && mo::indirect "$1" "$moResult"
+}
+
+
+# Internal: Handle a variable name
+#
+# $1 - Destination variable name
+# $2 - Variable name
+#
+# Returns nothing.
+mo::evaluateVariable() {
+    local moResult moArg moNameParts
+
+    moArg=$2
+    moResult=""
+    mo::findVariableName moNameParts "$moArg"
+    mo::debug "Evaluate variable ($moArg, $MO_CURRENT): ${moNameParts[*]}"
+
+    if [[ -z "${moNameParts[1]}" ]]; then
+        if mo::isArray "${moNameParts[0]}"; then
+            eval mo::join moResult "," "\${${moNameParts[0]}[@]}"
+        else
+            if mo::isVarSet "${moNameParts[0]}"; then
+                moResult=${moNameParts[0]}
+                moResult="${!moResult}"
+            elif [[ -n "${MO_FAIL_ON_UNSET-}" ]]; then
+                mo::error "Environment variable not set: ${moNameParts[0]}"
+            fi
+        fi
+    else
+        if mo::isArray "${moNameParts[0]}"; then
+            eval "set +u;moResult=\"\${${moNameParts[0]}[${moNameParts[1]%%.*}]}\""
+        else
+            mo::error "Unable to index a scalar as an array: $moArg"
+        fi
+    fi
+
+    local "$1" && mo::indirect "$1" "$moResult"
+}
+
+
+# Internal: Find the name of a variable to use
+#
+# $1 - Destination variable name, receives an array
+# $2 - Variable name from the template
+#
+# The array contains the following values
+#     [0] - Variable name
+#     [1] - Array index, or empty string
+#
+# Example variables
+#     a="a"
+#     b="b"
+#     c=("c.0" "c.1")
+#     d=([b]="d.b" [d]="d.d")
+#
+# Given these inputs (function input, current value), produce these outputs
+#     a c => a
+#     a c.0 => a
+#     b d => d.b
+#     b d.d => d.b
+#     a d => d.a
+#     a d.d => d.a
+#     c.0 d => c.0
+#     d.b d => d.b
+#     '' c => c
+#     '' c.0 => c.0
+# Returns nothing.
+mo::findVariableName() {
+    local moVar moNameParts moResultBase moResultIndex moCurrent
+
+    moVar=$2
+    moResultBase=$moVar
+    moResultIndex=""
+
+    if [[ -z "$moVar" ]]; then
+        moResultBase=${MO_CURRENT%%.*}
+
+        if [[ "$MO_CURRENT" == *.* ]]; then
+            moResultIndex=${MO_CURRENT#*.}
+        fi
+    elif [[ "$moVar" == *.* ]]; then
+        mo::debug "Find variable name; name has dot: $moVar"
+        moResultBase=${moVar%%.*}
+        moResultIndex=${moVar#*.}
+    elif [[ -n "$MO_CURRENT" ]]; then
+        moCurrent=${MO_CURRENT%%.*}
+        mo::debug "Find variable name; look in array: $moCurrent"
+
+        if mo::isArrayIndexValid "$moCurrent" "$moVar"; then
+            moResultBase=$moCurrent
+            moResultIndex=$moVar
+        fi
+    fi
+
+    local "$1" && mo::indirectArray "$1" "$moResultBase" "$moResultIndex"
+}
+
+
+# Internal: Join / implode an array
+#
+# $1    - Variable name to receive the joined content
+# $2    - Joiner
+# $3-@ - Elements to join
+#
+# Returns nothing.
+mo::join() {
+    local joiner part result target
+
+    target=$1
+    joiner=$2
+    result=$3
+    shift 3
+
+    for part in "$@"; do
+        result="$result$joiner$part"
+    done
+
+    local "$target" && mo::indirect "$target" "$result"
+}
+
+
+# Internal: Call a function.
+#
+# $1 - Variable for output
+# $2 - Content to pass
+# $3 - Function to call
+# $4-@ - Additional arguments as list of type, value/name
+#
+# Returns nothing.
+mo::evaluateFunction() {
+    local moArgs moContent moFunctionResult moTarget moFunction moTemp moFunctionCall
+
+    moTarget=$1
+    moContent=$2
+    moFunction=$3
+    shift 3
+    moArgs=()
+
+    while [[ $# -gt 1 ]]; do
+        mo::evaluateSingle moTemp "$1" "$2"
+        moArgs=(${moArgs[@]+"${moArgs[@]}"} "$moTemp")
+        shift 2
+    done
+
+    mo::escape moFunctionCall "$moFunction"
+
+    if [[ -n "${MO_ALLOW_FUNCTION_ARGUMENTS-}" ]]; then
+        mo::debug "Function arguments are allowed"
+
+        if [[ ${#moArgs[@]} -gt 0 ]]; then
+            for moTemp in "${moArgs[@]}"; do
+                mo::escape moTemp "$moTemp"
+                moFunctionCall="$moFunctionCall $moTemp"
+            done
+        fi
+    fi
+
+    mo::debug "Calling function: $moFunctionCall"
+
+    #: Call the function in a subshell for safety. Employ the trick to preserve
+    #: whitespace at the end of the output.
+    moContent=$(
+        export MO_FUNCTION_ARGS=(${moArgs[@]+"${moArgs[@]}"})
+        echo -n "$moContent" | eval "$moFunctionCall ; moFunctionResult=\$? ; echo -n '.' ; exit \"\$moFunctionResult\""
+    ) || {
+        moFunctionResult=$?
+        if [[ -n "${MO_FAIL_ON_FUNCTION-}" && "$moFunctionResult" != 0 ]]; then
+            mo::error "Function failed with status code $moFunctionResult: $moFunctionCall" "$moFunctionResult"
+        fi
+    }
+
+    local "$moTarget" && mo::indirect "$moTarget" "${moContent%.}"
+}
+
+
+# Internal: Check if a tag appears to have only whitespace before it and after
+# it on a line. There must be a new line before and there must be a newline
+# after or the end of a string
+#
+# No arguments.
+#
+# Returns 0 if this is a standalone tag, 1 otherwise.
+mo::standaloneCheck() {
+    local moContent moN moR moT
+
+    moN=$'\n'
+    moR=$'\r'
+    moT=$'\t'
+
+    #: Check the content before
+    moContent=${MO_STANDALONE_CONTENT//"$moR"/"$moN"}
+
+    #: By default, signal to the next check that this one failed
+    MO_STANDALONE_CONTENT=""
+
+    if [[ "$moContent" != *"$moN"* ]]; then
+        mo::debug "Not a standalone tag - no newline before"
+
+        return 1
+    fi
+
+    moContent=${moContent##*"$moN"}
+    moContent=${moContent//"$moT"/}
+    moContent=${moContent// /}
+
+    if [[ -n "$moContent" ]]; then
+        mo::debug "Not a standalone tag - non-whitespace detected before tag"
+
+        return 1
+    fi
+
+    #: Check the content after
+    moContent=${MO_UNPARSED//"$moR"/"$moN"}
+    moContent=${moContent%%"$moN"*}
+    moContent=${moContent//"$moT"/}
+    moContent=${moContent// /}
+
+    if [[ -n "$moContent" ]]; then
+        mo::debug "Not a standalone tag - non-whitespace detected after tag"
+
+        return 1
+    fi
+
+    #: Signal to the next check that this tag removed content
+    MO_STANDALONE_CONTENT=$'\n'
+
+    return 0
+}
+
+
+# Internal: Process content before and after a tag. Remove prior whitespace up
+# to the previous newline. Remove following whitespace up to and including the
+# next newline.
+#
+# No arguments.
+#
+# Returns nothing.
+mo::standaloneProcess() {
+    local moI moTemp
+
+    mo::debug "Standalone tag - processing content before and after tag"
+    moI=$((${#MO_PARSED} - 1))
+    mo::debug "zero done ${#MO_PARSED}"
+    mo::escape moTemp "$MO_PARSED"
+    mo::debug "$moTemp"
+
+    # Mac appears to allow getting characters from before the start of the string
+    while [[ "$moI" -ge 0 ]] && [[ "${MO_PARSED:$moI:1}" == " " || "${MO_PARSED:$moI:1}" == $'\t' ]]; do
+        moI=$((moI - 1))
+    done
+
+    if [[ $((moI + 1)) != "${#MO_PARSED}" ]]; then
+        MO_PARSED="${MO_PARSED:0:${moI}+1}"
+    fi
+
+    moI=0
+
+    while [[ "${MO_UNPARSED:${moI}:1}" == " " || "${MO_UNPARSED:${moI}:1}" == $'\t' ]]; do
+        moI=$((moI + 1))
+    done
+
+    if [[ "${MO_UNPARSED:${moI}:1}" == $'\r' ]]; then
+        moI=$((moI + 1))
+    fi
+
+    if [[ "${MO_UNPARSED:${moI}:1}" == $'\n' ]]; then
+        moI=$((moI + 1))
+    fi
+
+    if [[ "$moI" != 0 ]]; then
+        MO_UNPARSED=${MO_UNPARSED:${moI}}
+    fi
+}
+
+
+# Internal: Apply indentation before any line that has content in MO_UNPARSED.
+#
+# $1 - Destination variable name.
+# $2 - The indentation string.
+# $3 - The content that needs the indentation string prepended on each line.
+#
+# Returns nothing.
+mo::indentLines() {
+    local moContent moIndentation moResult moN moR moChunk
+
+    moIndentation=$2
+    moContent=$3
+
+    if [[ -z "$moIndentation" ]]; then
+        mo::debug "Not applying indentation, empty indentation"
+
+        local "$1" && mo::indirect "$1" "$moContent"
+        return
+    fi
+
+    if [[ -z "$moContent" ]]; then
+        mo::debug "Not applying indentation, empty contents"
+
+        local "$1" && mo::indirect "$1" "$moContent"
+        return
+    fi
+
+    moResult=
+    moN=$'\n'
+    moR=$'\r'
+
+    mo::debug "Applying indentation: '${moIndentation}'"
+
+    while [[ -n "$moContent" ]]; do
+        moChunk=${moContent%%"$moN"*}
+        moChunk=${moChunk%%"$moR"*}
+        moContent=${moContent:${#moChunk}}
+
+        if [[ -n "$moChunk" ]]; then
+            moResult="$moResult$moIndentation$moChunk"
+        fi
+
+        moResult="$moResult${moContent:0:1}"
+        moContent=${moContent:1}
+    done
+
+    local "$1" && mo::indirect "$1" "$moResult"
+}
+
+
+# Internal: Escape a value
+#
+# $1 - Destination variable name
+# $2 - Value to escape
+#
+# Returns nothing
+mo::escape() {
+    local moResult
+
+    moResult=$2
+    moResult=$(declare -p moResult)
+    moResult=${moResult#*=}
+
+    local "$1" && mo::indirect "$1" "$moResult"
+}
+
+
+# Internal: Get the content up to the end of the block by minimally parsing and
+# balancing blocks. Returns the content before the end tag to the caller and
+# removes the content + the end tag from MO_UNPARSED. This can change the
+# delimiters, adjusting MO_OPEN_DELIMITER and MO_CLOSE_DELIMITER.
+#
+# $1 - Destination variable name
+# $2 - Token string to match for a closing tag
+#
+# Returns nothing.
+mo::getContentUntilClose() {
+    local moChunk moResult moTemp moTokensString moTokens moTarget moTagStack moResultTemp
+
+    moTarget=$1
+    moTagStack=("$2")
+    mo::debug "Get content until close tag: ${moTagStack[0]}"
+    moResult=""
+
+    while [[ -n "$MO_UNPARSED" ]] && [[ "${#moTagStack[@]}" -gt 0 ]]; do
+        moChunk=${MO_UNPARSED%%"$MO_OPEN_DELIMITER"*}
+        moResult="$moResult$moChunk"
+        MO_UNPARSED=${MO_UNPARSED:${#moChunk}}
+
+        if [[ -n "$MO_UNPARSED" ]]; then
+            moResultTemp="$MO_OPEN_DELIMITER"
+            MO_UNPARSED=${MO_UNPARSED:${#MO_OPEN_DELIMITER}}
+            mo::getContentTrim moTemp
+            moResultTemp="$moResultTemp$moTemp"
+            mo::debug "First character within tag: ${MO_UNPARSED:0:1}"
+
+            case "$MO_UNPARSED" in
+                '#'*)
+                    #: Increase block
+                    moResultTemp="$moResultTemp${MO_UNPARSED:0:1}"
+                    MO_UNPARSED=${MO_UNPARSED:1}
+                    mo::getContentTrim moTemp
+                    mo::getContentWithinTag moTemp "$MO_CLOSE_DELIMITER"
+                    moResultTemp="$moResultTemp${moTemp[0]}"
+                    moTagStack=("${moTemp[1]}" "${moTagStack[@]}")
+                    ;;
+
+                '^'*)
+                    #: Increase block
+                    moResultTemp="$moResultTemp${MO_UNPARSED:0:1}"
+                    MO_UNPARSED=${MO_UNPARSED:1}
+                    mo::getContentTrim moTemp
+                    mo::getContentWithinTag moTemp "$MO_CLOSE_DELIMITER"
+                    moResultTemp="$moResultTemp${moTemp[0]}"
+                    moTagStack=("${moTemp[1]}" "${moTagStack[@]}")
+                    ;;
+
+                '>'*)
+                    #: Partial - ignore
+                    moResultTemp="$moResultTemp${MO_UNPARSED:0:1}"
+                    MO_UNPARSED=${MO_UNPARSED:1}
+                    mo::getContentTrim moTemp
+                    mo::getContentWithinTag moTemp "$MO_CLOSE_DELIMITER"
+                    moResultTemp="$moResultTemp${moTemp[0]}"
+                    ;;
+
+                '/'*)
+                    #: Decrease block
+                    moResultTemp="$moResultTemp${MO_UNPARSED:0:1}"
+                    MO_UNPARSED=${MO_UNPARSED:1}
+                    mo::getContentTrim moTemp
+                    mo::getContentWithinTag moTemp "$MO_CLOSE_DELIMITER"
+                    
+                    if [[ "${moTagStack[0]}" == "${moTemp[1]}" ]]; then
+                        moResultTemp="$moResultTemp${moTemp[0]}"
+                        moTagStack=("${moTagStack[@]:1}")
+
+                        if [[ "${#moTagStack[@]}" -eq 0 ]]; then
+                            #: Erase all portions of the close tag
+                            moResultTemp=""
+                        fi
+                    else
+                        mo::errorNear "Unbalanced closing tag, expected: ${moTagStack[0]}" "${moTemp[0]}${MO_UNPARSED}"
+                    fi
+                    ;;
+
+                '!'*)
+                    #: Comment - ignore
+                    mo::getContentComment moTemp
+                    moResultTemp="$moResultTemp$moTemp"
+                    ;;
+
+                '='*)
+                    #: Change delimiters
+                    mo::getContentDelimiter moTemp
+                    moResultTemp="$moResultTemp$moTemp"
+                    ;;
+
+                '&'*)
+                    #: Unescaped - bypass one then ignore
+                    moResultTemp="$moResultTemp${MO_UNPARSED:0:1}"
+                    MO_UNPARSED=${MO_UNPARSED:1}
+                    mo::getContentTrim moTemp
+                    moResultTemp="$moResultTemp$moTemp"
+                    mo::getContentWithinTag moTemp "$MO_CLOSE_DELIMITER"
+                    moResultTemp="$moResultTemp${moTemp[0]}"
+                    ;;
+
+                *)
+                    #: Normal variable - ignore
+                    mo::getContentWithinTag moTemp "$MO_CLOSE_DELIMITER"
+                    moResultTemp="$moResultTemp${moTemp[0]}"
+                    ;;
+            esac
+
+            moResult="$moResult$moResultTemp"
+        fi
+    done
+
+    MO_STANDALONE_CONTENT="$MO_STANDALONE_CONTENT$moResult"
+
+    if mo::standaloneCheck; then
+        moResultTemp=$MO_PARSED
+        MO_PARSED=$moResult
+        mo::standaloneProcess
+        moResult=$MO_PARSED
+        MO_PARSED=$moResultTemp
+    fi
+
+    local "$moTarget" && mo::indirect "$moTarget" "$moResult"
+}
+
+
+# Internal: Convert a list of tokens to a string
+#
+# $1 - Destination variable for the string
+# $2-$@ - Token list
+#
+# Returns nothing.
+mo::tokensToString() {
+    local moTarget moString moTokens
+
+    moTarget=$1
+    shift 1
+    moTokens=("$@")
+    moString=$(declare -p moTokens)
+    moString=${moString#*=}
+
+    local "$moTarget" && mo::indirect "$moTarget" "$moString"
+}
+
+
+# Internal: Trims content from MO_UNPARSED, returns trimmed content.
+#
+# $1 - Destination variable
+#
+# Returns nothing.
+mo::getContentTrim() {
+    local moChar moResult
+    
+    moChar=${MO_UNPARSED:0:1}
+    moResult=""
+
+    while [[ "$moChar" == " " ]] || [[ "$moChar" == $'\r' ]] || [[ "$moChar" == $'\t' ]] || [[ "$moChar" == $'\n' ]]; do
+        moResult="$moResult$moChar"
+        MO_UNPARSED=${MO_UNPARSED:1}
+        moChar=${MO_UNPARSED:0:1}
+    done
+
+    local "$1" && mo::indirect "$1" "$moResult"
+}
+
+
+# Get the content up to and including a close tag
+#
+# $1 - Destination variable
+#
+# Returns nothing.
+mo::getContentComment() {
+    local moResult
+
+    mo::debug "Getting content for comment"
+    moResult=${MO_UNPARSED%%"$MO_CLOSE_DELIMITER"*}
+    MO_UNPARSED=${MO_UNPARSED:${#moResult}}
+
+    if [[ "$MO_UNPARSED" == "$MO_CLOSE_DELIMITER"* ]]; then
+        moResult="$moResult$MO_CLOSE_DELIMITER"
+        MO_UNPARSED=${MO_UNPARSED#"$MO_CLOSE_DELIMITER"}
+    fi
+
+    local "$1" && mo::indirect "$1" "$moResult"
+}
+
+
+# Get the content up to and including a close tag. First two non-whitespace
+# tokens become the new open and close tag.
+#
+# $1 - Destination variable
+#
+# Returns nothing.
+mo::getContentDelimiter() {
+    local moResult moTemp moOpen moClose
+
+    mo::debug "Getting content for delimiter"
+    moResult=""
+    mo::getContentTrim moTemp
+    moResult="$moResult$moTemp"
+    mo::chomp moOpen "$MO_UNPARSED"
+    MO_UNPARSED="${MO_UNPARSED:${#moOpen}}"
+    moResult="$moResult$moOpen"
+    mo::getContentTrim moTemp
+    moResult="$moResult$moTemp"
+    mo::chomp moClose "${MO_UNPARSED%%="$MO_CLOSE_DELIMITER"*}"
+    MO_UNPARSED="${MO_UNPARSED:${#moClose}}"
+    moResult="$moResult$moClose"
+    mo::getContentTrim moTemp
+    moResult="$moResult$moTemp"
+    MO_OPEN_DELIMITER="$moOpen"
+    MO_CLOSE_DELIMITER="$moClose"
+
+    local "$1" && mo::indirect "$1" "$moResult"
+}
+
+
+# Get the content up to and including a close tag. First two non-whitespace
+# tokens become the new open and close tag.
+#
+# $1 - Destination variable, an array
+# $2 - Terminator string
+#
+# The array contents:
+#     [0] The raw content within the tag
+#     [1] The parsed tokens as a single string
+#
+# Returns nothing.
+mo::getContentWithinTag() {
+    local moUnparsed moTokens
+
+    moUnparsed=${MO_UNPARSED}
+    mo::tokenizeTagContents moTokens "$MO_CLOSE_DELIMITER"
+    MO_UNPARSED=${MO_UNPARSED#"$MO_CLOSE_DELIMITER"}
+    mo::tokensToString moTokensString "${moTokens[@]:1}"
+    moParsed=${moUnparsed:0:$((${#moUnparsed} - ${#MO_UNPARSED}))}
+
+    local "$1" && mo::indirectArray "$1" "$moParsed" "$moTokensString"
+}
+
+
+# Internal: Parse MO_UNPARSED and retrieve the content within the tag
+# delimiters. Converts everything into an array of string values.
+#
+# $1 - Destination variable for the array of contents.
+# $2 - Stop processing when this content is found.
+#
+# The list of tokens are in RPN form. The first item in the resulting array is
+# the number of actual tokens (after combining command tokens) in the list.
+#
+# Given: a 'bc' "de\"\n" (f {g 'h'})
+# Result: ([0]=4 [1]=NAME [2]=a [3]=VALUE [4]=bc [5]=VALUE [6]=$'de\"\n'
+# [7]=NAME [8]=f [9]=NAME [10]=g [11]=VALUE [12]=h
+# [13]=BRACE [14]=2 [15]=PAREN [16]=2
+#
+# Returns nothing
+mo::tokenizeTagContents() {
+    local moResult moTerminator moTemp moUnparsedOriginal moTokenCount
+
+    moTerminator=$2
+    moResult=()
+    moUnparsedOriginal=$MO_UNPARSED
+    moTokenCount=0
+    mo::debug "Tokenizing tag contents until terminator: $moTerminator"
+
+    while true; do
+        mo::trimUnparsed
+
+        case "$MO_UNPARSED" in
+            "")
+                mo::errorNear "Did not find matching terminator: $moTerminator" "$moUnparsedOriginal"
+                ;;
+
+            "$moTerminator"*)
+                mo::debug "Found terminator"
+                local "$1" && mo::indirectArray "$1" "$moTokenCount" ${moResult[@]+"${moResult[@]}"}
+                return
+                ;;
+
+            '('*)
+                #: Do not tokenize the open paren - treat this as RPL
+                MO_UNPARSED=${MO_UNPARSED:1}
+                mo::tokenizeTagContents moTemp ')'
+                moResult=(${moResult[@]+"${moResult[@]}"} "${moTemp[@]:1}" PAREN "${moTemp[0]}")
+                MO_UNPARSED=${MO_UNPARSED:1}
+                ;;
+
+            '{'*)
+                #: Do not tokenize the open brace - treat this as RPL
+                MO_UNPARSED=${MO_UNPARSED:1}
+                mo::tokenizeTagContents moTemp '}'
+                moResult=(${moResult[@]+"${moResult[@]}"} "${moTemp[@]:1}" BRACE "${moTemp[0]}")
+                MO_UNPARSED=${MO_UNPARSED:1}
+                ;;
+
+            ')'* | '}'*)
+                mo::errorNear "Unbalanced closing parenthesis or brace" "$MO_UNPARSED"
+                ;;
+
+            "'"*)
+                mo::tokenizeTagContentsSingleQuote moTemp
+                moResult=(${moResult[@]+"${moResult[@]}"} "${moTemp[@]}")
+                ;;
+
+            '"'*)
+                mo::tokenizeTagContentsDoubleQuote moTemp
+                moResult=(${moResult[@]+"${moResult[@]}"} "${moTemp[@]}")
+                ;;
+
+            *)
+                mo::tokenizeTagContentsName moTemp
+                moResult=(${moResult[@]+"${moResult[@]}"} "${moTemp[@]}")
+                ;;
+        esac
+
+        mo::debug "Got chunk: ${moTemp[0]} ${moTemp[1]}"
+        moTokenCount=$((moTokenCount + 1))
+    done
+}
+
+
+# Internal: Get the contents of a variable name.
+#
+# $1 - Destination variable name for the token list (array of strings)
+#
+# Returns nothing
+mo::tokenizeTagContentsName() {
+    local moTemp
+
+    mo::chomp moTemp "${MO_UNPARSED%%"$MO_CLOSE_DELIMITER"*}"
+    moTemp=${moTemp%%(*}
+    moTemp=${moTemp%%)*}
+    moTemp=${moTemp%%\{*}
+    moTemp=${moTemp%%\}*}
+    MO_UNPARSED=${MO_UNPARSED:${#moTemp}}
+    mo::trimUnparsed
+    mo::debug "Parsed default token: $moTemp"
+
+    local "$1" && mo::indirectArray "$1" "NAME" "$moTemp"
+}
+
+
+# Internal: Get the contents of a tag in double quotes. Parses the backslash
+# sequences.
+#
+# $1 - Destination variable name for the token list (array of strings)
+#
+# Returns nothing.
+mo::tokenizeTagContentsDoubleQuote() {
+    local moResult moUnparsedOriginal
+
+    moUnparsedOriginal=$MO_UNPARSED
+    MO_UNPARSED=${MO_UNPARSED:1}
+    moResult=
+    mo::debug "Getting double quoted tag contents"
+
+    while true; do
+        if [[ -z "$MO_UNPARSED" ]]; then
+            mo::errorNear "Unbalanced double quote" "$moUnparsedOriginal"
+        fi
+
+        case "$MO_UNPARSED" in
+            '"'*)
+                MO_UNPARSED=${MO_UNPARSED:1}
+                local "$1" && mo::indirectArray "$1" "VALUE" "$moResult"
+                return
+                ;;
+
+            \\b*)
+                moResult="$moResult"$'\b'
+                MO_UNPARSED=${MO_UNPARSED:2}
+                ;;
+
+            \\e*)
+                #: Note, \e is ESC, but in Bash $'\E' is ESC.
+                moResult="$moResult"$'\E'
+                MO_UNPARSED=${MO_UNPARSED:2}
+                ;;
+
+            \\f*)
+                moResult="$moResult"$'\f'
+                MO_UNPARSED=${MO_UNPARSED:2}
+                ;;
+
+            \\n*)
+                moResult="$moResult"$'\n'
+                MO_UNPARSED=${MO_UNPARSED:2}
+                ;;
+
+            \\r*)
+                moResult="$moResult"$'\r'
+                MO_UNPARSED=${MO_UNPARSED:2}
+                ;;
+
+            \\t*)
+                moResult="$moResult"$'\t'
+                MO_UNPARSED=${MO_UNPARSED:2}
+                ;;
+
+            \\v*)
+                moResult="$moResult"$'\v'
+                MO_UNPARSED=${MO_UNPARSED:2}
+                ;;
+
+            \\*)
+                moResult="$moResult${MO_UNPARSED:1:1}"
+                MO_UNPARSED=${MO_UNPARSED:2}
+                ;;
+
+            *)
+                moResult="$moResult${MO_UNPARSED:0:1}"
+                MO_UNPARSED=${MO_UNPARSED:1}
+                ;;
+        esac
+    done
+}
+
+
+# Internal: Get the contents of a tag in single quotes. Only gets the raw
+# value.
+#
+# $1 - Destination variable name for the token list (array of strings)
+#
+# Returns nothing.
+mo::tokenizeTagContentsSingleQuote() {
+    local moResult moUnparsedOriginal
+
+    moUnparsedOriginal=$MO_UNPARSED
+    MO_UNPARSED=${MO_UNPARSED:1}
+    moResult=
+    mo::debug "Getting single quoted tag contents"
+
+    while true; do
+        if [[ -z "$MO_UNPARSED" ]]; then
+            mo::errorNear "Unbalanced single quote" "$moUnparsedOriginal"
+        fi
+
+        case "$MO_UNPARSED" in
+            "'"*)
+                MO_UNPARSED=${MO_UNPARSED:1}
+                local "$1" && mo::indirectArray "$1" VALUE "$moResult"
+                return
+                ;;
+
+            *)
+                moResult="$moResult${MO_UNPARSED:0:1}"
+                MO_UNPARSED=${MO_UNPARSED:1}
+                ;;
+        esac
+    done
+}
+
+
+# Save the original command's path for usage later
+MO_ORIGINAL_COMMAND="$(cd "${BASH_SOURCE[0]%/*}" || exit 1; pwd)/${BASH_SOURCE[0]##*/}"
+MO_VERSION="3.1.0"
+
+# If sourced, load all functions.
+# If executed, perform the actions as expected.
+if [[ "$0" == "${BASH_SOURCE[0]}" ]] || [[ -z "${BASH_SOURCE[0]}" ]]; then
+    mo "$@"
+fi
diff --git a/build/templates/home.html b/build/templates/home.html
new file mode 100644
index 0000000..e0b7521
--- /dev/null
+++ b/build/templates/home.html
@@ -0,0 +1,12 @@
+
+
+    
+        {{> head.html}}
+        ubergeek
+    
+    
+        {{> header.html}}
+        
{{{CONTENT}}}
+ {{> footer.html}} + + diff --git a/build/templates/page.html b/build/templates/page.html new file mode 100644 index 0000000..502dd58 --- /dev/null +++ b/build/templates/page.html @@ -0,0 +1,12 @@ + + + + {{> head.html}} + ubergeek{{#PAGE_TITLE}}: {{PAGE_TITLE}}{{/PAGE_TITLE}} + + + {{> header.html}} +
{{{CONTENT}}}
+ {{> footer.html}} + + diff --git a/build/templates/partials/.gitkeep b/build/templates/partials/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/build/templates/partials/footer.html b/build/templates/partials/footer.html new file mode 100644 index 0000000..6dca778 --- /dev/null +++ b/build/templates/partials/footer.html @@ -0,0 +1,16 @@ + diff --git a/build/templates/partials/head.html b/build/templates/partials/head.html new file mode 100644 index 0000000..f9f143e --- /dev/null +++ b/build/templates/partials/head.html @@ -0,0 +1,11 @@ + + + + + {{#META_DATE}}{{/META_DATE}} + {{#META_DESCRIPTION}}{{/META_DESCRIPTION}} + + {{#HAS_CODE}} + + {{/HAS_CODE}} + diff --git a/build/templates/partials/header.html b/build/templates/partials/header.html new file mode 100644 index 0000000..cbe3936 --- /dev/null +++ b/build/templates/partials/header.html @@ -0,0 +1,6 @@ +
+ +
diff --git a/linux-command-line/shell-basics/index.md b/linux-command-line/shell-basics/index.md new file mode 100644 index 0000000..c0cf75b --- /dev/null +++ b/linux-command-line/shell-basics/index.md @@ -0,0 +1,327 @@ +--- +title: Shell Basics +--- + +# Shell Basics + +The shell is your primary interface to a Linux system. Everything — file management, process control, system administration — flows through it. Before graphical tools, before IDEs, there was the shell. Mastering it is non-negotiable. + +**Prerequisites:** None +**Estimated time:** 4-6 hours of practice + +--- + +## What is a Shell? + +A shell is a program that reads commands from your input and executes them. It's both a **command interpreter** (runs programs) and a **programming language** (scripts). + +Common shells: +- **bash** — Bourne Again Shell, the default on most Linux systems +- **zsh** — Extended bash with better completion and prompting +- **sh** — POSIX shell, the lowest common denominator +- **fish** — Friendly interactive shell, not POSIX-compatible + +When you open a terminal, you're running a **terminal emulator** (alacritty, kitty, xterm) which hosts a **shell process**. The terminal draws pixels; the shell interprets commands. + +```bash +# Check your current shell +echo $SHELL + +# List available shells +cat /etc/shells + +# The shell prompt typically shows: +# username@hostname:current_directory$ +``` + +> **Key distinction:** The terminal and the shell are different things. You can run bash inside alacritty, or inside a Linux TTY, or over SSH. The shell doesn't care what's displaying its output. + +--- + +## Navigating the Filesystem + +Linux has a single filesystem tree rooted at `/`. Everything is a file — devices, processes, sockets. + +### Essential Navigation Commands + +```bash +pwd # Print working directory — where you are +ls # List files in current directory +ls -la # Long format, show hidden files (dotfiles) +ls -lh # Human-readable sizes (K, M, G) +cd /etc # Change to /etc +cd ~ # Change to home directory (also just: cd) +cd - # Change to previous directory +cd .. # Go up one level +``` + +### The Filesystem Hierarchy + +| Path | Purpose | +|------|---------| +| `/` | Root of everything | +| `/home` | User home directories | +| `/etc` | System configuration files | +| `/var` | Variable data (logs, databases, mail) | +| `/tmp` | Temporary files (cleared on reboot) | +| `/usr` | User programs and data (read-only) | +| `/usr/bin` | Most user commands | +| `/usr/local` | Locally installed software | +| `/dev` | Device files | +| `/proc` | Process information pseudo-filesystem | +| `/sys` | Kernel/device information pseudo-filesystem | +| `/opt` | Optional/third-party software | + +```bash +# The filesystem is a tree. Explore it: +ls / +ls /etc +ls /dev + +# Everything is a file: +file /dev/null # character special device +file /etc/hostname # ASCII text +file /usr/bin/ls # ELF 64-bit executable +``` + +--- + +## File Operations + +### Creating, Copying, Moving, Deleting + +```bash +# Create +touch newfile.txt # Create empty file (or update timestamp) +mkdir mydir # Create directory +mkdir -p path/to/nested/dir # Create nested directories + +# Copy +cp file.txt backup.txt # Copy file +cp -r mydir/ mydir_backup/ # Copy directory recursively + +# Move / Rename +mv old.txt new.txt # Rename +mv file.txt ~/Documents/ # Move to another directory + +# Delete +rm file.txt # Remove file (no trash, no undo) +rm -r mydir/ # Remove directory recursively +rm -ri mydir/ # Interactive — asks before each deletion +rmdir emptydir/ # Remove only if empty +``` + +> **Warning:** `rm` is permanent. There is no trash can. `rm -rf /` will destroy your entire system if you have the permissions. Always double-check your path before pressing Enter. + +### Viewing Files + +```bash +cat file.txt # Print entire file to stdout +less file.txt # Paginated viewer (q to quit, / to search) +head -n 20 file.txt # First 20 lines +tail -n 20 file.txt # Last 20 lines +tail -f /var/log/syslog # Follow a file in real-time (great for logs) +wc -l file.txt # Count lines +``` + +`less` is your friend. Use it for anything longer than a screenful. Key bindings inside `less`: +- `Space` / `b` — page down / up +- `/pattern` — search forward +- `n` / `N` — next / previous search match +- `g` / `G` — go to top / bottom +- `q` — quit + +--- + +## Permissions + +Every file has an **owner**, a **group**, and a set of **permissions** (read, write, execute) for each of: owner, group, others. + +```bash +ls -l file.txt +# -rw-r--r-- 1 ahmed users 1234 Mar 31 10:00 file.txt +# │├──┤├──┤├──┤ +# │ │ │ └── others: read only +# │ │ └────── group: read only +# │ └─────────── owner: read + write +# └───────────── file type: - = regular, d = directory, l = symlink +``` + +### chmod — Change Permissions + +```bash +# Symbolic notation +chmod u+x script.sh # Add execute for owner +chmod go-w file.txt # Remove write for group and others +chmod a+r file.txt # Add read for all + +# Octal notation (most common) +chmod 755 script.sh # rwxr-xr-x — owner full, others read+execute +chmod 644 file.txt # rw-r--r-- — owner read+write, others read +chmod 600 secret.key # rw------- — owner only +``` + +**Octal cheat sheet:** r=4, w=2, x=1. Add them up per group. +- `7` = rwx (4+2+1) +- `6` = rw- (4+2) +- `5` = r-x (4+1) +- `4` = r-- (4) +- `0` = --- (0) + +### chown — Change Ownership + +```bash +chown ahmed file.txt # Change owner +chown ahmed:users file.txt # Change owner and group +chown -R ahmed:users mydir/ # Recursive +``` + +### Directories and Execute Permission + +The execute bit on directories means something different: it grants **access** (the ability to `cd` into it and access files within). A directory with `r--` lets you list filenames but not read the files. A directory with `--x` lets you access files by name but not list them. + +--- + +## Pipes and Redirection + +This is where the shell gets powerful. The Unix philosophy: small programs that do one thing well, connected together. + +### Redirection + +Every process has three standard streams: +- **stdin** (0) — input +- **stdout** (1) — normal output +- **stderr** (2) — error output + +```bash +# Redirect stdout to a file +ls > filelist.txt # Overwrite +ls >> filelist.txt # Append + +# Redirect stderr +gcc broken.c 2> errors.txt # Errors go to file +gcc broken.c 2>&1 # Stderr merges into stdout + +# Redirect both +command > output.txt 2>&1 # Both to same file +command &> output.txt # Bash shorthand for the above + +# Discard output +command > /dev/null 2>&1 # Silence everything + +# Read stdin from a file +sort < unsorted.txt +``` + +### Pipes + +Pipes connect stdout of one command to stdin of the next. + +```bash +# Find the 10 largest files in /var/log +du -sh /var/log/* 2>/dev/null | sort -rh | head -10 + +# Count unique IP addresses in a log +cat access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20 + +# Find running processes matching a pattern +ps aux | grep nginx | grep -v grep + +# Chain as many as you need +cat /etc/passwd | cut -d: -f1 | sort | head -5 +``` + +> **Useless use of cat:** `cat file | grep pattern` can be written as `grep pattern file`. But in pipelines with many stages, starting with `cat` can be clearer. Don't stress about it early on — clarity matters more than micro-optimization. + +### Command Substitution + +Use the output of one command inside another: + +```bash +# Using $() — preferred +echo "Today is $(date +%Y-%m-%d)" +files=$(ls *.txt) + +# Using backticks — older style, harder to nest +echo "Today is `date +%Y-%m-%d`" +``` + +--- + +## Wildcards (Globbing) + +The shell expands patterns before passing them to commands: + +```bash +* # Match any characters (except leading dot) +? # Match exactly one character +[abc] # Match a, b, or c +[0-9] # Match any digit +[!abc] # Match anything except a, b, c +** # Match directories recursively (bash: shopt -s globstar) + +# Examples +ls *.txt # All .txt files +ls file?.log # file1.log, fileA.log, etc. +ls *.{jpg,png} # All .jpg and .png files (brace expansion, not globbing) +rm /tmp/test_* # All files starting with test_ in /tmp +``` + +--- + +## Getting Help + +```bash +man ls # Manual page for ls (press q to quit) +man -k "copy files" # Search manual descriptions +ls --help # Quick help (most GNU tools) +type ls # Is it a builtin, alias, or binary? +which python # Full path of a command +whatis ls # One-line description +apropos network # Search man pages by keyword +``` + +**Reading man pages:** The synopsis section uses conventions: +- `[optional]` — square brackets mean optional +- `REQUIRED` — caps or no brackets means required +- `...` — can be repeated +- `a | b` — choose one + +--- + +## Exercises + +### Drill Exercises + +1. Navigate to `/etc` and list all files starting with `host`. What do they contain? +2. Create a directory structure: `~/practice/project/{src,docs,tests}` in one command +3. Create a file, set its permissions to 750, then explain who can do what with it +4. Use `ls -la /` and identify which entries are regular files, directories, and symlinks +5. Redirect the output of `ls /nonexistent` to `/dev/null` while keeping error messages visible + +### Pipeline Challenges + +6. Count how many lines in `/etc/passwd` contain the string `nologin` +7. List the 5 largest files in `/usr/bin` by size (hint: `ls -lS` or `du`) +8. Extract just the usernames from `/etc/passwd` (field 1, colon-separated) and sort them +9. Find all environment variables that contain the string `PATH` (hint: `env | grep`) +10. Get the total line count of all `.conf` files in `/etc/` (hint: `wc -l`, `cat`) + +### Mini-Project + +**Build a quick reference card:** Create a file `~/cheatsheet.md` that contains the 20 commands you found most useful from this section, organized by category, with a one-line description and example for each. This is your cheatsheet — you'll extend it throughout the course. + +--- + +## Key Takeaways + +- The shell is a command interpreter and a programming environment +- Everything in Linux is a file, organized in a single tree from `/` +- Permissions are the foundation of Linux security — understand `rwx` and octal +- Pipes and redirection are the core of composing commands +- `man` and `--help` are always available — read them first, search the web second +- Practice in a real terminal. Reading about commands is not the same as using them. + +--- + +**Next:** [File Management](../file-management/) — find, locate, tar, rsync, and more -- cgit v1.2.3