I type git status probably two hundred times a day, and at some point in my early sysadmin years I got tired of it and just made it gs. That one habit — building a personal shorthand for the commands I run constantly — is the entire reason the alias command exists, and it’s still one of the first things I set up on any new machine I touch. In this guide I want to walk through everything I know about alias: the syntax, the quirks that trip people up (including one that got me just last week while testing scripts), how it actually works under the hood, and how to use it properly in real sysadmin work instead of just typing ll in your personal terminal.
What alias Actually Is
alias is a shell builtin, not a standalone binary. You won’t find it in /usr/bin or /bin — it lives inside the shell itself (bash, zsh, dash, etc.), which is why its behavior differs slightly between shells and why you can’t call it from a compiled program or from system() in C without going through a shell.
At its core, an alias is a simple text-substitution rule. When the shell reads a command line and the first word matches a defined alias, it swaps that word for the alias’s replacement text before doing anything else — before parsing arguments, before checking $PATH, before anything. That’s the whole trick, and it explains almost every quirk you’ll run into.
I confirmed this directly:
$ type alias
alias is a shell builtin
Basic Syntax
alias [-p] [name[=value] ...]
- Called with no arguments,
aliaslists every alias currently defined. - Called with
-p, it lists them in a form that’s guaranteed to be re-usable as input (useful in scripts that dump your environment). - Called with
name=value, it defines (or redefines) an alias. - Called with just
name(no=value), it prints that one alias’s definition.
Here’s a plain definition and lookup I ran myself:
$ alias ll='ls -la'
$ alias
ll='ls -la'
$ alias ll
ll='ls -la'
Notice the quoting. If your replacement text contains spaces (and it almost always does), you need to quote it. I always use single quotes unless I specifically want variable expansion to happen at definition time rather than at call time.
Removing an Alias
The counterpart command is unalias:
unalias name # remove one alias
unalias -a # remove all aliases in the current shell
I tested this directly and it behaves exactly as documented:
$ alias ll='ls -la'
$ unalias ll
$ alias ll
bash: alias: ll: not found
That “not found” message is bash telling you the name isn’t a defined alias — it’s not an error in your command, just confirmation the removal worked.
The Gotcha: Aliases Don’t Expand in Non-Interactive Shells by Default
This one caught me off guard while I was testing examples for this very article. I wrote a small script, ran it, and my alias silently failed:
$ bash -c 'alias greet="echo hello"; greet world'
bash: line 1: greet: command not found
The reason is that bash only expands aliases in interactive shells by default. A script run with bash -c or bash script.sh is non-interactive, so alias expansion is off unless you explicitly turn it on with shopt:
$ bash -c 'shopt -s expand_aliases; alias greet="echo hello"; greet world'
hello world
This is exactly why the standard advice is don’t rely on aliases inside shell scripts — use functions instead. Aliases are a login-shell/interactive convenience, not a scripting tool. I’ll come back to this point in the best-practices section because it matters for anyone writing production automation.
alias Does Not Take Positional Parameters
This is the single most common misunderstanding I see from people coming from other languages. An alias is text substitution, not a function call — it has no concept of $1, $2, or argument placement inside the replacement text. Whatever you type after the alias name just gets appended to the end of the expanded text.
$ alias greet='echo hello'
$ greet world
hello world
That works because world gets tacked onto the end of echo hello, producing echo hello world. But if you wanted to insert an argument in the middle of a command, aliases can’t do it:
$ alias cpv='cp -v'
$ cpv file1.txt /tmp/
# expands to: cp -v file1.txt /tmp/ -> works fine, args go at the end
But something like “always compress before copying” with an argument placed mid-command is impossible with an alias. For that, you need a shell function:
backup_and_copy() {
tar czf "$1.tar.gz" "$1" && cp "$1.tar.gz" "$2"
}
I keep this rule in my head permanently: if the command needs logic, conditionals, or an argument that isn’t simply appended at the end, it’s a function, not an alias.
Nested Aliases
Aliases can reference other aliases, and bash resolves them at expansion time:
$ shopt -s expand_aliases
$ alias ll='ls -la'
$ alias la='ll -A'
$ type la
la is aliased to `ll -A'
This works, but I try not to build deep chains of aliases referencing aliases — it gets confusing to debug two weeks later when you forget which alias points to which.
Aliasing sudo-Prefixed Commands
A common pattern I use on servers I manage directly:
alias update='sudo apt update && sudo apt upgrade -y'
alias ports='sudo ss -tulnp'
One subtlety worth knowing: if you want a command following a sudo alias to also have alias expansion applied (say, you have another alias for the command sudo is running), you need a trailing space in the sudo alias:
alias sudo='sudo '
That trailing space is not a typo — it tells bash “keep checking for more alias expansion after this word,” which is needed because by default alias expansion only looks at the first word of a command.
Persisting Aliases Across Sessions
Everything above only lives in your current shell session. Close the terminal and it’s gone. To make aliases permanent, put them in your shell’s startup file:
- bash (interactive login shells):
~/.bash_profileor~/.profile - bash (interactive non-login shells, most terminal emulators):
~/.bashrc - zsh:
~/.zshrc
I keep mine in a separate file and source it, which makes it easier to manage under version control:
# in ~/.bashrc
if [ -f ~/.bash_aliases ]; then
. ~/.bash_aliases
fi
Then all my actual alias definitions live in ~/.bash_aliases, which I sync across machines with a dotfiles repo.
System-Wide Aliases
For aliases you want available to every user on a machine, drop a file into /etc/profile.d/ (for login shells) or edit /etc/bash.bashrc (for interactive non-login shells). I use this on shared servers to enforce safety habits team-wide:
# /etc/profile.d/safety-aliases.sh
alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
Practical Aliases I Actually Use
These are pulled straight from my own .bash_aliases:
# Listing
alias ll='ls -la'
alias la='ls -A'
alias l='ls -CF'
# Safety nets
alias rm='rm -i'
alias mv='mv -i'
alias cp='cp -i'
# Navigation
alias ..='cd ..'
alias ...='cd ../..'
# Git shortcuts
alias gs='git status'
alias gd='git diff'
alias gl='git log --oneline --graph --decorate'
# System admin
alias meminfo='free -h'
alias diskinfo='df -h'
alias ports='sudo ss -tulnp'
# Networking
alias myip='curl -s ifconfig.me'
# Package management (Debian/Ubuntu)
alias update='sudo apt update && sudo apt upgrade -y'
Overriding a Real Command with an Alias — And How to Bypass It
Once you alias rm='rm -i', typing rm file.txt really runs rm -i file.txt, prompting before every delete. That’s great for safety, but sometimes you genuinely want the raw command. Three ways to bypass an active alias:
\rm file.txt # backslash disables alias expansion for that word
command rm file.txt # 'command' also skips aliases and functions
env rm file.txt # runs the actual binary via env, also bypasses aliases
I use \rm constantly in scripts that need the unmodified behavior of the real binary, and it’s a habit worth building.
How alias Interacts with $PATH and Functions
There’s a resolution order the shell follows when it sees a command word:
- Aliases (if expansion is enabled)
- Shell keywords (
if,for, etc.) - Shell functions
- Shell builtins
- Executables found by searching
$PATH
Because aliases are resolved first, an alias named the same as a real command (like rm, ls, grep) will always win unless you explicitly bypass it. This is powerful but also a trap: if you forget you’ve aliased grep='grep --color=auto' and then wonder why piping grep output into another program behaves oddly (color codes leaking into a file), the alias is almost always the cause.
Troubleshooting Aliases
A few things I check when an alias “isn’t working”:
- Am I in a script? Alias expansion is off by default in non-interactive shells (see above). Use
shopt -s expand_aliasesor, better, rewrite as a function. - Did I actually reload my shell config? Editing
~/.bashrcdoesn’t affect already-open sessions. Runsource ~/.bashrcor open a new terminal. - Is there a typo in the source file? A broken alias definition can silently abort the rest of the file from loading in some shells. I check with
bash -n ~/.bashrcto syntax-check without executing. - Is another alias or function shadowing it?
type nametells you exactly what the shell will run for that word — alias, function, builtin, or file path.
$ type ls
ls is aliased to `ls --color=auto'
alias vs. Shell Functions vs. Scripts
I get asked which one to use, so here’s how I decide:
| Feature | alias | shell function | standalone script |
|---|---|---|---|
| Argument placement | End only | Anywhere ($1, $2, …) | Anywhere |
| Conditionals/loops | No | Yes | Yes |
| Works in scripts by default | No | Yes | Yes |
| Persists across shell restart | Only if in rc file | Only if in rc file | Always (it’s a file) |
| Callable from other programs/cron | No | No (unless exported) | Yes |
| Best for | Quick shorthand, flag defaults | Small reusable logic | Anything reused across systems or users |
My rule of thumb: aliases for shorthand you type interactively, functions for logic you want in your interactive shell, and scripts for anything that needs to run outside your terminal — cron jobs, other users, CI pipelines.
Security Considerations
Aliases are a legitimate (if uncommon) vector for confusion or mischief on shared systems. Because alias resolution happens before $PATH lookup, a malicious or careless alias can silently redirect a trusted command name to something else entirely. A few practices I follow:
- Never source an untrusted
.bashrcor dotfiles repo without reading it first. - On systems where multiple people have shell access, be cautious about what you put in shared files under
/etc/profile.d/. - If you’re auditing a compromised or suspicious system, always check
aliasoutput (andtypefor common commands likels,sudo,ssh) as part of your review — it’s a cheap check that occasionally reveals something odd. - Aliases are never a security boundary. They’re a convenience layer for interactive use, easily bypassed with
\commandorcommand.
Compatibility Across Shells
alias syntax as described here (alias name=value) is the bash/zsh/ksh style and is what you’ll use in virtually every interactive Linux terminal. Two things worth knowing:
- dash (Ubuntu/Debian’s default
/bin/sh) supportsaliasas a builtin too, but scripts run with#!/bin/shon Debian-based systems point at dash, where alias expansion in non-interactive mode is also disabled — reinforcing why aliases don’t belong in portable scripts. - csh/tcsh use a different syntax entirely (
alias name 'value'with no=), which is one more reason POSIX-style scripting avoids aliases altogether.
Across Ubuntu, Debian, Fedora, RHEL, Arch, and basically every mainstream distro, bash’s alias builtin behaves identically since it’s part of bash itself, not the distro’s packaging.
Summary
alias is a shell builtin that performs simple text substitution on the first word of a command line before any other parsing happens. It’s fantastic for interactive shorthand — shortening long commands, adding safety flags, or setting personal defaults — but it has real limits: no argument placement beyond the end of the string, no conditionals, and disabled expansion in non-interactive shells by default. For anything with logic or that needs to run in scripts, cron, or by other users, reach for a shell function or a proper script instead. Keep your aliases in a dedicated dotfile, version-control them, and remember \command or command when you need to bypass one temporarily.
References
- GNU Bash Reference Manual, “Aliases” section — https://www.gnu.org/software/bash/manual/bash.html#Aliases
man bash(Builtin Commands section coversalias/unalias)- Debian Policy Manual on shell scripts and
/bin/sh— https://www.debian.org/doc/debian-policy/