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.

QuestionTools 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?

# Full status entry for one package dpkg -s git # Just the facts, in a format you choose dpkg-query -W -f='${Package} ${Version} ${db:Status-Abbrev}\n' git

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

# List packages matching a pattern (installed or not) dpkg -l 'python3*' # Everything installed, from apt's point of view apt list --installed 2>/dev/null | grep -i python # What could be upgraded right now? apt list --upgradable

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 asMeaning
iiInstalled, and meant to be.
rcRemoved, but its configuration files were left behind. Not installed, even though it is listed.
Listed does not mean installed
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.
Piping apt's output
When you pipe 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 git apt show git

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:

# Is the command available? (prints its path, or nothing) command -v rg command -v gcc # Show EVERY copy of a command on the PATH, in the order the shell would find them type -a python3 # What version is it? rg --version gcc --version

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.

The package name is not always the command name
Several of the tools you installed in Chapter 2 do not share a name with their command: 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:

# Which package owns this command? dpkg -S "$(command -v lspci)" # What did a package install? dpkg -L ripgrep

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:

sudo apt install apt-file sudo apt-file update # Which package contains a file called pg_config? apt-file search pg_config

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:

# A file that apt installed dpkg -S /usr/bin/git # git: /usr/bin/git # A file that something else put there (a made-up example) dpkg -S /usr/local/bin/mytool # dpkg-query: no path found matching pattern /usr/local/bin/mytool

“No path found matching pattern” is the tell: nothing managed by apt owns that file. Where a file lives is also a clue:

LocationUsually means
/usr/binA Debian package installed it.
/usr/local/binInstalled by hand or by a script, outside apt.
~/.local/binInstalled for your user only: often pip, pipx, or a downloaded tool.
Somewhere under your home directoryA version manager or a per-user tool, such as one you install in Chapters 5 and 6.
Two copies, one winner
If 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 packages that were installed on purpose apt-mark showmanual # How many? apt-mark showmanual | wc -l

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:

# pkg_installed NAME : is this Debian package installed? pkg_installed() { dpkg-query -W -f='${db:Status-Abbrev}' "$1" 2>/dev/null | grep -q '^ii' } # have NAME : does this command exist? have() { command -v "$1" >/dev/null 2>&1 } # Example use if pkg_installed git; then echo "git package: yes"; fi if have rg; then echo "rg command: yes"; fi

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

Exercise 1

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.

๐Ÿ“„ View solution
Exercise 2

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.

๐Ÿ“„ View solution
Exercise 3

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.

๐Ÿ“„ View solution

Chapter 3 Quick Reference

  • Ask three questions before installing: is it installed, which version, where did it come from
  • dpkg -s NAME and dpkg-query -W -f='${Package} ${Version} ${db:Status-Abbrev}\n' NAME check one package
  • In dpkg -l, ii means installed; rc means removed with config files left behind
  • apt policy NAME shows Installed vs Candidate versions and their repositories
  • command -v NAME tests for a command; type -a NAME shows every copy on the PATH, first one wins
  • Package names and command names differ: ripgrep → rg, pciutils → lspci
  • dpkg -S FILE finds the owning package; “no path found matching pattern” means apt does not manage it
  • dpkg -L PACKAGE lists a package's files; apt-file search finds packages for files you do not have yet
  • apt-mark showmanual is the list of packages you chose to install
  • Piping apt list triggers a warning; use 2>/dev/null, or prefer dpkg-query in scripts