Shell basics: PATH, variables, streams and sudo
How the shell finds commands, how variables and dotfiles shape every session, where output and errors go, and how to use root power without hurting yourself.
By the end of this lesson you'll be able to
0 of 5 completeGetting help fast
You'll never remember every option, and nobody expects you to. You need to find the answer in seconds:
$ ls --help | less # short summary of options, built into most commands $ man ls # the full manual. /word searches, n next match, q quits $ man 5 crontab # section 5: the crontab FILE format, not the command (section 1) $ man -k password # search all manual pages for a word (same as apropos) $ type cd # what kind of command is this? cd is a shell builtin
ls --help | lesslslist files|pipe: send output to the next commandlessscroll through a file; q quits
Manual sections you'll meet: 1 commands, 5 file formats (/etc/fstab, crontab), 8
admin commands (useradd, sshd). When a command has no man page, try help (for bash
builtins, such as help cd) or command --help.
How the shell finds a command
When you type grep error app.log, bash has to decide what grep is. It checks, in order:
$ echo $PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin $ type grep ls cd grep is /usr/bin/grep ls is aliased to `ls --color=auto' cd is a shell builtin $ which python3 # the file PATH would run /usr/bin/python3 $ ./deploy.sh # not in PATH? Give the path yourself
echo $PATHechoprint text
cd has to be a builtin: a separate program could only change its own folder, not your shell's.
"Command not found" means one of three things: a typo, the program isn't installed, or it's
installed in a folder that isn't in PATH. Check with type name or ls the folder you expect.
Variables and the environment
$ APP_ENV=staging # a shell variable: this shell only. No spaces around = $ echo "$APP_ENV" staging $ bash -c 'echo "[$APP_ENV]"' # a child process can't see it [] $ export APP_ENV # now it's an environment variable, inherited by children $ bash -c 'echo "[$APP_ENV]"' [staging] $ env | grep APP_ENV # every environment variable APP_ENV=staging $ unset APP_ENV # remove it $ APP_ENV=prod ./deploy.sh # set it for ONE command only
echo "$APP_ENV"echoprint text
env | grep APP_ENV|pipe: send output to the next commandgrepfind lines that match a pattern
Every program you start gets a copy of the environment. That's how PATH, HOME, USER,
LANG and your app's settings reach it, and why changing a variable in one terminal doesn't
change it in another.
Quoting
| You write | Bash does |
|---|---|
echo "$HOME/logs" | expands variables: /home/rajeev/logs |
echo '$HOME/logs' | nothing: prints $HOME/logs literally |
echo "Today is $(date +%A)" | runs the command inside $( ) and inserts its output |
rm "$FILE" | always quote variables: a name with a space stays one argument |
Making it permanent: the dotfiles
Variables, aliases and PATH changes vanish when the shell closes. To keep them, put them in a
file bash reads at start-up:
| File | Read by | Put here |
|---|---|---|
~/.bashrc | every interactive bash | aliases, PATH, EDITOR, prompt |
~/.profile (or ~/.bash_profile) | login shells (SSH logins) | things for the whole session; on Ubuntu it sources ~/.bashrc |
/etc/profile, /etc/bash.bashrc | everyone | system-wide defaults (admins only) |
$ echo 'alias ll="ls -lah"' >> ~/.bashrc $ source ~/.bashrc # reload now, without opening a new terminal
echo 'alias ll="ls -lah"' >> ~/.bashrcechoprint text>>append output to a file
History
history lists what you ran. Ctrl+R searches it as you type (press it again for older
matches). !! repeats the last command, so sudo !! re-runs it with sudo.
Three streams in, three streams out
Every process starts with three open channels, each with a number:
That separation is why ls /etc /nope > out.txt still shows the error on screen: you redirected
stream 1, and the error travels on stream 2.
Redirects and pipes
$ ls /etc > files.txt # stdout to a file (overwrites it) $ ls /etc >> files.txt # append instead $ ls /etc /nope 2> errors.txt # only errors to a file $ ls /etc /nope > all.txt 2>&1 # both, to the same file (order matters) $ ls /etc /nope &> all.txt # bash shorthand for the same $ find / -name "*.conf" 2> /dev/null # throw errors away $ sort < names.txt # stdin from a file $ ps aux | grep nginx # pipe: output of one becomes input of the next $ ./deploy.sh | tee deploy.log # see output AND save it $ find . -name "*.log" | xargs gzip # turn lines of input into arguments
ls /etc > files.txtlslist files>write output to a file, replacing it
ls /etc >> files.txt>>append output to a file
ls /etc /nope > all.txt 2>&12>&1send errors to the same place as output
find / -name "*.conf" 2> /dev/nullfindsearch for files-namematch the file name (wildcards allowed)
sort < names.txtsortsort lines<read input from a file
ps aux | grep nginxpslist processes|pipe: send output to the next commandgrepfind lines that match a pattern
./deploy.sh | tee deploy.logteewrite input to a file and pass it on
find . -name "*.log" | xargs gzip.this folder
2>&1 reads as "send stream 2 wherever stream 1 is going right now". So the file redirect
has to come first. /dev/null is a bin that throws away whatever you write to it.
sudo and root
root (user id 0) can do anything: read every file, kill any process, delete the system. Your normal account can't, and that's protection, not a limitation.
$ whoami rajeev $ sudo systemctl restart nginx # one command as root; asks YOUR password $ sudo -l # what am I allowed to run with sudo? $ sudo -i # a root shell. The prompt ends in #. Leave with exit $ sudo !! # re-run the last command with sudo
whoamiwhoamiprint your user name
sudo systemctl restart nginxsudorun the next command as rootsystemctlcontrol systemd services
sudo command | su - | |
|---|---|---|
| Asks for | your password | root's password |
| Scope | one command | a whole root shell |
| Logged as | your name, every command | "root", nothing more |
Prefer sudo for each command over a long-lived root shell. If you do open one with sudo -i,
do the one task and exit.
Install a tool for yourself, and log it
You keep running the same health-check commands. Turn them into a script called hello that runs from any folder, without sudo, and keeps a log of every run with output and errors.
~/bin/hello and ~/hello.logReveal one safe solution
$ mkdir -p ~/bin $ cat > ~/bin/hello <<'EOF' #!/bin/bash echo "$(date '+%F %T') host=$(hostname) user=$USER" ls /nope # a deliberate error, to prove errors are logged too EOF $ chmod +x ~/bin/hello $ echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc $ source ~/.bashrc $ type hello hello is /home/rajeev/bin/hello $ cd /tmp && hello >> ~/hello.log 2>&1 $ cat ~/hello.log 2026-10-13 10:42:07 host=web-01 user=rajeev ls: cannot access '/nope': No such file or directory
mkdir -p ~/binmkdirmake a directory-pcreate missing parents, no error if it exists
cat > ~/bin/hello <<'EOF'catprint a file>write output to a file, replacing it
chmod +x ~/bin/hellochmodchange permissions
echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrcechoprint text>>append output to a file
cd /tmp && hello >> ~/hello.log 2>&1cdchange directory&&run the next command only if this one worked2>&1send errors to the same place as output
Putting ~/bin first in PATH means your tools win over system ones with the same name. Use
that knowingly: never name a script ls or test.
Try it yourself
0 of 6 steps doneRun each task in your own account or terminal, then tick it off.
Quick check
You wrote a script deploy.sh in your current folder. Typing deploy.sh says "command not found". Why?
Official reading for this lesson
Or get every quiz answer right and it completes itself.