Lister les paquets installés dans un fichier : dpkg-query -W -f

dpkg-query -W -f='${binary:Package}\t${Version}\n' > inventaire.txt écrit une ligne par paquet de la base dpkg, avec le nom du binaire, une tabulation, la version installée. Les deux options sont -W (--show), qui parcourt /var/lib/dpkg/status, et -f (--showformat, disponible depuis dpkg 1.13.1), qui remplace le format de sortie. Ce format par défaut vaut exactement ${binary:Package}\t${Version}\n d’après la page de manuel de dpkg-query 1.21.22 sous Debian 12 (bookworm), datée du 11 mai 2023 : la commande explicite reproduit donc la sortie standard, mais dans un fichier.
Sommaire
- Un inventaire en deux colonnes se construit en quatre commandes
- dpkg-query -W conserve les paquets supprimés non purgés, marqués rc
- Deux machines se comparent avec diff, puis comm pour les noms seuls
- Un nom de champ inconnu ne provoque pas d’erreur, seulement des lignes vides
- Quatre erreurs de format s’affichent avec le préfixe du programme
- ${Package} ne porte pas l’architecture, ${binary:Package} si
- Cinq commandes, cinq sorties différentes pour le même système
- L’inventaire se relit hors ligne, sans droit particulier
Le fichier obtenu se copie sur une autre machine et se compare ligne à ligne. Trois choses faussent silencieusement le résultat : les paquets supprimés mais non purgés restent dans la liste, un nom de champ mal orthographié ne déclenche aucune erreur, et ${Package} ne dit rien de l’architecture.
Un inventaire en deux colonnes se construit en quatre commandes
-
Écrire la liste complète dans un fichier nommé d’après la machine.
LC_ALL=C dpkg-query -W -f='${binary:Package}\t${Version}\n' > inventaire-$(hostname).txtÀ l’écran : rien, la sortie standard part dans le fichier et seule une erreur de base s’afficherait. Le fichier doit exister et n’être pas vide.
-
Compter les lignes obtenues.
wc -l < inventaire-$(hostname).txtÀ l’écran : un nombre entier. C’est le total des paquets présents dans
/var/lib/dpkg/status, à l’exclusion des paquets purgés, qui ne figurent plus dans le fichier depuis leur purge. -
Contrôler deux lignes dont les versions sont documentées.
grep -E '^(apt|dpkg)[[:space:]]' inventaire-$(hostname).txtÀ l’écran, sur Debian 12 :
apt 2.6.1 dpkg 1.21.22Les deux colonnes sont séparées par une tabulation. L’alignement visuel est irrégulier, ce qui n’a aucune importance pour
cut,awkoudiff, qui découpent sur ce caractère. -
Composer un autre format pour d’autres usages, sans changer de commande. La largeur se place après un point-virgule et un nombre négatif aligne à gauche.
dpkg-query -W -f='${binary:Package;-30}${Version}\n' dpkgÀ l’écran : le nom complété par des espaces jusqu’à 30 caractères, puis la version collée derrière, soit
dpkg, 16 espaces et1.21.22.
dpkg-query -W conserve les paquets supprimés non purgés, marqués rc
Sans motif, la commande liste tous les paquets de /var/lib/dpkg/status sauf ceux marqués not-installed, c’est-à-dire les paquets purgés. Un paquet désinstallé sans purge garde donc sa fiche et son ancienne version, et il entre dans l’inventaire comme s’il était présent. Ces lignes portent les trois caractères rc : r pour l’action voulue Remove, c pour l’état Config-files, la troisième place restant vide quand aucun drapeau d’erreur n’est levé.
Le champ virtuel ${db:Status-Abbrev} rend ces trois caractères et sert au contrôle.
LC_ALL=C dpkg-query -W -f='${db:Status-Abbrev} ${binary:Package}\n' | grep -v '^ii '
À l’écran : uniquement les lignes qui ne commencent pas par ii, de la forme rc nom-de-paquet. Une sortie vide signifie que tous les paquets listés sont dans l’état installé. Pour ne garder que ceux-là, le filtre s’appuie sur la largeur fixe du champ, trois caractères espaces compris, ce qui explique la comparaison avec ii suivi d’un espace dans la commande suivante.
LC_ALL=C dpkg-query -W -f='${db:Status-Abbrev}\t${binary:Package}\t${Version}\n' | awk -F'\t' '$1 == "ii " { print $2 "\t" $3 }' > inventaire-$(hostname).txt
À l’écran : rien. Le fichier ne contient plus que les paquets réellement installés, et les états intermédiaires comme iU ou iF, qui signalent une installation inachevée, en sortent aussi.
Deux machines se comparent avec diff, puis comm pour les noms seuls
-
Produire l’inventaire des deux côtés avec la même commande, une fois les deux systèmes à jour. La mise à niveau elle-même est décrite dans installer et mettre à jour Debian.
LC_ALL=C dpkg-query -W -f='${binary:Package}\t${Version}\n' | LC_ALL=C sort > inventaire-$(hostname).txtÀ l’écran : rien. Le tri par
sortest ici redondant avec celui de dpkg-query, qui compare les noms avecstrcmp, octet par octet ;LC_ALL=Cgarantit que les deux commandes classent de la même façon, quelle que soit la locale du système. -
Copier un des deux fichiers vers la machine qui portera la comparaison.
rsync -av inventaire-$(hostname).txt utilisateur@autre-machine:~/inventaires/À l’écran : le nom du fichier, puis un bilan avec le nombre d’octets transmis. Le transfert se comporte comme celui décrit dans rsync et sauvegarde.
-
Comparer les deux fichiers, version comprise.
diff -u inventaire-machine-a.txt inventaire-machine-b.txtÀ l’écran : des lignes préfixées par
-pour ce qui n’existe que dans le premier fichier, par+pour le second. Un code de sortie 0 signifie que les fichiers sont identiques, le code 1 qu’ils diffèrent, et ce code 1 n’est pas une erreur de la commande. -
Isoler les paquets présents sur une seule machine.
cut -f1 inventaire-machine-a.txt | LC_ALL=C sort > noms-a.txt cut -f1 inventaire-machine-b.txt | LC_ALL=C sort > noms-b.txt LC_ALL=C comm -3 noms-a.txt noms-b.txtÀ l’écran : une ligne par paquet absent d’une des deux listes, sans indentation pour ceux de A et décalés d’une tabulation pour ceux de B. Le décompte des différences se fait avec
diff noms-a.txt noms-b.txt | grep -c '^[<>]'.
Un nom de champ inconnu ne provoque pas d’erreur, seulement des lignes vides
dpkg-query examine successivement les champs de la fiche du paquet, les champs virtuels qu’il fabrique (binary:Package, binary:Synopsis, db:Status-Abbrev, source:Version) et les champs libres de la fiche. Quand aucune de ces tables ne contient le nom demandé, la référence ne produit rien du tout. La faute de frappe la plus courante, ${Versions} au lieu de ${Version}, donne donc un fichier de lignes vides et un code de sortie 0, sans le moindre avertissement.
dpkg-query -W -f='${Versions}\n' dpkg
dpkg-query -W -f='${Version}\n' dpkg
À l’écran : une ligne vide pour la première commande, puis 1.21.22 pour la seconde sous Debian 12. Le test se fait sur un seul paquet avant de relancer la commande sur tout le système, et les noms sont sensibles à la casse : ${version} en minuscules est inconnu, tout comme ${Architecture} écrit ${architecture}. La liste des champs reconnus et la syntaxe ${champ[;largeur]} figurent dans dpkg-query --help.
Le bloc d’aide est en retrait sur un point : il cite \n, \r et \\ mais omet \t, alors que la page de manuel liste bien les trois et que le code source accepte la tabulation.
Quatre erreurs de format s’affichent avec le préfixe du programme
Une faute dans la structure du format, elle, arrête la commande. Messages relevés tels quels avec dpkg-query 1.21.22 :
dpkg-query: error in show format: missing closing brace
dpkg-query: error in show format: invalid character 'x' in field width
dpkg-query: error in show format: field width is out of range
dpkg-query: error in show format: may not be empty string
missing closing brace: une référence ouverte par${n’est jamais fermée, cas de-f='${Package'. Le remède est l’accolade fermante.invalid character 'x' in field width: la largeur qui suit le point-virgule n’est pas un entier, cas de${Package;12x}. Le caractère fautif est recopié entre apostrophes dans le message.field width is out of range: la largeur dépasse les bornes d’un entier, cas d’une valeur recopiée par erreur.may not be empty string: le format passé à-fest vide.
Dans les quatre cas, dpkg-query quitte avec le code de sortie 1 et le fichier de sortie reste vide, la vérification du format ayant lieu avant toute lecture de la base.
${Package} ne porte pas l’architecture, ${binary:Package} si
Le champ ${Package} rend le nom tel qu’il est écrit dans la fiche, sans qualificatif. Le champ virtuel ${binary:Package}, disponible depuis dpkg 1.16.2, ajoute un qualificatif d’architecture quand le nom serait ambigu : paquets portant Multi-Arch: same, et paquets d’une architecture étrangère à la machine. Sur un système amd64 où l’architecture i386 a été activée, une même bibliothèque installée deux fois produit donc deux lignes identiques avec ${Package}, et deux noms distincts du type libc6:amd64 et libc6:i386 avec ${binary:Package}.
Pour un fichier destiné à la comparaison, le qualificatif vaut mieux, et l’ajout du champ ${Architecture} lève le doute complètement.
LC_ALL=C dpkg-query -W -f='${binary:Package}\t${Version}\t${Architecture}\n' > inventaire-$(hostname).txt
À l’écran : rien. Chaque ligne du fichier se lit désormais en trois colonnes, la troisième valant amd64, i386 ou all.
Cinq commandes, cinq sorties différentes pour le même système
| Commande | Version installée | Colonnes | Paquets supprimés non purgés | Texte traduit dans la sortie |
|---|---|---|---|---|
dpkg-query -W -f='…' |
oui, sur demande avec ${Version} |
2 avec le format proposé, davantage si des champs sont ajoutés | oui, état rc, filtrable avec ${db:Status-Abbrev} |
non, les données sortent brutes |
dpkg-query -l |
oui | 5 : état sur trois caractères, nom, version, architecture, résumé | oui, état rc |
oui, en-têtes et résumés ; format non configurable et variable selon la largeur du terminal |
dpkg --get-selections |
non | 2 : nom, état de sélection (install, hold, deinstall ou purge) | oui, sous l’état deinstall |
non |
apt list --installed |
oui | 4 : nom/origine, version, architecture, crochet d’état | crochet [residual-config] pour une fiche dont il ne reste que la configuration |
oui, crochets du type [installed,automatic] et avertissement WARNING: apt does not have a stable CLI interface. Use with caution in scripts. |
apt-mark showmanual |
non | 1 : le nom | non, seuls les paquets marqués manuellement sont listés | non ; la liste est stockée dans /var/lib/apt/extended_states |
L’avertissement d’apt apparaît sur la sortie d’erreur dès que la sortie standard n’est plus un terminal, donc dans tout script qui redirige vers un fichier ; il se désactive avec -o Apt::Cmd::Disable-Script-Warning=true. Pour le choix inverse, celui d’un fichier à lire soi-même, les dépôts et les versions disponibles sont traités dans les dépôts APT.
L’inventaire se relit hors ligne, sans droit particulier
Faut-il utiliser dpkg-query -W ou apt list –installed pour un inventaire ?
dpkg-query -W quand le fichier doit être comparé ou traité par un script : une tabulation sépare les champs, aucune traduction ne s’y glisse et le code de sortie vaut 1 si un motif de recherche ne correspond à rien. apt list –installed reste pratique pour un coup d’œil humain, avec deux réserves mesurables : l’avertissement WARNING: apt does not have a stable CLI interface. Use with caution in scripts. sur la sortie d’erreur dès que la sortie est redirigée, et des crochets d’état traduits selon la locale. L’avertissement se coupe avec apt -o Apt::Cmd::Disable-Script-Warning=true list --installed, ce qui laisse intact l’argument du message.
Comment comparer deux machines, concrètement ?
Générer inventaire-$(hostname).txt des deux côtés avec la même commande, puis copier le second fichier avec rsync -av. La comparaison tient ensuite en une ligne : diff -u inventaire-machine-a.txt inventaire-machine-b.txt, dont le code de sortie 1 signale des différences et 0 l’égalité. Pour ne voir que les paquets manquants, la première colonne se découpe avec cut -f1, chaque liste se trie avec LC_ALL=C sort, et comm -3 affiche les paquets présents d’un seul côté.
Quel espace disque et quels droits faut-il ?
Une ligne par paquet, soit un fichier texte de quelques dizaines de kilo-octets sur un poste de travail ; le compte exact se lit avec wc -l et la taille avec du -h inventaire-*.txt. Aucun droit particulier n’est nécessaire : la page de manuel de dpkg-query 1.22.22 sous Debian 13 rappelle que les opérations de consultation ne devraient jamais exiger root, et /var/lib/dpkg/status est lisible par tous les comptes. La commande ne contacte aucun dépôt, tout vient de la base locale.
Que se passe-t-il si le format est mis entre guillemets doubles ?
Le shell développe ${binary:Package} avant que dpkg-query ne le voie : la variable n’existe pas dans l’environnement, elle est remplacée par une chaîne vide, et le fichier ne contient plus que des tabulations et des retours à la ligne. Avec set -u, bash refuse carrément la commande en signalant une variable non définie. Le remède tient aux guillemets simples, que le format supporte sans difficulté puisqu’il ne contient aucune apostrophe.
Pourquoi une colonne est-elle vide alors que le paquet est installé ?
Parce qu’un nom de champ inconnu est ignoré sans message : dpkg-query imprime une ligne vide et sort avec le code 0, cas de ${Versions} ou de ${version} en minuscules. Le test se fait sur un seul paquet, dpkg-query -W -f='${Version}\n' dpkg, qui doit afficher 1.21.22 sous Debian 12 ; une ligne vide indique une faute dans le nom du champ. La liste des noms reconnus figure dans dpkg-query --help.
Comment inventorier un système éteint, monté depuis une clé USB ?
L’option --admindir fait lire une autre base dpkg que celle de la machine en cours, par exemple /mnt/racine/var/lib/dpkg pour un système monté. L’option --root=/mnt/racine, disponible depuis dpkg 1.21.0, produit le même effet en préfixant var/lib/dpkg à la racine indiquée. La consultation reste en lecture seule : aucun fichier du système monté n’est modifié.
dpkg-query --admindir=/mnt/racine/var/lib/dpkg -W -f='${binary:Package}\t${Version}\n' > inventaire-sauvegarde.txt
Le fichier obtenu se compare ensuite au système en cours avec diff -u, comme deux machines distinctes.