With the environment set up in the previous lesson — Bash 5.x, your ~/veloz-ops created and the PATH configured — the time has come to move around the system with confidence. Navigating the command line is not a formality: it is the skill that everything else rests on. When, in Module 9, your script has to locate /var/log/veloz/access.log from any directory, or when at three in the morning you have to work out what is filling up the disk on srv-veloz-01, it will all come down to moving and looking with precision. In this lesson you will learn the anatomy of a command, the map of the Linux filesystem and the tools to walk and inspect it.
Contents
- Anatomy of the prompt and of a command
- The Linux directory tree
- Absolute and relative paths
pwd: where am Icd: moving aroundls: looking- Reading the output of
ls -l tree: seeing the structure at a glancepushdandpopd: the directory stack- Inspecting without opening:
file,stat,duanddf - A guided tour of the Veloz Envíos infrastructure
- Anatomy of the prompt and of a command
1.1 The prompt
The prompt is the line Bash prints while waiting for your orders. With the configuration we left in the previous lesson it looks like this:
Every piece tells you something:
| Part | Meaning |
|---|---|
joan |
The user you are working as |
@ |
Separator |
srv-veloz-01 |
The machine you are on |
: |
Separator |
~/veloz-ops |
Current directory (~ is your home folder) |
$ |
A normal user. If you were root, you would see # here |
That last character deserves attention: # means you have full permissions and that a mistake can be irreversible. Get into the habit of looking at it before running anything destructive.
1.2 The parts of a command
A Bash command breaks down like this:
- Command: the program to run (
ls). - Options (or flags): they change the behavior. They start with a dash.
- Arguments: what the command acts on.
Options come in two formats worth telling apart:
| Format | Example | Characteristics |
|---|---|---|
| Short | -l, -h |
A single letter. They can be grouped: -lh is the same as -l -h |
| Long | --human-readable |
A whole word, more readable. They cannot be grouped |
These four forms are equivalent:
ls -l -h /var/log/veloz
ls -lh /var/log/veloz
ls --format=long --human-readable /var/log/veloz
ls -l --human-readable /var/log/velozA practical rule: use short options when typing interactively and long ones when writing scripts. In a script, --human-readable explains itself; -h forces you to check the manual (and in many other commands -h means "help", which is misleading).
When a long option takes a value, it is written with = or with a space:
And one detail that saves odd situations: -- marks the end of the options. If one day you have a file called -report.txt, ls -- -report.txt treats it as an argument and not as an option.
- The Linux directory tree
In Linux there are no drive letters. Everything hangs from a single origin: the root, /. Additional disks, USB drives or network partitions are "mounted" at some point in that tree.
graph TD
R["/"] --> etc["/etc<br/>configuration"]
R --> var["/var<br/>variable data"]
R --> srv["/srv<br/>service data"]
R --> home["/home<br/>users"]
R --> tmp["/tmp<br/>temporary"]
R --> usr["/usr<br/>programs"]
var --> log["/var/log"]
log --> veloz["/var/log/veloz<br/>access.log, app.log"]
srv --> sv["/srv/veloz/data<br/>shipments.csv"]
home --> joan["/home/joan (~)"]
joan --> ops["~/veloz-ops<br/>bin lib etc logs"]
usr --> bin["/usr/bin<br/>ls, grep, date..."]
The directories you will use daily:
| Directory | What it holds | Example at Veloz Envíos |
|---|---|---|
/ |
The root of everything | — |
/etc |
System configuration files | /etc/nginx/, /etc/passwd |
/var |
Data that varies during operation | Logs, queues, caches |
/var/log |
System and application logs | /var/log/veloz/access.log |
/srv |
Data served by the machine's services | /srv/veloz/data/shipments.csv |
/home |
Users' home folders | /home/joan, shortened to ~ |
/tmp |
Temporary files; wiped on reboot | Intermediate files from scripts |
/usr/bin |
System programs for all users | ls, grep, date, awk |
/usr/local/bin |
Programs installed manually by the administrator | In-house tools |
/opt |
Self-contained third-party software | Monitoring agents |
/root |
The superuser's home folder | — |
/proc |
Virtual filesystem with kernel information | /proc/cpuinfo, /proc/meminfo |
/dev |
Devices represented as files | /dev/null, /dev/sda |
This layout is not arbitrary: it is defined by the FHS (Filesystem Hierarchy Standard). Knowing it lets you guess where things are on any Linux server, even one you have never seen. If someone tells you "check the application logs", you already know you will start by looking in /var/log.
- Absolute and relative paths
A path is the address of a file or directory. There are two ways of expressing it.
- Absolute path: it starts with
/and describes the full route from the root. It is unambiguous and works from anywhere./var/log/veloz/access.log - Relative path: it does not start with
/and is interpreted from the current directory.veloz/access.log (if you are in /var/log)
Bash also understands four fundamental shortcuts:
| Symbol | Meaning | Example |
|---|---|---|
. |
The current directory | ./report.sh |
.. |
The parent directory | cd .. |
~ |
Your home folder (/home/joan) |
cd ~/veloz-ops |
- |
The previous directory (where you were before) | cd - |
Examples with the current directory set to /var/log/veloz:
| You type | It resolves to |
|---|---|
access.log |
/var/log/veloz/access.log |
./access.log |
/var/log/veloz/access.log |
../syslog |
/var/log/syslog |
../../log/veloz |
/var/log/veloz |
~/veloz-ops/bin |
/home/joan/veloz-ops/bin |
/srv/veloz/data |
/srv/veloz/data |
When should you use each? The professional rule is clear:
- In scripts, absolute paths. A
veloz-opsscript launched by cron may start from any directory; if it uses relative paths, it will fail unpredictably. - Interactively, relative ones. You type far less.
The - shortcut deserves a special mention because it saves an enormous amount of time:
cd - takes you back to the previous directory and also prints where it went. Switching between two distant locations becomes a single keystroke.
pwd: where am I
pwd: where am Ipwd (print working directory) answers the most basic question.
It looks unnecessary if the prompt already shows it, but it is not: some prompts only display the directory's short name (\W instead of \w), and in a script there is no prompt at all.
It has an interesting nuance with symbolic links:
-P (physical) shows the real path, resolving symbolic links. By default, pwd shows the logical path, that is, the route you took to get there. If /srv/veloz/data were actually a link to /mnt/veloz-data, pwd would say the former and pwd -P the latter. When diagnosing disk space, that difference matters.
cd: moving around
cd: moving aroundcd (change directory) changes the current directory. Its most useful forms:
cd /var/log/veloz # absolute path
cd veloz # relative path (from /var/log)
cd .. # go up one level
cd ../.. # go up two levels
cd ~ # go to your home folder
cd # identical to 'cd ~'
cd - # go back to the previous directory
cd ~/veloz-ops/bin # combining ~ and a relative pathA realiztic working sequence on srv-veloz-01:
Notice the detail: cd ../.. from /var/log/veloz goes up two levels and lands in /var, not in the root. Each .. steps back exactly one rung.
An important technical note that will fully make sense in the next lesson: cd is not a program, it is a Bash builtin. It has to be. If it were an external executable, it would launch as a child process, change its directory and die, leaving yours untouched. That is why man cd finds nothing and you have to use help cd; we will see this in 01-05.
Tab completion, which is essential here: type cd /var/log/vel and press Tab. Bash completes it to /var/log/veloz/. If several options are possible, press Tab twice to list them. The full set of keyboard shortcuts appears in 02-06, but start using this one today: it cuts down typing errors dramatically.
ls: looking
ls: lookingls lists the contents of a directory. It is probably the command you will run the most times in your life.
With no arguments, it lists the current directory. You can point it at another one:
6.1 The options you will really use
| Option | Long name | What it does |
|---|---|---|
-l |
--format=long |
Long format: permissions, owner, size, date |
-a |
--all |
Also shows hidden files (the ones starting with .) |
-A |
--almost-all |
Like -a but without . and .. |
-h |
--human-readable |
Sizes in K, M, G instead of bytes (requires -l) |
-t |
--sort=time |
Sorts by modification date, most recent first |
-r |
--reverse |
Reverses the order |
-S |
--sort=size |
Sorts by size, largest first |
-R |
--recursive |
Descends through every subdirectory |
-d |
--directory |
Shows the directory itself, not its contents |
-i |
--inode |
Shows the inode number |
-F |
--classify |
Appends a suffix by type (/ directory, * executable, @ link) |
The combinations that will become reflexes:
total 47M -rw-r--r-- 1 www-data adm 28M Aug 3 10:14 access.log -rw-r--r-- 1 www-data adm 9.4M Aug 2 23:59 access.log.1 -rw-r--r-- 1 veloz veloz 6.2M Aug 3 10:15 app.log -rw-r--r-- 1 veloz veloz 2.1M Aug 2 23:59 app.log.1 -rw-r--r-- 1 veloz veloz 1.3M Aug 3 09:02 veloz-api.log
Sorted by date: the first one is the most recently modified. This is the star command when something has just broken: it immediately tells you which log is being written right now.
Sorted by size from largest to smallest: the star command when the disk fills up.
total 68 drwxr-xr-x 8 joan joan 4096 Aug 3 09:30 . drwxr-xr-x 3 root root 4096 Jul 12 08:00 .. -rw------- 1 joan joan 2814 Aug 3 10:02 .bash_history -rw-r--r-- 1 joan joan 3841 Aug 3 09:28 .bashrc -rw-r--r-- 1 joan joan 807 Jul 12 08:00 .profile drwx------ 2 joan joan 4096 Jul 12 08:05 .ssh drwxrwxr-x 6 joan joan 4096 Aug 3 09:30 veloz-ops
A key concept shows up here: in Linux, a file is hidden simply because its name starts with a dot. There is no special attribute. That is why ~/.bashrc does not appear with a plain ls. The . and .. directories you see are the directory itself and its parent.
- Reading the output of
ls -l
ls -lThis is one of those skills used daily throughout an entire professional career. Take one line:
It breaks down into seven fields:
| # | Value | Meaning |
|---|---|---|
| 1 | -rw-r--r-- |
File type and permissions |
| 2 | 1 |
Number of hard links |
| 3 | veloz |
Owning user |
| 4 | veloz |
Owning group |
| 5 | 6291456 |
Size in bytes (with -h, 6.0M) |
| 6 | Aug 3 10:15 |
Date and time of last modification |
| 7 | app.log |
Name |
The first field is read in four blocks:
The first character indicates the type:
| Character | Type |
|---|---|
- |
Regular file |
d |
Directory |
l |
Symbolic link |
c |
Character device (e.g. /dev/null) |
b |
Block device (e.g. a disk) |
p |
Named pipe (FIFO) |
s |
Socket |
The next three blocks are permissions: r read, w write, x execute; a dash means that permission is not granted. So -rw-r--r-- reads as: regular file, the owner can read and write, the group can only read, everyone else can only read.
This analysis immediately explains practical things. If you try to write to /var/log/veloz/app.log as joan and the file belongs to veloz with -rw-r--r-- permissions, you will get Permission denied. And if a script of yours will not start with ./script.sh, the first thing you will check is whether it has the x. Permissions, how to change them and octal notation are studied in depth in lesson 02-03; here you only need to know how to read them.
A special case you will see a lot on a server:
The leading l and the -> arrow indicate a symbolic link: a file that points to another. The rwxrwxrwx permissions of a link are always like that and mean nothing; the ones that matter are the target's.
tree: seeing the structure at a glance
tree: seeing the structure at a glancetree draws the directory hierarchy. It is not installed by default on many systems:
Useful options:
| Option | Effect |
|---|---|
-L n |
Limits the depth to n levels |
-d |
Directories only |
-a |
Includes hidden entries |
-h |
Shows human-readable sizes |
-f |
Shows the full path of each item |
Limiting the depth with -L is almost mandatory in large directories: without it, tree / can spit out hundreds of thousands of lines.
If tree is not available and you cannot install it (common on production servers or in containers), the alternative is ls -R, which we will see in the final tour, or find combined with a format, which is covered in 05-01.
pushd and popd: the directory stack
pushd and popd: the directory stackcd - toggles between two directories, but sometimes you need to juggle three or four. For that, Bash maintains a directory stack.
pushd PATH: pushes the current directory onto the stack and moves toPATH.popd: pops the stack and returns to the directory at the top.dirs -v: shows the stack, numbered.
Each pushd prints the whole stack, with the current position on the left. It is the ideal tool when you are diagnosing an incident and need to jump between logs, data and scripts without losing your thread. You can also jump straight to a position: pushd +2 rotates the stack to item number 2.
- Inspecting without opening:
file, stat, du and df
file, stat, du and dfBefore opening a file it pays to know what it is and how much space it takes. Four commands cover 90% of the cases.
10.1 file: what kind of file it is
Linux does not trust the extension: file examines the contents to determine the type.
/var/log/veloz/app.log: ASCII text /srv/veloz/data/shipments.csv: CSV text /usr/bin/ls: ELF 64-bit LSB pie executable, x86-64, dynamically linked /home/joan/veloz-ops: directory
Its practical value is enormous: it stops you from opening a 2 GB binary in a text editor and freezing the terminal. Before running cat on an unknown file, run file on it.
A very relevant use for Veloz Envíos: spotting compressed files with a misleading extension.
Even though it does not end in .gz, it is compressed, and it would have to be read with zcat instead of cat.
10.2 stat: the full metadata
File: /var/log/veloz/app.log Size: 6291456 Blocks: 12288 IO Block: 4096 regular file Device: 8,1 Inode: 262147 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 1001/ veloz) Gid: ( 1001/ veloz) Access: 2026-08-03 10:15:22.000000000 +0200 Modify: 2026-08-03 10:15:22.000000000 +0200 Change: 2026-08-03 10:15:22.000000000 +0200 Birth: 2026-07-01 08:00:00.000000000 +0200
stat gives you the same as ls -l but with full precision, including the three timestamps Linux maintains:
| Stamp | Name | Changes when... |
|---|---|---|
| atime | Access | The contents are read |
| mtime | Modify | The contents are changed |
| ctime | Change | The contents or the metadata change (permissions, owner) |
Confusing mtime with ctime is a common mistake in audits: if someone changed a file's permissions but not its contents, mtime stays the same and only ctime gives it away.
stat also accepts custom formats, very handy in scripts:
Where %n is the name, %s the size and %y the modification date. In Module 9 we will use this form to check whether a log has been updated recently.
10.3 du: how much a directory takes up
du (disk usage) adds up the space used.
-s(summarize): a single total instead of the detail of every subdirectory.-h(human-readable): in K, M, G.
To see the breakdown per subdirectory, sorted from largest to smallest:
This command is the standard answer to "the disk is filling up": it tells you which subdirectory the problem is in, and by repeating it one level down you reach the culprit in a few steps. The | pipe and sort are studied in Module 2; here we are using it as a recipe.
10.4 df: how much is left on the disk
df (disk free) reports the mounted partitions and how full they are.
Filesystem Size Used Avail Use% Mounted on /dev/sda1 100G 72G 23G 76% / /dev/sdb1 500G 410G 65G 87% /srv tmpfs 3.9G 1.2M 3.9G 1% /run
The difference between the two commands tends to confuse:
| Command | Point of view | Question it answers |
|---|---|---|
du |
From the files upwards | "How much does this folder take up?" |
df |
From the filesystem | "How much is left on the disk?" |
And a classic anomaly worth knowing: if df says the disk is full but du cannot find the data, it is almost always because a process is keeping an already deleted file open. The space is not released until that process ends. That is exactly what happens when someone deletes access.log without restarting the service that writes to it. Process management is covered in 05-02.
df -i is also worth knowing: it shows the free inodes. A disk with space but no available inodes (millions of tiny files) also produces "no space left" errors.
- A guided tour of the Veloz Envíos infrastructure
Let us put it all together in a real reconnaissance session on srv-veloz-01. It is your first day and you want to understand the terrain.
Step 1: Locate yourself
Step 2: Explore the logs
total 47M -rw-r--r-- 1 veloz veloz 6.0M Aug 3 10:15 app.log -rw-r--r-- 1 www-data adm 28M Aug 3 10:14 access.log -rw-r--r-- 1 veloz veloz 1.3M Aug 3 09:02 veloz-api.log -rw-r--r-- 1 www-data adm 9.4M Aug 2 23:59 access.log.1 -rw-r--r-- 1 veloz veloz 2.1M Aug 2 23:59 app.log.1
You already know a lot from this single screen:
app.logandaccess.logare being written right now (today's date, a recent time): the service is alive.- The
.1files are the previous day's rotations, cut off at 23:59. access.logweighs 28 MB, four times more thanapp.log: if space needs watching, that is the candidate.access.logbelongs towww-dataandapp.logtoveloz: they are two different services, with different identities. Asjoan, you will be able to read them (the "others" block hasr) but not write to them.
Step 3: Check the type before opening
access.log: ASCII text, with very long lines access.log.1: gzip compressed data, from Unix, original size 84213760
Confirmed: the rotated file is compressed despite not ending in .gz. Good to know before trying to read it.
Step 4: Visit the business data
/srv/veloz/data /var/log/veloz total 12M -rw-rw-r-- 1 veloz veloz 11M Aug 3 06:00 shipments.csv -rw-rw-r-- 1 veloz veloz 512K Jul 31 06:00 shipments-2026-07.csv drwxrwxr-x 2 veloz veloz 4.0K Jul 1 06:00 archive
A working pattern is visible: there is an active file, a monthly one and an archive directory. The 06:00 timestamp suggests an automated process that runs in the early hours.
Step 5: Check the space
47M /var/log/veloz 12M /srv/veloz/data Filesystem Size Used Avail Use% Mounted on /dev/sdb1 500G 410G 65G 87% /srv
/srv at 87%: not critical yet, but exactly the kind of metric our future report script should watch. Make a mental note, because it will be one of the requirements in Module 9.
Step 6: Head home
For now it is empty. From Module 3 onwards we will start filling it.
The alternative without tree
If tree is not installed on the server:
/home/joan/veloz-ops: bin etc lib logs /home/joan/veloz-ops/bin: /home/joan/veloz-ops/etc: /home/joan/veloz-ops/lib: /home/joan/veloz-ops/logs:
Less pretty, but available on any machine.
Common Mistakes and Tips
- Confusing
/var/logwith~/veloz-ops/logs. The first is managed by the system and requires permissions; the second is yours. Our scripts will write to the second. - Running
cdwithout checking whether it worked. If the directory does not exist,cdfails but you stay where you were; in a script, the following commands will act on the wrong directory. It is solved withcd /path || exit, which we will see in 03-04. - Using relative paths in scripts. A script launched by cron does not start in the directory you imagine. Always absolute paths.
- Forgetting that
-hdoes not mean the same thing in every command. Inls,duanddfit is "human readable"; in many others it is "help". When in doubt,man(lesson 01-05). - Running
caton a huge or binary file. It freezes the terminal and can leave it full of corrupted characters (fixed withreset). Usefilefirst andlessto read. - Looking for hidden files with a plain
ls. In Linux a leading dot is all it takes to hide a file. Usels -awhen something "does not show up". - Running
tree /ordu /with no limit. It takes forever and floods the terminal. Use-Lor--max-depth. - Tip: use tab completion systematically. On top of typing less, it validates that the path exists while you write it: if it does not complete, you have made a mistake.
- Tip:
ls -ltanddu -h --max-depth=1 | sort -hrare the two diagnostic commands that most often save a night shift. Memorise them.
Exercises
Exercise 1: Resolving paths in your head
You are in /var/log/veloz. Without running anything, state which absolute path each expression is equivalent to:
..../../.../app.log../nginx/error.log~/veloz-ops/bin../../../srv/veloz/data/shipments.csv
Exercise 2: Reconnaissance of a server
On your own machine (adapting the paths if you do not have the Veloz Envíos structure), carry out this reconnaissance sequence and explain what each result tells you:
- Find out your current directory.
- List
/var/login long format, with human-readable sizes and sorted by modification date, newest first. - Identify the largest file or directory in
/var/log. - Find out the usage percentage of the partition where
/varlives. - Determine the type of the file
/etc/passwdwithout opening it.
Exercise 3: Navigating with the stack
Using only pushd, popd and dirs, write the sequence of commands that:
- Starting from
~, visits/var/log, then/etcand then/tmp, pushing each one. - Shows the numbered stack.
- Returns to
/etcby undoing a single level. - Finally goes back to
~.
State which directory you end up in after each step.
Solutions
Solution to Exercise 1
| Expression | Absolute path | Reasoning |
|---|---|---|
.. |
/var/log |
Goes up one level from /var/log/veloz |
../../.. |
/ |
Three levels: log → var → /. You cannot go above the root |
./app.log |
/var/log/veloz/app.log |
. is the current directory |
../nginx/error.log |
/var/log/nginx/error.log |
Goes up to /var/log and down into nginx |
~/veloz-ops/bin |
/home/joan/veloz-ops/bin |
~ does not depend on where you are |
../../../srv/veloz/data/shipments.csv |
/srv/veloz/data/shipments.csv |
Goes up to / and down into srv |
A detail about the third case: / is the ceiling. cd ../../../../.. from anywhere leaves you in /, with no error.
Solution to Exercise 2
# 1
pwd
# 2
ls -lht /var/log
# 3
du -h --max-depth=1 /var/log | sort -hr | head -3
# 4
df -h /var
# 5
file /etc/passwdOutput and reading of each point:
You are in your home folder.
total 312M drwxr-x--- 2 root adm 4.0K Aug 3 10:16 veloz -rw-r----- 1 syslog adm 18M Aug 3 10:16 syslog drwxr-xr-x 2 root root 4.0K Aug 3 00:00 nginx
The first entry is the most recently modified: the services writing right now. It is the starting point for investigating an ongoing incident.
The first line is the total of the directory you asked about; from there on, veloz is the biggest consumer. Note: --max-depth=1 includes the directory itself, which is why /var/log heads the list.
/var has no partition of its own: it hangs off /, which is at 76%. That is an important fact, because it means a runaway log can fill the entire system, not just /var.
It is plain text, so it can be read with cat or less with no risk. Despite the name, it does not contain passwords (those are in /etc/shadow).
Solution to Exercise 3
cd ~
pushd /var/log # -> /var/log | stack: /var/log ~
pushd /etc # -> /etc | stack: /etc /var/log ~
pushd /tmp # -> /tmp | stack: /tmp /etc /var/log ~
dirs -v
popd # -> /etc | stack: /etc /var/log ~
popd; popd # -> ~ | stack: ~Output of dirs -v at step 2:
The route: ~ → /var/log → /etc → /tmp → (popd) /etc → (popd) /var/log → (popd) ~.
Notice that each pushd pushes the directory you were in and takes you to the new one, while popd discards the top and places you in the next. An alternative for the last step would be cd ~ directly, but the exercise asked for the stack only. If you wanted to jump straight to /var/log from /tmp without popping, pushd +2 would rotate the stack to that position.
Conclusion
You now move around the system with judgement. You can break a command down into program, options and arguments; you know the FHS map and what each key directory holds; you tell absolute from relative paths and know when to use each; you handle pwd, cd, ls with their decisive options, tree and the pushd/popd stack; and you can read ls -l line by line and inspect files with file, stat, du and df before touching them. Above all, you have done your first real reconnaissance of srv-veloz-01 and you now know where every piece of Veloz Envíos lives.
So far you have been using the shell. In the next lesson, Understanding the Shell, we will open the box: what exactly happens between pressing Enter and the result appearing, why cd cannot be an external program, how Bash looks for commands in the PATH, what a subshell is and why a variable defined inside one disappears when you leave it. Understanding that mechanism is what separates someone who copies commands from someone who knows what they are doing.
Bash Programming Course
Module 1: Introduction to Bash
- What Is Bash?
- Setting Up Your Environment
- Basic Command Line Navigation
- Understanding the Shell
- Finding Help: man, help and --help
Module 2: Basic Bash Commands
- File and Directory Operations
- Text Processing Commands
- File Permissions and Ownership
- Redirection and Piping
- Wildcards and Path Expansion
- History and Keyboard Shortcuts
Module 3: Scripting Fundamentals
- Creating and Running a Script
- Variables and Constants
- Basic Operators
- Conditional Statements
- Arguments and User Input
- Quoting, Expansion and Substitution
Module 4: Intermediate Scripting
- Loops in Bash
- Functions in Bash
- Arrays and Associative Arrays
- String Manipulation
- The case Statement and Interactive Menus
- Arithmetic and Numeric Calculations
Module 5: Advanced Scripting Techniques
- Advanced File Operations
- Process Management
- Error Handling and Debugging
- Regular Expressions
- Advanced I/O: Descriptors and Here-Documents
- Modular Scripts and Reusable Libraries
Module 6: Working with External Tools
Module 7: Automation and Scheduling
- Cron Jobs
- Automating Tasks
- Backup and Restore Scripts
- Monitoring and Logging
- Services and Timers with systemd
- Remote Automation with SSH
Module 8: Best Practices and Optimization
- Writing Readable Code
- Optimizing Bash Scripts
- Security Considerations
- Version Control with Git
- Static Analysis with ShellCheck and shfmt
- Automated Testing with Bats
- Portability: POSIX sh versus Bashisms
