What's Installed
Debian Development Machine Setup
Chapter 3 ยท Knowing What's Installed
Most of the trouble on a development machine comes from a simple gap: you think a tool is installed, or think it is a particular version, and you are wrong. You install it a second time by a different route, and now two copies fight over which one runs. This chapter builds a habit that prevents that. Before you install anything in the rest of this course, you ask three questions.
| Question | Tools that answer it |
|---|---|
| 1. Is it installed? | dpkg -s, dpkg-query -W, command -v |
| 2. Which version? | apt policy, dpkg-query -W, tool --version |
| 3. Where did it come from? | dpkg -S, type -a, apt-mark showmanual |
Asking the Package System
Debian has two layers: dpkg is the low-level tool that knows what is installed, and
apt sits on top of it, knows about the repositories, and works out dependencies. Linux Package
Managers 2 (APT in Depth) and Linux Package Managers 4 (dpkg Deep Dive) cover both in detail. Here we only
need the commands that answer questions.
Is this one package installed?
dpkg -s git prints a block that includes the line Status: install ok installed
when the package is present. The dpkg-query -W -f=... form lets you print exactly the fields
you want, so the answer is one short line: the package name, its version string, and ii. If the package is not installed, dpkg -s says so and returns a non-zero
exit status, which matters when you put the check into a script.
Searching the list of installed packages
Look at the first three characters of each line of dpkg -l. They are status letters: the first
is what you asked for, the second is the package's actual state, and the third flags errors. The two you
will meet most are:
| Shown as | Meaning |
|---|---|
ii | Installed, and meant to be. |
rc | Removed, but its configuration files were left behind. Not installed, even though it is listed. |
dpkg -l 'python3*' lists packages dpkg has any record of, including ones with status
rc, and the pattern can also match the names of packages that are not on your machine. Always
read the status letters, or use dpkg -s, which answers the exact question you asked.
apt list into another command, apt prints a warning that it does not have a stable
command-line interface for scripts. That is why the example above adds 2>/dev/null, which hides
the warning. Apt's own advice is that scripts should use dpkg-query or apt-get
instead; both are stable.
Which version is installed, and which is available?
apt policy shows two lines that answer the version question directly: Installed
(what you have, or (none)) and Candidate (what apt install would
give you). If they differ, an upgrade is available. Below them it lists which repository each version comes
from. apt show adds the description, dependencies and size.
Asking the Shell
The package system answers questions about packages. But what you actually type is a command, and the two names are not always the same. The shell can tell you whether a command exists and where it lives:
command -v is the reliable check: it prints the path when the command exists and prints nothing
(with a non-zero exit status) when it does not. type -a is the one that catches the classic
two-copies problem, because it lists every match on your PATH, and the first one listed is the
one that runs.
ripgrep gives you rg, pciutils gives you lspci,
and build-essential gives you gcc and make but no command called
build-essential. So command -v ripgrep finds nothing even though the package is
installed. Ask the package system about package names, and the shell about command names.
From a file to its package, and back
Two tools connect the two views. Given a file, dpkg -S tells you which installed package owns
it. Given a package, dpkg -L lists the files it installed:
dpkg -S only knows about installed packages. To find which package would provide a file
you do not have yet, use apt-file. It downloads an index of every file in every package, so it
needs one extra install and one extra update before it works:
This is the tool to reach for when a build fails with “missing header file” or “command not found”. It tells you which package to install to fix it.
Not Everything Comes from apt
Here is the part that catches people. On a development machine, a lot of software never touches apt. Python
packages come from pip, JavaScript tools from npm, and plenty of programs are just downloaded and dropped
into a directory. The package system knows nothing about those, so dpkg -S is a useful test:
“No path found matching pattern” is the tell: nothing managed by apt owns that file. Where a file lives is also a clue:
| Location | Usually means |
|---|---|
/usr/bin | A Debian package installed it. |
/usr/local/bin | Installed by hand or by a script, outside apt. |
~/.local/bin | Installed for your user only: often pip, pipx, or a downloaded tool. |
| Somewhere under your home directory | A version manager or a per-user tool, such as one you install in Chapters 5 and 6. |
type -a python3 lists two paths, the shell runs the first. That may not be the one you meant
to update. Chapter 5 (Python) and Chapter 6 (Node.js) return to this, because version managers work by
putting their own copy first on the PATH on purpose.
Which Packages Did You Ask For?
When you run apt install git, apt also installs whatever git needs. Debian remembers the
difference. Packages you asked for are marked manual, and packages pulled in as dependencies
are marked automatic. An automatic package that nothing needs any more can be removed with
apt autoremove.
The manual list is the honest record of what you chose to put on the machine, which makes it the right starting point for rebuilding it. Chapter 10 exports it for exactly that purpose.
Putting It Together: Two Small Helper Functions
Typing three commands each time is slow. Two shell functions in ~/.bashrc turn the checks into
one word each. Add them below the prompt lines from Chapter 2:
Note that the two functions ask different questions, which is the point of the earlier finding-box.
pkg_installed ripgrep and have rg are both true on a machine with the ripgrep
package installed, while have ripgrep is false.
Hands-On Exercises
For each of git, rg, lspci, gcc and python3, work out: whether the command exists, its path, its version, and which Debian package owns it. Present the results as a table, and explain any case where the command name and package name differ.
Write a script, check-tools.sh, that takes a list of commands and prints, for each one, whether it is present, its path, and the first line of its version output. It must exit with a non-zero status if any are missing. Run it on the Chapter 2 starter tools.
Create a tiny script named hello in ~/.local/bin and show how apt's tools treat it. Then run apt-mark showmanual, pick three packages you do not recognise, and use apt show to find out what they are and why they might be on the machine.
Chapter 3 Quick Reference
- Ask three questions before installing: is it installed, which version, where did it come from
dpkg -s NAMEanddpkg-query -W -f='${Package} ${Version} ${db:Status-Abbrev}\n' NAMEcheck one package- In
dpkg -l,iimeans installed;rcmeans removed with config files left behind apt policy NAMEshows Installed vs Candidate versions and their repositoriescommand -v NAMEtests for a command;type -a NAMEshows every copy on the PATH, first one wins- Package names and command names differ:
ripgrep→rg,pciutils→lspci dpkg -S FILEfinds the owning package; “no path found matching pattern” means apt does not manage itdpkg -L PACKAGElists a package's files;apt-file searchfinds packages for files you do not have yetapt-mark showmanualis the list of packages you chose to install- Piping
apt listtriggers a warning; use2>/dev/null, or preferdpkg-queryin scripts