Type a single asterisk, press Enter, and the shell answers a question you never asked:
$ echo *
Documents Downloads notes.txt projects
$ ls *.log
report.log system.log
The echo program never saw an asterisk. Neither did ls. Before either command ran, bash scanned your line, found the *, and swapped it for the matching filenames in the current directory. Each command received a finished list of words. That gap, between the characters you type and the words the program actually gets, is the whole story of the shell. Miss it and half of Linux feels like magic or malfunction. See it clearly and you can predict what any command line will do before your finger leaves the Enter key.
New to the shell: open any Linux box, or a free cloud VM, and type every command as you read. Already scripting for a living: skip to the quoting and PATH sections, where the rules that bite in production live. This follows Part 2, the map of the filesystem you are about to move through.
The shell is a program, usually bash, that reads a line, rewrites it through a fixed set of expansions, finds the command, and runs it. Learn three things and the rest follows: the order those expansions happen in, how quoting switches them off, and how the shell decides which program a name points to. Pipes, redirection, and history all sit on top of that one loop.
The program behind the prompt
Three words get used interchangeably and they are not the same thing. The terminal is the window, or the full-screen console, that shows text and takes your keystrokes. The shell is the program running inside it that reads what you type and decides what to do. The prompt is the short string the shell prints when it is ready for the next command, usually ending in $ for a normal user and # for root. Swap the terminal and keep the same shell, or swap the shell and keep the terminal; they are separate layers.
On almost every Linux distribution the default shell is bash. Ask the system what you are actually running:
$ echo $SHELL
/bin/bash
$ ps -p $$
PID TTY TIME CMD
4812 pts/0 00:00:00 bash
$SHELL holds the shell set as your login default. $$ is the process ID of the shell you are typing into right now, so ps -p $$ shows that live process by name. If it says bash, every rule in this part applies. If it says zsh or sh, most still do, but a few expansion details differ. The same shell greets you when you SSH into a cloud server or open the console of a virtual machine. Learn it once and it travels everywhere.
What happens between Enter and the program
Press Enter and bash runs a short, fixed sequence before a single byte of your command executes. It splits the line into words, runs each kind of expansion in a set order, works out which program the first word names, then hands off to it. Here is the path your line takes:
The order of expansions
The order is written in the bash manual, and it explains almost every surprising result you will hit. Bash performs, in this sequence: brace expansion; tilde expansion; variable, arithmetic, and command substitution; word splitting; then filename expansion. Quote removal happens last. Only three of those steps can change how many words your line becomes, and the one that catches people is word splitting.
| Syntax | Expansion | Example gives |
|---|---|---|
{a,b,c} | Brace | a b c |
~ | Tilde (home) | /home/pranay |
$name | Variable | the value stored in name |
$(date) | Command substitution | output of the command |
$((6*7)) | Arithmetic | 42 |
*.log | Filename (glob) | matching filenames, sorted |
Watch a line get rewritten
You can make the expansions visible with echo, which just prints the words it receives. Set a variable, then see what the shell hands over:
$ name="Dr Pranay"
$ echo Hello $name
Hello Dr Pranay
$ echo ~
/home/pranay
$ echo file{1,2,3}.txt
file1.txt file2.txt file3.txt
$ echo $((6 * 7))
42
Each line shows a different expansion doing its job before echo ever runs: variable, tilde, brace, arithmetic. The command itself is simple. All the cleverness happened earlier, in the shell, and the program only saw the result.
Quoting and what it switches off
Quotes are how you tell the shell to stop expanding. There are two kinds and they behave differently. Double quotes block word splitting and filename expansion but still let variables and command substitution run. Single quotes are literal: everything inside is passed through untouched, dollar signs and all. No quotes means every expansion is live.
| You write | Variables expand | Globs expand | echo prints |
|---|---|---|---|
$HOME/*.txt | yes | yes | /home/pranay/notes.txt … |
"$HOME/*.txt" | yes | no | /home/pranay/*.txt |
'$HOME/*.txt' | no | no | $HOME/*.txt |
Now the failure this causes. A file named with a space is the simplest way to feel word splitting bite:
$ ls
my report.txt
$ rm my report.txt
rm: cannot remove 'my': No such file or directory
rm: cannot remove 'report.txt': No such file or directory
$ rm "my report.txt"
$
Unquoted, the shell split my report.txt on the space into two separate arguments, and rm went looking for two files that do not exist. Quote the name and it stays one argument. The command was never confused; the shell handed it the wrong words.
A script sets dir="/var/log/old logs" and later runs rm -rf $dir. Unquoted, the shell splits that value on the space into /var/log/old and logs, and rm tries to delete both. On a bad day one of them exists and is not what you meant.
Quote it, rm -rf "$dir", and the value stays a single argument. The rule is short: quote every variable that could hold a path, a filename, or anything a user typed. That habit is worth more than any single command you will memorize.
Where commands come from
When you type ls, how does bash find the program? For an external command it walks a list of directories held in the PATH variable, left to right, and runs the first match it finds. Look at the list:
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Not every command is a program on disk, though. Some are built into the shell itself. cd has to be a builtin: a separate program could only change its own working directory and then exit, leaving yours untouched. This is exactly where a lot of beginners reach for the wrong tool.
Use type, not which
The usual advice is to run which ls to locate a command. I disagree, and I would push back on any guide that teaches it first. which is a separate external program that only knows about files in PATH. It cannot see aliases, shell functions, or builtins, so it quietly lies about how your command will actually run. type is a shell builtin that reports how bash itself resolves the name:
$ type cd
cd is a shell builtin
$ type -a ls
ls is aliased to `ls --color=auto'
ls is /usr/bin/ls
$ which cd
$
See the gap. which cd prints nothing, because cd is not a file, and a beginner reads that empty output as "command not found" when the command is fine. type cd tells the truth. type -a ls goes further and shows both the alias that fires first and the real program underneath it. When a command does something you did not expect, type -a is the first thing to run. Reach for command -v when you want the same answer inside a portable script.
Prove that one name can resolve to more than one thing, and see the order bash uses. Run:
type -a echo
type -a ls
If echo reports both a shell builtin and /usr/bin/echo, you have found why a builtin and a program can share a name. The builtin wins, because bash checks builtins before it walks PATH.
Rewiring input and output
Every command starts life wired to three streams: standard input, where it reads; standard output, where normal results go; and standard error, where problems go. By default input comes from your keyboard and both outputs go to your screen, which is why errors and results look tangled together. Redirection rewires those streams to files. A pipe wires one command’s output straight into the next command’s input.
| Operator | What it does | Example |
|---|---|---|
> | Send stdout to a file, overwriting | ls > out.txt |
>> | Append stdout to a file | echo done >> log |
2> | Redirect stderr only | cmd 2> err.txt |
2>&1 | Send stderr where stdout goes | cmd > all 2>&1 |
< | Read stdin from a file | sort < names |
| | Pipe stdout into the next command | ps | grep ssh |
Two facts save hours. Stdout and stderr are separate streams, so you can throw away errors while keeping results. And the number in front matters: 1 is stdout, 2 is stderr. Here is the difference on a real command:
$ find / -name '*.conf' 2>/dev/null | head -3
/etc/host.conf
/etc/resolv.conf
/etc/ld.so.conf
Without 2>/dev/null, that find floods the screen with "Permission denied" lines from directories you cannot read, burying the three results you wanted. Sending stream 2 to /dev/null, the system’s trash chute, drops the noise and keeps the signal. To capture both streams in one file, send stdout to the file and point stderr at the same place with 2>&1:
$ ./backup.sh > backup.log 2>&1
The pipe is the idea that made Unix: small tools, each doing one job, chained so the output of one becomes the input of the next. Count how many processes you own in a single line:
$ ps -u pranay | grep -v grep | wc -l
37
ps lists your processes, grep -v drops the grep line itself, and wc -l counts what is left. None of the three knows the others exist. The shell built the assembly line between them.
Recall and speed at the keyboard
Everything so far assumed you retype commands. You will not. Bash keeps a history of what you have run and hands you fast ways to reach back into it. These shortcuts are most of the gap between someone who pecks at the keyboard and someone who moves through it. Start with history:
$ history | tail -3
511 systemctl status nginx
512 journalctl -u nginx --since '10 min ago'
513 history | tail -3
$ !512
journalctl -u nginx --since '10 min ago'
!512 reruns command number 512 from that list. !! reruns the previous command, which is why sudo !! is the reflex after a command fails for lack of privilege: it repeats the last line with sudo in front. Better than either is reverse search. Press Ctrl-R and start typing any fragment of an old command, and bash walks backward through history to the most recent match. Press Ctrl-R again to step further back, then Enter to run it or an arrow key to edit it first.
| Keys | What it does |
|---|---|
Tab | Complete a command or filename; press twice to list options |
Ctrl-R | Search backward through command history |
Ctrl-A / Ctrl-E | Jump to start / end of the line |
Ctrl-U / Ctrl-K | Delete to start / to end of the line |
Ctrl-C | Cancel the running command |
Ctrl-D | Signal end of input; on an empty line, log out |
Ctrl-L | Clear the screen, keep the current line |
Tab completion is the one to build into muscle first. Type a few letters of a command or a path, press Tab, and bash finishes it when the match is unique or shows the choices when it is not. It saves keystrokes, and more usefully it catches typos in real time: if Tab refuses to complete a filename, the file is not where you think it is. One caution on !!. Because it pulls back the exact previous line, running sudo !! after a destructive command repeats that destruction with root privileges. Glance at the history line before you rerun it.
Questions I actually get
Why does cd inside a script not change my shell’s directory? Because the script runs in a child process with its own shell. It changes its own working directory and then exits, and yours never moved. Run the script with source script.sh (or . script.sh) to execute it in your current shell instead of a child.
Single or double quotes, which do I reach for? Default to double quotes when the string contains a variable you want expanded, and single quotes when you want the text completely literal. If you are not sure and there is no variable inside, single quotes are the safe choice.
Why do I need ./ to run a script in the current directory? Because the current directory is deliberately not in PATH on most systems, for safety. Typing ./script.sh gives the shell an explicit path, so it runs that file directly instead of searching PATH and finding nothing.
Why does the order in > file 2>&1 matter? 2>&1 means "send stderr wherever stdout points right now." Put it after the file redirect and stderr follows stdout into the file. Put it first, 2>&1 > file, and stderr copies the screen before stdout is redirected, so errors still land on your terminal.
Is bash the same as sh? Not quite. sh is the POSIX shell, and on many systems it is a slimmer program (often dash) with fewer features. A script that works in bash can fail under sh when it uses bash-only syntax. Put #!/bin/bash at the top of scripts that rely on bash features.
Walk me through what happens between pressing Enter and a program running.
Bash splits the line into tokens, then runs expansions in order: brace, tilde, variable and command substitution and arithmetic, then word splitting, then filename expansion, and finally quote removal. It resolves the first word by checking aliases, then functions and builtins, then walking PATH for an executable. Then it forks a child process and calls exec to replace that child with your program, passing the finished argument list. The detail that shows real understanding is this: expansion happens before the program starts, so the command never sees your variables or wildcards, only the words they turned into.
You can now read a command line the way bash does: split, expand, resolve, run. That one model turns wildcards, quoting bugs, and redirection from mysteries into things you can predict before you press Enter. Next we stay at the keyboard and use these commands to create, copy, move, and find files and directories, the daily work the shell exists for. Follow the series in order from the Complete Guide.
References
Shell Expansions, Bash Reference Manual (GNU)
Word Splitting, Bash Reference Manual (GNU)
Why the use of the which command is not recommended, Baeldung on Linux


DrJha