A snippet that works grows a life of its own: first it is an alias, then it needs an argument, then a loop, then it belongs in three shells. Knowing which container — alias, function, or script — fits each stage saves the rewrite you were about to regret.

The three containers #

The same job at three sizes
  # alias: text substitution, no logic
alias gs='git status -sb'

  # function: logic, arguments, lives in the shell
gco() { git checkout "$1" && git pull; }

  # script: a program anyone can run
#!/usr/bin/env bash
  # ~/bin/deploy <env>
set -euo pipefail
env="${1:?usage: deploy <env>}"
rsync -a dist/ "server:/srv/$env/"

An alias is string replacement the shell performs before anything else — perfect for retyping, useless for logic. A function is real code inside your interactive shell session. A script is a file: versioned, shareable, callable from cron and CI, working in every shell.

The decision table #

NeedUse
Shorter typing, no logicAlias
Logic, needs your aliases/env, interactiveFunction
Logic, runs anywhere, unattendedScript
Shared with a teamScript (in a repo)
Needs to survive a different shellScript (POSIX or explicit shebang)
Needs arguments and flagsFunction or script — never an alias

Aliases cannot take positional arguments — alias gco='git checkout' works only because the rest of the line trails on; the day you need $1, it is a function.

Where each lives #

Aliases and functions go in shell startup files — ~/.bashrc for interactive bash, or a ~/.*-aliases file you source from it. Scripts go in a PATH directory, ideally ~/.local/bin or a repo's bin/:

Sourcing the personal toolkit
  # ~/.bashrc
if [ -f "$HOME/.bash_aliases" ]; then
  . "$HOME/.bash_aliases"
fi
export PATH="$HOME/.local/bin:$PATH"

That split matters for one subtle reason: functions are not inherited by scripts or non-interactive shells (with few exceptions like exported functions), so anything a cron job, CI pipeline, or teammate touches must be a script.

The migration path #

The honest lifecycle most utilities follow:

  1. Typed twice — leave it, or alias it.
  2. Typed with variations — function, with a usage guard.
  3. Needed outside the shell — script, with a shebang, set -euo pipefail, and shellcheck.
  4. Needed by others — repo, README, tests.

Each step is small; skipping two at once is where unmaintainable one-liner-scripts come from.

Functions make the middle stage pleasant — see bash functions; scripts start with the first-script note. The scripting series continues.