Using the Command-line
I live on the terminal. The two windows I always have open: a terminal and a browser. This is a guide to how I use the command-line for development, productivity, and daily computing tasks. Whether you're new to the command-line or looking to level up your skills, this guide provides practical tips and real-world examples that build from foundations to advanced techniques.
Prerequisites
To get the most from this guide, you should:
- Know how to open a terminal application
- Understand basic file system concepts (files, folders)
- Be comfortable typing commands
Terminology used in this guide:
- Terminal: The application that displays a command-line interface (e.g., Terminal.app, iTerm2, GNOME Terminal)
- Shell: The program that interprets commands (e.g., bash, zsh, fish)
- Command-line: The text interface where you type commands
New to terminals entirely? This guide starts with fundamentals and builds progressively. You can stop at your comfort level and return as you grow.
Part 1: Getting Started 🌱
This section covers essential concepts and setup. These are the foundations that everything else builds upon.
Understanding Your Shell Environment
When you open a terminal, you're running a shell - a program that interprets your commands. The most common shells are bash (Bourne Again SHell) and zsh (Z Shell). macOS uses zsh by default; Linux typically uses bash.
Shell configuration files control your environment. When you start a shell:
- Login shells read
~/.zprofile
or~/.bash_profile
- Interactive shells read
~/.zshrc
or~/.bashrc
This is where you put aliases, functions, and environment customization. Changes take effect when you open a new terminal or run source ~/.zshrc
(or source ~/.bashrc
).
Environment variables are settings your shell uses. Key ones:
$PATH
: Directories where the shell looks for commands$HOME
: Your home directory (same as~
)$PWD
: Your current working directory
Check a variable: echo $PATH
Essential Setup
Use a Package Manager
On Linux systems, use your distribution's package manager (apt, dnf, pacman). On macOS, use Homebrew, which provides the same streamlined package management experience.
Install Homebrew on macOS:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Then follow the installer's instructions to add Homebrew to your PATH.
Manage Your Dotfiles with Version Control
Your dotfiles (.zshrc
, .vimrc
, etc.) are your command-line configuration. Use source control to track them and sync across systems. I keep mine in a dotfiles github repo.
Simple dotfiles workflow:
- Create a
~/dotfiles
directory and initialize git - Move your config files there (e.g.,
mv ~/.zshrc ~/dotfiles/zshrc
) - Create symlinks:
ln -s ~/dotfiles/zshrc ~/.zshrc
- Commit and push to GitHub
The symlink means edits to ~/.zshrc
automatically update your repo. When setting up a new system, clone your repo and recreate the symlinks.
Understanding File Paths
Absolute paths start from the root directory: /Users/mkaz/projects/myapp
Relative paths start from your current location: ./projects/myapp
or just projects/myapp
Special path shortcuts:
~
: Your home directory.
: Current directory..
: Parent directory-
: Previous directory (withcd
)
Navigating the File System
Basic navigation:
pwd # print working directory (where am I?)
ls # list files
ls -la # list all files (including hidden) with details
cd /path/to/dir # change directory
cd # go straight to home directory
cd - # return to previous directory
Quick directory movement:
Create these aliases to save time moving up directories:
alias cd..='cd ..'
alias cd...='cd ../../'
alias cd....='cd ../../../'
Advanced: Jump to directories with Zoxide
Zoxide tracks your most-used directories and lets you jump to them with fragments. Install it:
brew install zoxide
Add to your shell config:
eval "$(zoxide init zsh)" # or bash
Try it now:
- Navigate to a project:
cd ~/Documents/MyProject
- Go somewhere else:
cd ~
- Jump back instantly:
z MyProject
Zoxide ranks directories by frequency and recency. Type z
followed by any fragment of a directory name.
Basic File Operations
mkdir dirname # create directory
mkdir -p path/to/dir # create nested directories (makes parent dirs)
cp file1 file2 # copy file
cp -r dir1 dir2 # copy directory recursively
mv oldname newname # move/rename file
rm file # remove file
rm -rf dirname # remove directory and contents (BE CAREFUL!)
Pro tip: Add alias mkdir='mkdir -p'
so mkdir always creates parent directories as needed.
Understanding Input and Output Streams
Unix commands work with three streams:
- stdin (standard input): Data coming into a command
- stdout (standard output): Normal output from a command
- stderr (standard error): Error messages from a command
You can redirect these streams:
ls -l > files.txt # redirect stdout to file (overwrite)
ls -l >> files.txt # append stdout to file
ls -l 2> errors.txt # redirect stderr to file
ls -l > /dev/null # discard stdout (errors still show)
ls -l > /dev/null 2>&1 # discard both stdout and stderr
The pipe operator |
connects commands by sending one command's stdout to another's stdin:
ls -l | grep myfile # list files, filter to lines containing "myfile"
This is the magic glue of Unix - combining simple tools to solve complex problems.
Part 2: Daily Workflows 🔧
These are the productivity techniques that will save you the most time every day.
Time-Saving with Aliases
Aliases let you create shortcuts for commands you run frequently. The average developer types the same commands hundreds of times daily - aliases save thousands of keystrokes.
Create useful aliases:
alias ll='ls -la'
alias ..='cd ..'
alias gs='git status'
alias gd='git diff'
Include sudo in aliases:
alias alog='sudo tail -f /var/log/apache2/error.log'
alias apt='sudo aptitude'
Combine commands:
alias xps='ps -ax'
alias xpsg='ps -ax | grep -i'
Bypass an alias temporarily by prefixing with \
: \ls
runs the original ls
command.
Where to put aliases: Add them to your shell config file (~/.zshrc
or ~/.bashrc
).
Discover what to alias: Check your most-used commands:
history | awk '{print $2}' | sort | uniq -c | sort -rn | head -10
Create aliases for the commands you run most often.
Powerful Functions
When aliases aren't enough, use shell functions. Functions let you use arguments in flexible ways and add logic.
Simple function example:
# List the last 10 files in any directory
function lsr() {
ls -lrt "$@" | tail -n 10
}
Usage: lsr /etc/
lists the 10 most recently modified files in /etc/
.
Function with error handling:
# Create directory and cd into it
function mkcd() {
if [ -z "$1" ]; then
echo "Usage: mkcd <directory>"
return 1
fi
mkdir -p "$1" && cd "$1"
}
Function with conditional logic:
# Quick backup of a file
function backup() {
if [ ! -f "$1" ]; then
echo "File $1 does not exist"
return 1
fi
cp "$1" "$1.backup.$(date +%Y%m%d_%H%M%S)"
echo "Backed up to $1.backup.$(date +%Y%m%d_%H%M%S)"
}
Where to put functions: Add them to your shell config file, or create a separate ~/dotfiles/functions
file and source it:
# In your .zshrc or .bashrc
source ~/dotfiles/functions
System-specific configuration:
I bounce between Macs and Linux, so I maintain system-specific configs:
SYS_OS=$(uname -s)
SHORT_HOSTNAME=$(hostname -s)
# Load system-specific aliases
if [[ "$SYS_OS" == "Linux" ]]; then
source ~/dotfiles/aliases.linux
else
source ~/dotfiles/aliases.mac
fi
# Load host-specific config if it exists
if [[ -e ~/dotfiles/profile.$SHORT_HOSTNAME ]]; then
source ~/dotfiles/profile.$SHORT_HOSTNAME
fi
Working with Files
View files without opening an editor:
cat file.txt # output entire file
head file.txt # first 10 lines
tail file.txt # last 10 lines
head -n 5 file.txt # first 5 lines
tail -n 20 file.txt # last 20 lines
less file.txt # paginated view (searchable, use q to quit)
I use less
for peeking at files - it handles large files well, is searchable (type /
then your search term), and there's no risk of accidentally editing.
Count things:
wc -l file.txt # count lines
wc -w file.txt # count words
wc -c file.txt # count bytes
Recursive file operations with globs:
Use **
to recurse through all subdirectories:
ls -l **/*.jpg # find all jpg files recursively
grep "TODO" **/*.py # search all Python files
Text Search with Ripgrep
ripgrep (rg
) is my go-to search tool. It's blazing fast, respects .gitignore
, and smarter than traditional grep
.
Install ripgrep:
brew install ripgrep # macOS
apt install ripgrep # Debian/Ubuntu
Most useful patterns:
rg pattern # search recursively
rg -i pattern # case-insensitive
rg -w word # match whole words only
rg -l pattern # list files with matches
rg -c pattern # count matches per file
rg -v pattern file.txt # show lines NOT matching
rg -A 2 -B 2 pattern # show 2 lines before/after match
Search specific file types:
rg pattern --type py # only Python files
rg pattern --type js # only JavaScript files
rg --type-list # see all available types
Exclude files:
rg jquery --glob '!*.min.js' # exclude minified JS
rg pattern --glob '!*.log' # exclude log files
Real-world examples:
rg "function my_func" --type php # find function definition
rg -l TODO # list all files containing TODO
rg "import.*pandas" --type py # find pandas imports
Command History and Shortcuts
Navigate history:
- Up/Down arrows: Previous/next command
- Ctrl-r: Search command history (start typing to search)
history
: Show recent commands!!
: Run previous commandsudo !!
: Run previous command with sudo
Tag commands for future searching:
Add a comment to commands you'll want to find later:
dd if=debian.img of=/dev/disk2 bs=1m # bootable-usb
Now use Ctrl-r
and search for bootable-usb
to find this command instantly.
Vim command-line editing:
Add this to your .vimrc
to save files with sudo when you forgot:
" sudo write
ca w!! w !sudo tee >/dev/null "%"
Combining Commands with Pipes
The pipe |
chains commands together, taking output from one as input to the next:
Basic piping:
ls -l | grep myfile # list files, filter to myfile
ps aux | grep python # find Python processes
cat file.txt | sort | uniq # output, sort, remove duplicates
Using xargs:
xargs
converts piped input into command-line arguments:
ls -1 *.txt | xargs wc -l # count lines in all .txt files
find . -name "*.log" | xargs rm # find and delete log files
Practical combinations:
# Find and delete all .DS_Store files
find . -name ".DS_Store" | xargs rm -rf
# List installed packages, find ones marked for removal, purge them
dpkg --get-selections | grep deinstall | xargs aptitude purge
# Get list of URLs from file and check HTTP status
cat urls.txt | xargs -I {} curl -I {}
Brace Expansion and Patterns
Brace expansion creates multiple strings from patterns:
echo {one,two,three}-alligator
# Output: one-alligator two-alligator three-alligator
# Create multiple directories at once
mkdir -p app/storage/{cache,logs,meta,sessions,views}
# Backup multiple files
cp config.{yml,yml.backup}
# Expands to: cp config.yml config.yml.backup
# Rename with numbered suffix
mv report.txt report-{1,2,3}.txt
Negative patterns - match everything except:
rm !(*.txt) # delete all files except .txt files
ls !(*.log|*.tmp) # list all except .log and .tmp files
Note: Negative patterns require shopt -s extglob
in bash.
Text Editing on the Command-line
Why learn a terminal editor?
When you're working on remote servers or in a pure terminal environment, GUI editors aren't available. Terminal editors open instantly and integrate seamlessly with command-line workflows.
Vim is my choice - powerful, ubiquitous, and lightning fast once you learn it. I've written an extensive Working with Vim guide covering everything from basics to advanced techniques.
Vim survival guide:
vim filename
: Open filei
: Enter insert mode (start typing)Esc
: Exit insert mode:w
: Save:q
: Quit:wq
: Save and quit:q!
: Quit without saving
How I use Vim: As a text editor, not an IDE. The command-line is my IDE. Vim opens so quickly that I constantly open and close it. If I'm editing a file it's open; if not it's closed.
Part 3: Next Level âš¡
Modern Tools
There are many modern alternatives to classic Unix tools that work better and easier to use.
Comparison:
Task | Traditional | Modern Alternative | Key Improvement |
---|---|---|---|
List files | ls |
eza | Better colors, Git integration, tree view |
Find files | find |
fd | Simpler syntax, respects .gitignore |
Search text | grep |
ripgrep | Much faster, smarter defaults |
System monitor | top |
htop | Interactive, better UI, easier navigation |
Fuzzy finder | - | fzf | Interactive filtering for anything |
Change directory | cd |
zoxide | Frecency-based directory jumping |
Prompt | Complex $PS1 | Starship | Beautiful, easy config, fast |
Use aliases to transition smoothly:
alias ls='eza'
alias find='fd'
alias grep='rg'
alias top='htop'
Your muscle memory stays the same, but you get modern tool benefits.
Installing modern tools:
# macOS
brew install eza fd ripgrep htop fzf zoxide starship
# Debian/Ubuntu
apt install fd-find ripgrep
# Many tools also have pre-built binaries on GitHub
Setup Modern Tools
Starship prompt:
Starship makes your prompt beautiful and informative without complex $PS1 configuration.
Install and enable:
brew install starship
echo 'eval "$(starship init zsh)"' >> ~/.zshrc
Customize by editing ~/.config/starship.toml
. See Starship docs for options.
fzf fuzzy finder:
fzf adds interactive filtering to anything. Install:
brew install fzf
$(brew --prefix)/opt/fzf/install # installs key bindings
Key bindings:
- Ctrl-r: Search command history (better than default)
- Ctrl-t: Search files in current directory
- Alt-c: Change to subdirectory
Use fzf in scripts:
# Let user select a file interactively
selected_file=$(find . -type f | fzf)
vim "$selected_file"
Automation: Loops and Scripts
One-line loops for repetitive tasks:
# Backup all .txt files
for file in *.txt; do cp "$file" "$file.bak"; done
# Convert all .png to .jpg
for img in *.png; do convert "$img" "${img%.png}.jpg"; done
# Run command on list of servers
for server in web1 web2 web3; do ssh "$server" 'uptime'; done
Basic bash script structure:
Create script.sh
:
#!/bin/bash
set -e # exit on error
echo "Starting task..."
for ITEM in file1 file2 file3; do
echo "Processing $ITEM"
# your commands here
done
echo "Done!"
Run with: bash script.sh
Script best practices:
- Start with
#!/bin/bash
(shebang) - Use
set -e
to exit on errors - Quote variables:
"$var"
not$var
- Check exit codes:
if [ $? -eq 0 ]; then
Learn more: Bash Guide for Beginners
Project Automation with Justfiles
For project-specific tasks, I recommend just - a modern command runner with better defaults than Make.
Why just over Make:
- Easier syntax (no tab nightmares)
- Better error messages
- Works cross-platform
- More intuitive for scripting tasks
Install:
brew install just
Create justfile
in project root:
# Build the project
build:
npm run build
# Run tests
test:
npm test
# Run linter
lint:
eslint src/
# Development workflow: build, test, lint
dev: build test lint
# Clean build artifacts
clean:
rm -rf dist/ node_modules/
Run commands:
just build
just test
just dev # runs multiple commands
List available commands: just --list
This creates a consistent interface across all your projects. New team members can run just --list
to see available commands.
Scheduling with Cron
Cron runs commands on a schedule - perfect for backups, updates, and routine maintenance.
Edit crontab:
crontab -e
Cron syntax:
* * * * * command
│ │ │ │ │
│ │ │ │ └─ day of week (0-7, 0 and 7 are Sunday)
│ │ │ └─── month (1-12)
│ │ └───── day of month (1-31)
│ └─────── hour (0-23)
└───────── minute (0-59)
Examples:
# Run backup daily at 2am
0 2 * * * /home/user/backup.sh
# Run script every 15 minutes
*/15 * * * * /home/user/check-status.sh
# Run on first of month
0 0 1 * * /home/user/monthly-report.sh
See my tutorial: How to use Unix crontab
Timing and Delays
Time a command:
time sleep 5
time python slow-script.py
Shows real time, user CPU time, and system CPU time.
Delay execution:
sleep 5; echo "5 seconds later"
sleep 60 && notify-send "Timer done" # minute timer with notification
Working with Remote Systems
Simplify SSH with config
Instead of: ssh -l marcus.kazmierczak remote.server.example.com
Create ~/.ssh/config
:
Host remote
HostName remote.server.example.com
User marcus.kazmierczak
Port 22
IdentityFile ~/.ssh/id_rsa
Now just: ssh remote
Use SSH keys instead of passwords:
- Generate key pair:
ssh-keygen -t ed25519
- Copy public key to server:
ssh-copy-id user@server
- Now login without password:
ssh user@server
SSH agent for convenience:
Instead of typing your key passphrase repeatedly, use ssh-agent:
eval $(ssh-agent)
ssh-add ~/.ssh/id_ed25519
Add to your shell config to run automatically.
More details: How to setup SSH keys
Copy files with rsync:
rsync efficiently copies files, only transferring changes. Great for deployments and backups.
# Copy directory to remote server
rsync -avzC local-dir/ user@server:/remote-dir/
# Flags explained:
# -a: archive mode (preserves permissions, timestamps)
# -v: verbose
# -z: compress during transfer
# -C: auto-ignore like CVS (skips .git, .svn, etc.)
Dry run first:
rsync -avzC --dry-run local/ remote:/path/
Shows what would be transferred without actually copying.
More examples: Rsync command examples
HTTP Debugging with curl
curl is essential for testing APIs, debugging sites, and scripting HTTP interactions.
Most useful flags:
curl -I https://example.com # dump response headers only
curl -v https://example.com # verbose (see full request/response)
curl -X POST https://api.example.com # specify HTTP method
curl -d "param=value" url # send POST data
curl -H "Auth: token" url # add custom header
Download files:
curl -O https://example.com/file.zip # save with original filename
curl -o myfile.zip https://example.com/file.zip # save with custom name
Follow redirects:
curl -L https://example.com
Test API:
curl -X GET https://api.example.com/users \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json"
Learn more: Art of HTTP Scripting
Extract Archives
One command for tar archives (no need to remember flags):
tar xvfz tarball.tar.gz # extract .tar.gz
tar xvfj tarball.tar.bz2 # extract .tar.bz2
Mnemonic: "eXtract Verbosely the File, you Zealot/Bzip2-er"
Part 4: Specialized Uses 📚
These are optional topics you can explore based on your specific needs.
Password Management
Generate secure passwords:
brew install pwgen
pwgen 32 # generate 32-character password
pwgen -s 20 # generate 20-char password (more secure)
Advanced: pass password manager
pass is a Unix-philosophy password manager using GPG encryption. Setup requires GPG knowledge but provides secure, command-line accessible password storage.
File Manipulation Utilities
Batch rename files:
Note: rename
utility varies between systems. On macOS, install via Homebrew. Linux has Perl version shown here.
# Change file extensions
rename 's/JPG/jpg/' *.JPG
# Replace spaces with dashes
rename 'y/ /-/' *
# Add prefix to files
rename 's/^/backup-/' *.txt
Random lines from file:
shuf -n 1 quotes.txt # one random line
shuf -n 5 data.txt # five random lines
Document Conversion
Pandoc converts between text formats - Markdown, HTML, PDF, Word, and many more.
Install:
brew install pandoc
Common conversions:
pandoc input.md -o output.html # Markdown to HTML
pandoc input.md -o output.pdf # Markdown to PDF
pandoc input.md -o output.docx # Markdown to Word
pandoc slides.md -t beamer -o slides.pdf # Markdown to Beamer slides
See Pandoc documentation for supported formats and options.
Presentations with Markdown
Use MARP to create slide decks from Markdown. Write slides in plain text, generate HTML/PDF presentations.
Install:
npm install -g @marp-team/marp-cli
Create slides in slides.md
:
# My Presentation
---
## Slide 2
- Bullet points
- More content
---
## Slide 3
Code blocks, images, and tables all work
Generate presentation:
marp slides.md --html # generate HTML
marp slides.md --pdf # generate PDF
Full guide: Markdown Slides with MARP
Email from Command-line
Sending email from command-line requires mail agent configuration (like msmtp or ssmtp). Setup can be tricky - consider if you really need this capability.
Once configured:
# Email command output
command | mail -s "subject" user@example.com
# Email file contents
mail -s "subject" user@example.com < file.txt
# Attach file
echo "See attached" | mail -s "subject" -a file.pdf user@example.com
Task Management Tools
Todo.txt
Simple system using plain text files to track tasks. Available at todotxt.com for multiple platforms.
Taskwarrior
Feature-rich command-line todo manager at taskwarrior.org with many plugins and third-party apps.
My own tool
I created a tasks management tool that displays a TUI kanban board.
Troubleshooting
Command Not Found
Problem: command not found
error
Solutions:
- Check if command is installed:
which command-name
- Install the package:
brew install package-name
- Check if in PATH:
echo $PATH
- Find where installed:
find /usr -name command-name 2>/dev/null
Permission Denied
Problem: Permission denied
error
Solutions:
- Check file permissions:
ls -l filename
- Run with sudo:
sudo command
(be careful!) - Change ownership:
sudo chown $USER filename
- Make executable:
chmod +x script.sh
Reading Documentation
Man pages (manual pages) document most commands:
man ls # read ls documentation
man grep # read grep documentation
Navigate man pages:
- Space: next page
- b: previous page
- /search: search for text
- q: quit
Quick help:
command --help # most commands have help flag
command -h # sometimes -h works too
Checking Exit Codes
Commands return exit codes (0 = success, non-zero = error):
command
echo $? # print exit code of last command
Use in scripts:
if command; then
echo "Success!"
else
echo "Command failed"
fi
Quick Reference
Essential Commands Cheat Sheet
Navigation:
- pwd
- print working directory
- ls -la
- list all files with details
- cd path
- change directory
- cd -
- previous directory
Files:
- cat file
- output file
- less file
- view file (paginated)
- head file
- first 10 lines
- tail file
- last 10 lines
- wc -l file
- count lines
Search:
- rg pattern
- search recursively
- rg -i pattern
- case-insensitive search
- find . -name "*.txt"
- find files by name
System:
- ps aux
- show all processes
- top
or htop
- monitor system
- df -h
- disk space
- du -sh *
- directory sizes
Keyboard Shortcuts:
- Ctrl-c
- cancel current command
- Ctrl-r
- search command history
- Ctrl-l
- clear screen
- Ctrl-a
- beginning of line
- Ctrl-e
- end of line
- Tab
- autocomplete
File Permissions Primer
Permissions shown by ls -l
:
-rwxr-xr-x
│││││││││└─ execute (others)
││││││││└── write (others)
│││││││└─── read (others)
││││││└──── execute (group)
│││││└───── write (group)
││││└────── read (group)
│││└─────── execute (owner)
││└──────── write (owner)
│└───────── read (owner)
└────────── file type (- = file, d = directory)
Change permissions:
chmod +x script.sh # make executable
chmod 755 script.sh # rwxr-xr-x
chmod 644 file.txt # rw-r--r--
Additional Resources
- The Art of Command Line - Comprehensive guide
- Bash Guide for Beginners - Scripting fundamentals
- Command Line Challenge - Interactive exercises
- Explain Shell - Paste commands, get explanations
The command-line gives you direct, unmediated access to your computer. It's faster, more scriptable, and more powerful than any GUI. Invest in learning it well - it's one of the highest-leverage skills you can develop as someone who works with computers.