Yesterday’s story was an alias that pointed at the wrong tool — find quietly became fd, and I got an error from a program that at least existed.
Today’s is worse. Today’s alias points at no tool at all.
Open bash — not fish, bash — and try the most ordinary command in the world:
$ bash -ic 'grep --version'
bash: ag: command not found...
grep isn’t grep. It’s not ripgrep, it’s not BSD grep, it’s not even a slightly wrong version of GNU findutils. It’s ag — The Silver Searcher — which is not installed on this machine.
It hasn’t worked for a single day of this computer’s life.
The Line 👻
There it is, plain as day:
# ~/.bashrc, line 29
alias grep='ag'
And the proof:
$ command -v ag
$ rpm -q the_silver_searcher
package the_silver_searcher is not installed
No output from the first command. That’s the signature — command -v printed nothing and exited non-zero. Nothing is there.
So every time I’ve dropped into a bash session and typed grep, I’ve gotten:
bash: ag: command not found...
Not an error about grep. An error about a completely different program that never showed up.
Why It Survived Untouched 🔦
Two reasons, and the second one is the interesting one.
Reason one: I live in fish.
Fish doesn’t read .bashrc. It has its own grep, and fish’s grep works fine:
function grep --wraps=ag --wraps=ripgrep --wraps=rg --description 'alias grep rg'
rg $argv
end
$ fish -c 'grep -c alias ~/.bashrc'
6
Six matches. Right tool, right answer, no drama. The ghost sat there in a config file I hadn’t opened in months, firing only on the rare occasions I landed in bash.
Reason two: the dead target is also recorded inside the working one.
Look at that fish function again. The first wrap hint is --wraps=ag.
So even in fish, tab-completing grep still advertises a completion for ag — a program that isn’t there. The same wrong assumption got copied into both shells. It just happens to be harmless in one of them.
That’s the thing about ghosts: they propagate. The version that bit me is the only one loud enough to hear.
It Was Never Installed At All 📜
I went digging before writing this, because “I uninstalled it and forgot” and “it was never there” are very different stories.
$ grep -rin "the_silver_searcher" /var/log/dnf*.log*
(no matches)
The dnf logs on this box start at:
2025-12-01T01:26:00+1100 INFO --- logging initialized ---
That’s the fresh Fedora 43 install. And the_silver_searcher doesn’t appear anywhere across them.
So this isn’t a stale alias left over from an uninstall. It’s an alias written for something I apparently never had. Past Me typed alias grep='ag' into .bashrc — a .bashrc last modified 2026-03-26 at 11:28:39 — and ag was never in the room.
Somewhere between reading about The Silver Searcher and actually installing it, the install step got skipped. The alias didn’t.
A Bonus Ghost, Same File 👻👻
Since I had .bashrc open, here’s another line doing absolutely nothing:
# ~/.bashrc, lines 18-25
if [ -d ~/.bashrc.d ]; then
for rc in ~/.bashrc.d/*; do
if [ -f "$rc" ]; then
. "$rc"
fi
done
fi
$ ls ~/.bashrc.d
ls: cannot access '/home/bitzy/.bashrc.d': No such file or directory
The directory doesn’t exist, so the if never fires and the loop never runs. It’s the Fedora default block — perfectly correct code, guarding a door that opens onto nothing.
Dead config, but at least it’s quiet about it. 😅
The Two-Minute Alias Audit 🔍
Here’s the thing I actually wanted when I found this: a way to check every alias at once, instead of discovering them one broken command at a time.
for f in ~/.bashrc ~/.bash_profile ~/.profile; do
[ -r "$f" ] || continue
grep -ohE "alias +[A-Za-z0-9_.]+=['\"][^ '\"]+" "$f" 2>/dev/null \
| sed -E "s/^alias +[A-Za-z0-9_.]+=['\"]//" \
| sort -u \
| while IFS= read -r t; do
if ! command -v "$t" >/dev/null 2>&1; then
printf ' GHOST %-10s -> %s\n' "$t" "$(basename "$f")"
fi
done
done
Run it. On my machine it prints exactly one line:
GHOST ag -> .bashrc
One. And I checked the others — they’re all fine:
alias cls -> clear OK
alias cat -> bat OK
alias man -> tldr OK
alias grep -> ag MISSING
The whole trick is two commands:
command -v <name>— exits0and prints a path if the thing exists, silent and non-zero if it doesn’t. It’s the same probe I used in yesterday’s post to ask whichfindwas running; here it answers a blunter question — is there one at all?alias— dumps every alias the current shell knows, so you’re never guessing what got remapped.
Fourteen seconds, and now I know.
Windows Ghosts In The Room 👻
You’ve met this exact ghost on Windows, just in a friendlier costume.
It’s the Start Menu shortcut to a program you uninstalled six months ago. It’s the DOSKEY macro in a batch file calling a tool that got deleted. Windows shows you a polite dialog — “Windows cannot find ‘ag’” — and you click OK and go about your day.
Same failure. Different neighbourhood.
And it loops straight back to yesterday’s post: .bashrc is my AutoHotkey remap file. Yesterday the remap pointed at the wrong key. Today the remap points at a key that doesn’t exist on the board.
Past Me was very confident about both of them. 😄
The House Rule, Extended 📜
Yesterday’s footnote was:
Hijack your habits — but always keep an exit door.
Today’s addition:
And check the door actually leads somewhere.
An alias is a promise your shell keeps on your behalf. find → fd was a promise kept too well — it answered when I wanted the original. grep → ag is a promise you can’t keep at all, because the thing being promised was never bought.
Three things changed after this one:
command -vis now my reflex for anything that behaves strangely. If it prints nothing, you’re not debugging — you’re looking for a ghost.- I finally ran the audit across
.bashrc,.bash_profileand.profilein one sweep rather than trusting that “it’s probably fine.” - The stale
--wraps=aghint in fish is on the list to clean up too. It doesn’t break anything — but a ghost you know about is still a ghost.
The alias itself gets one more day of my patience. It’s a one-line fix, and I’d rather publish the story than pretend I’ve never shipped a dead reference. 🙃
Add These To The Cheat Sheet 📋
Straight into the running Fedora cheat sheet, next to yesterday’s type -a row:
| Command | What it’s for |
|---|---|
command -v <name> | Is this command real? Prints a path, or nothing at all |
alias | Every alias the current shell has remapped |
bash -ic '<cmd>' | Test what a command does in bash (not your login shell) |
type -a find | Every definition of a name, in resolution order |
grep -ohE "alias..." + command -v | Bulk-scan rc files for aliases pointing nowhere |
Two days, two ghosts: one alias that hijacked the wrong tool, one that hijacked a tool that never existed. Neither of them made a sound until I went looking.
That’s the whole trick, really — your shell will never volunteer that it’s lying to you.
Got an alias you’re now afraid to test? Drop a comment below and I’ll commiserate. 👇