Linux Command Line Basics: Essential Terminal Commands
Learn the Linux terminal: move around with cd and ls, manage files, read logs with less and tail, search with find and grep, and chain commands with pipes.
The terminal is where most Linux work happens. Servers usually have no desktop at all, containers have only a shell, and every automation tool you will use later (Bash scripts, Ansible, CI pipelines, Dockerfiles) runs the same commands you type by hand. Once you can move around, find things and chain small tools together, the rest of this guide gets much easier.
This part covers the commands you will use every day: navigation, files and directories, reading files, finding files and text, redirection and pipes, and the shortcuts that save typing.
Prerequisites
- A Linux shell: a desktop terminal, a WSL window, a virtual machine or an SSH session to a cloud server. Part 2 shows how to get one.
- Nothing else. Every command here is part of a standard Ubuntu, Debian or Fedora install, apart from
tree, which you can install withsudo apt install tree(orsudo dnf install tree).
The output in this part comes from real runs on Ubuntu 24.04.5 LTS as a user called dev on a machine called devbox. Your username, hostname, dates and file sizes will differ. The commands work on a practice directory, ~/lab, that you create below, so they have something real to work on.
The Terminal, the Shell and the Prompt
Three pieces work together when you type a command:
- The terminal (GNOME Terminal, Windows Terminal, iTerm2, or the SSH client) draws text on the screen and sends your keystrokes.
- The shell reads what you type, expands wildcards and variables, and starts programs. Ubuntu, Debian and Fedora use Bash by default. macOS uses Zsh, and some people install Fish.
- The prompt is the text the shell prints when it waits for input.
A default Ubuntu prompt looks like this:
dev@devbox:~/lab$
That reads as user dev, on host devbox, in the directory ~/lab (~ is short for your home directory, /home/dev). The final $ means a regular user. A # means you are root, the administrator account, so slow down when you see it.
How a Command Is Built
Most commands follow the same shape:
command [options] [arguments]
- Options change how the command behaves. Short options use one dash and can be combined (
ls -l -ais the same asls -la). Long options use two dashes (ls --all). - Arguments are what the command works on, usually files or directories.
- A lone
--ends the options, sorm -- -strange-nameremoves a file whose name starts with a dash.
When you do not know a command, ask the system:
man lsopens the full manual page (pressqto quit,/wordto search).man -k keywordsearches all manual page titles. Minimal cloud and container images often leave manual pages out; on Ubuntu, install theunminimizepackage and runsudo unminimizeto put them back.help cdexplains shell builtins, which have no manual page of their own.typetells you what will run. On a desktop Ubuntu install,type lsprintsls is aliased to 'ls --color=auto', because the default~/.bashrcadds that alias.
Set Up the Practice Directory
The examples in this guide use a small practice directory, ~/lab: a tiny static website, a web server log with 48 requests and a short notes file. Create it with this script. In your home directory, open an editor with nano make-lab.sh, paste the script, save with Ctrl+O and Enter, and exit with Ctrl+X:
#!/usr/bin/env bash
# make-lab.sh: create ~/lab, the practice directory used in this guide
set -euo pipefail
lab="$HOME/lab"
if [[ -e "$lab" ]]; then
echo "$lab already exists; rename or delete it first" >&2
exit 1
fi
mkdir -p "$lab/site/css" "$lab/site/images" "$lab/logs"
cat > "$lab/site/index.html" <<'HTML'
<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><title>Lab site</title><link rel="stylesheet" href="css/style.css"></head>
<body><h1>Hello from the Linux lab</h1><p>Served from ~/lab/site.</p></body>
</html>
HTML
cat > "$lab/site/about.html" <<'HTML'
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><title>About</title></head>
<body><h1>About</h1><p>A tiny static site for practice.</p></body></html>
HTML
printf 'body { font-family: sans-serif; max-width: 40rem; margin: 2rem auto; }\n' > "$lab/site/css/style.css"
printf 'Practice notes\n- learn ls, cd and pwd\n- learn grep and find\n- try pipes\n' > "$lab/notes.txt"
# A web server log with 48 requests, generated the same way every time
python3 - "$lab/logs" <<'PY'
import random, sys
random.seed(7)
ips = ['203.0.113.10'] * 9 + ['198.51.100.23'] * 6 + ['192.0.2.44'] * 4 + ['203.0.113.77'] * 2 + ['198.51.100.5']
paths = [('/index.html', 200, 612)] * 10 + [('/about.html', 200, 418)] * 4 + [('/css/style.css', 200, 74)] * 4 + \
[('/css/style.css', 304, 0)] * 2 + [('/favicon.ico', 404, 162)] * 3 + [('/wp-login.php', 404, 162)] * 2 + \
[('/admin', 404, 162)] + [('/api/health', 500, 21)] * 2
agents = ['curl/8.5.0', 'Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15']
lines, t = [], 8 * 3600 + 15 * 60
for i in range(48):
ip = random.choice(ips)
path, status, size = random.choice(paths)
if path == '/wp-login.php':
ip = '198.51.100.5'
t += random.randint(5, 140)
agent = 'python-requests/2.32.3' if ip == '198.51.100.5' else random.choice(agents)
stamp = f'23/Oct/2026:{t // 3600:02d}:{t % 3600 // 60:02d}:{t % 60:02d} +0000'
lines.append(f'{ip} - - [{stamp}] "GET {path} HTTP/1.1" {status} {size} "-" "{agent}"')
with open(sys.argv[1] + '/access.log', 'w') as f:
f.write('\n'.join(lines) + '\n')
with open(sys.argv[1] + '/error.log', 'w') as f:
for l in lines:
if '" 500 ' in l:
f.write('[' + l.split('[')[1].split(']')[0] + '] ERROR health check failed: database connection refused\n')
PY
echo "created $lab"
The script only creates files inside ~/lab and refuses to run if that directory already exists. Later parts start from a fresh copy: rm -rf ~/lab && bash ~/make-lab.sh resets it whenever you want the same starting point as the examples.
Moving Around the File System
Everything in Linux lives in one tree that starts at / (Part 4 explains the layout). Three commands get you around: pwd prints where you are, ls lists what is there, and cd changes directory.
A few things to notice:
- In
ls -l, the first column shows the file type and permissions (dis a directory), followed by the link count, owner, group, size, modification time and name. Part 4 explains each field. - Entries that start with a dot (
.bashrc,.ssh) are hidden.ls -ashows them, along with.(this directory) and..(the parent). cdwithout arguments takes you home,cd ..goes up one level, andcd -returns to where you were before.
Paths that start with / are absolute (/home/dev/lab/site). All other paths are relative to the current directory (site/css). ~ expands to your home directory, and ~/lab works from anywhere.
Press Tab while you type a path or command name. The shell completes it, or lists the options when the start is ambiguous. Tab completion saves typing and catches typos before you press Enter.
Creating, Copying, Moving and Deleting
The rmdir error is expected: drafts/2026 still contains a directory, and rmdir refuses to delete anything that is not empty. rm -r deletes a directory with everything inside it.
rm does not use a trash can. Deleted files are gone, and rm -rf deletes without asking. Before you run rm -rf with a variable or a wildcard, run ls with the same argument first and read what it matches. Use rm -i to confirm each file, or rm -I to confirm once before deleting more than three files.
cp and mv overwrite the destination without asking. Add -i to be asked first, or -n to never overwrite. Brace expansion creates several names at once: mkdir -p app/{src,tests,docs} makes three directories.
Reading Files
catprints a whole file. It is for short files; for anything longer, use a pager.less fileopens a file one screen at a time. Use the arrow keys or Space to move,/textto search forward,nfor the next match,Gto jump to the end, andqto quit.less +F filefollows a growing file and Ctrl+C stops following.headandtailprint the first or last 10 lines (-nchanges the count).tail -f logs/access.logkeeps the file open and prints new lines as they are written, which is how you watch a log while you test something. On systemd machines, many services log to the journal instead, andjournalctl -fdoes the same job (Part 10).
Wildcards and Quoting
The shell expands wildcards before the command runs, so ls *.html receives a list of file names, not the *.
| Pattern | Matches |
|---|---|
* |
any characters, including none |
? |
exactly one character |
[abc] |
one character from the set |
{a,b} |
brace expansion: generates both words, no match |
**/ |
any depth of directories, after shopt -s globstar |
Spaces separate arguments, so a file name with a space needs quotes:
Double quotes keep the text together but still expand variables ("$HOME"). Single quotes keep everything literal ('$HOME' stays $HOME). When in doubt, quote. Unquoted variables that contain spaces are a common source of broken scripts (Part 9).
Finding Files
find walks a directory tree and tests every entry. It is slower than an index, but it is always accurate and it can act on what it finds.
Useful tests: -name and -iname (case-insensitive), -type f or -type d, -mtime -7 (changed in the last 7 days), -size +100M, -user, and -perm. Add -maxdepth 2 to limit how deep it goes, and 2>/dev/null to hide "Permission denied" messages when you search system directories.
To find files by name across the whole disk, locate name searches a prebuilt index and answers instantly. On Ubuntu and Debian, install the plocate package; a daily systemd timer keeps its index up to date, and sudo updatedb refreshes it now.
Searching Inside Files With grep
grep prints the lines that match a pattern.
Other flags you will use often: -w matches whole words only, -o prints only the matching part, -A 3 and -B 3 show lines after and before each match, and --exclude-dir=node_modules skips a directory when you search recursively.
Redirection and Pipes
Every program has three standard streams: standard input (0), standard output (1) and standard error (2). The shell can point them at files or at other programs.
| Syntax | What it does |
|---|---|
cmd > file |
write standard output to a file, replacing its contents |
cmd >> file |
append standard output to a file |
cmd 2> file |
write standard error to a file |
cmd > file 2>&1 |
write both streams to the same file (order matters) |
cmd &> file |
Bash shorthand for the line above |
cmd < file |
read standard input from a file |
cmd1 | cmd2 |
send the output of cmd1 to the input of cmd2 |
cmd | tee file |
show the output and save a copy |
A pipe connects small tools into a bigger one. Each tool does one job: cut takes a column, sort sorts, uniq -c counts repeated lines, and head keeps the top.
awk '{print $9}' prints the ninth space-separated field of each line, which in this log format is the status code. uniq only collapses lines that are next to each other, which is why sort comes first.
xargs turns lines of input into arguments for another command. find . -name "*.log" -print0 | xargs -0 wc -l counts lines in every log file and copes with spaces in names.
Chaining Commands and Exit Codes
Every command ends with an exit code: 0 means success, anything else means failure. The shell stores the last one in $?, and the chaining operators use it.
cmd1 ; cmd2runs both, one after the other.cmd1 && cmd2runscmd2only ifcmd1succeeded. Use it for steps that depend on each other.cmd1 || cmd2runscmd2only ifcmd1failed.cmd &runs a command in the background and gives you the prompt back (Part 7 covers jobs).
History and Keyboard Shortcuts
Bash keeps your command history in ~/.bash_history. history lists it, and these shortcuts save the most time:
| Keys / syntax | What it does |
|---|---|
| Ctrl+R | search history backwards; press again for older matches |
| ↑ / ↓ | previous / next command |
!! |
the previous command, for example sudo !! after "Permission denied" |
!$ or Alt+. |
the last argument of the previous command |
| Ctrl+C | stop the running command |
| Ctrl+D | end of input; at an empty prompt it closes the shell |
| Ctrl+A / Ctrl+E | jump to the start / end of the line |
| Ctrl+U / Ctrl+K | delete to the start / end of the line |
| Ctrl+W | delete the word before the cursor |
| Ctrl+L | clear the screen |
Start a command with a space and Bash leaves it out of the history (Ubuntu's default HISTCONTROL=ignoreboth). Use that when you must type a secret, but prefer reading secrets from a file or a prompt.
Aliases, ~/.bashrc and PATH
An alias is a shortcut for a longer command. Aliases you type disappear when the shell exits, so put the ones you want to keep in ~/.bashrc, which Bash reads every time it starts an interactive shell:
# ~/.bashrc
alias ll='ls -alF'
alias gs='git status'
alias ports='ss -tulpn'
Run source ~/.bashrc (or open a new terminal) to load the changes. Ubuntu's default ~/.bashrc already defines ll, la and l.
When you type a command name, the shell looks for it in each directory of the PATH variable, from left to right, and runs the first match. A fresh Ubuntu login gets its PATH from /etc/environment:
To run your own scripts by name, put them in ~/.local/bin. Ubuntu's ~/.profile adds that directory to PATH at login if it exists, so create it and log in again. On other systems, add export PATH="$HOME/.local/bin:$PATH" to ~/.bashrc.
Editing Files in the Terminal
You need a terminal editor for configuration files on servers.
- nano is the easiest. The shortcuts are listed at the bottom of the screen: Ctrl+O saves, Ctrl+X exits, Ctrl+W searches, and
^means Ctrl. - vim (or
vi) is installed almost everywhere. It starts in normal mode: pressito type, Esc to stop typing, then:wqand Enter to save and quit, or:q!to quit without saving. Runvimtutorfor a 30-minute introduction.
Set the editor that other tools open (for example crontab -e, git commit and visudo) with export EDITOR=nano in ~/.bashrc, or on Debian and Ubuntu with sudo update-alternatives --config editor.
Ubuntu 25.10 and 26.04 LTS replaced most GNU coreutils (ls, sort, cut, date and many more) with the Rust rewrite from the uutils project; cp, mv and rm followed in Ubuntu 26.10. The commands and options in this part work the same way there; a few edge cases and error messages differ. See what changes with Rust coreutils if you maintain scripts.
Common Mistakes
- Unquoted names with spaces.
rm my file.txttries to delete two files,myandfile.txt. Quote it, or press Tab and let the shell escape it. - Overwriting with
>.sort data.txt > data.txtempties the file beforesortreads it. Write to a new file, then move it.set -o noclobbermakes>refuse to overwrite existing files. sudowith a redirection. Insudo echo "x" > /etc/file, the redirection runs in your own shell, without root, and fails. Useecho "x" | sudo tee /etc/file(ortee -ato append). See how to redirect output to a privileged file.rm -rf $DIR/with an empty variable. IfDIRis empty, that becomesrm -rf /. Quote variables and use${DIR:?}in scripts so an empty value stops the command.- Copying commands from the web without reading them. Check what a command does before you paste it, especially when it starts with
sudoor pipes a download intobash.
Summary
- The prompt shows user, host and directory;
$is a regular user,#is root. pwd,lsandcdmove you around; Tab completes names;~is your home directory.mkdir -p,cp -r,mvandrm -rmanage files.rmhas no undo.less,head,tailandtail -fread files and logs.findlocates files by name, type, age and size;grepfinds text inside them.>,>>,2>and|connect commands, and exit codes drive&&and||.
Next: Linux File System Hierarchy, where you learn what lives in /etc, /var and /usr, and how file permissions work.
Keep Learning
- Practice in the browser: Linux Terminal simulator
- Linux Command Line Quiz
- What does 2>&1 mean?
- Find files containing specific text
- Interview questions: using grep for text search
Tags
Found an issue?