From Alias to Function to Script

In short. Type the same command twice in a terminal and you make an alias. Use aliases for a while and two things turn up that they cannot do. You cannot put an if statement in one, and inside a script they do not work at all. There are two other ways to give a command a name — functions and script files. Here is the test for choosing among the three, starting from what an alias actually is.

Dotori Bluetooth Keyboard - Your phone as a keyboard for PC and TV

A free app I built myself. Give it a try.

An alias replaces the first word of a command

An alias replaces the first word of a command with another string you decided in advance. The shell swaps the text as it reads the command line. The rest of the command runs as it always would. Creating one is a single line and removing it is a single unalias. It is simple, and it does three jobs.

First, it cuts keystrokes. A command you type twenty times a day becomes two characters.

alias gs='git status'
alias la='ls -a'

Second, you no longer have to remember options. This is more useful than the keystrokes. -w ignores lines that changed only in whitespace. Putting it inside a name is more reliable than recalling it each time.

alias gdu='git --no-pager diff -w'
alias rgrep='grep -rni'

Third, it changes a tool's default behaviour, by redefining it under the same name.

alias lsd='lsd --group-dirs=first'

It calls itself, so it looks like it should recurse forever, and it does not. The text swap happens once, on the first word only. So lsd becomes a command that always lists folders first.

$ lsd
zdir
a.txt
b.txt

When you need the original behaviour, put a backslash in front. \lsd is the unmodified command. To see what is currently bound, use type lsd; for the full list, run alias with no arguments.

If you need an if statement, or a change of directory, use a function

Swapping text is all it does, so there are things it cannot do. You cannot put in an if statement, and you cannot insert an argument in the middle of the command. That is when it becomes a function.

My gd is a function. Some of my machines have delta, which prettifies diffs, and some do not.

gd() {
    if command -v delta >/dev/null 2>&1; then
        git -c core.pager=delta diff -w "$@"
    else
        git --no-pager diff -w "$@"
    fi
}

The second case has no way around it at all. Running a script starts a separate process. That process cannot change the current directory of the shell that called it. A cd inside a script disappears with the process.

before: /tmp/cdtest
/tmp/cdtest/sub     <- pwd printed after the script's cd
after:  /tmp/cdtest <- the parent shell is unchanged

Say you browse folders in the file manager yazi and want your shell left where you stopped. That has to be a function. You take the directory yazi ended in through a temporary file and cd there in the shell itself.

yz() {
    local tmp="$(mktemp -t yazi-cwd.XXXXXX)"
    yazi "$@" --cwd-file="$tmp"
    local cwd
    if cwd="$(cat -- "$tmp")" && [ -n "$cwd" ] && [ "$cwd" != "$PWD" ]; then
        cd -- "$cwd"
    fi
    rm -f -- "$tmp"
}

Put the same code in a script file and the cd happens inside that process only. It has no effect at all.

If a program will call it rather than a person, make it a file

The last one is a slightly different story. An alias only works in a shell where a person types commands directly. Such a shell is called an interactive shell. I confirmed it with a two-line script.

$ cat t.sh
alias hello='echo hi'
hello

$ bash t.sh
t.sh: line 2: hello: command not found

The bash manual says so — "Aliases are not expanded when the shell is not interactive". The only exception is the expand_aliases option. Put shopt -s expand_aliases on the first line of the script and the same code works.

So commands inside a Makefile, CI jobs and commands an AI coding agent calls cannot be aliases. They are not typed by a person; they are called by a program. In those cases you make a file. Three lines of work.

mkdir -p ~/bin                      # a folder to collect scripts in
chmod +x ~/bin/git-log-file.sh      # execute permission
echo 'export PATH="$PATH:$HOME/bin"' >> ~/.bashrc

From then on you call it by name, git-log-file.sh, from any folder. It is the same command whether you type it, a Makefile runs it, or an agent calls it.

The merge script from the post on merging worktrees is a real example of something kept as a file. I type it by hand and an agent also calls it, so it had to be a file.

You do not have to pick only one of the three. Make the file, then point an alias at it and the typing gets shorter again.

alias glf='~/bin/git-log-file.sh'

Summary

MethodUse it forWhere it lives
aliasJobs where replacing the first word is enoughA shell you type in
functionJobs that need an if statement or a change of directoryA shell you type in
script fileJobs called by scripts, Makefiles, CI or agentsA file on your PATH

An alias replaces the first word and nothing more. That is why it is easy to make, and why its limits are clear. When you need an if statement or a change of directory, move to a function. When a program rather than a person will call it, make it a file.

What you can do right now is type alias with no arguments and read the list. Some of them you wished had an if statement, or wanted to call from a script. Move those to a function or a file first.