3

I sometimes find myself wanting to use the output of apt list as a list of arguments to apt, i.e. a space separated list of package names.

Any time I want this I have to search for how to do it and end up combining answers that cut and sed, etc. Great, but I can never remember this, because I don't do it daily — is there a simpler way?

I'm thinking it's natural to want to do this and that I just haven't understood the trick.

One example would be if I want to hold all installed packages with the version 24.2.6-1, I'd do apt list --installed | grep "24.2.6-1" and then a web search: "Right, cut -d'/' -f1", and then I'd still end up with newlines, prompting another search. Does apt perhaps have something built in that makes this easier and more memorable?

2
  • 1
    For this kind of thing, I rather use aptitude: aptitude search '~S~i ~V ^24\.2\.6-1$' (add -F '%p %v for the list to have package name and version). Replace search with hold to hold them Commented yesterday
  • 1
    I haven't used apt in a while, so I don't know if it has something, but would you be open to a scripting solution? This is really quite east to script and once you've done the work of scripting it once, you never need to do that websearch again. Commented yesterday

1 Answer 1

2

I'd use aptitude which has a very powerful search capability and can do most if not all of what apt can do both interactively and not (and has long before the apt command (which itself is mostly a wrapper around the other dpkg and APT utilities) even existed).

Searching for installed packages whose installed version is 24.2.6-1 would be a search string of:

~S ~i ~V ^24\.2\.6-1$

Which is short for:

?narrow(?installed, ?version(^24\.2\.6-1$))
Long form Short form Description
?narrow(filter, pattern) ~S filter pattern Select packages for which a single version matches both filter and pattern.
?installed ~i Select installed packages.
?version(version) ~Vversion Select packages whose version matches version (special values: CURRENT, CANDIDATE, and TARGET).

Non-interactively, you can use the search subcommand to see the matching list:

$ aptitude -q search '?narrow(?installed, ?version(^0\.8\.13-5ubuntu5$))'
i   aptitude                        - terminal-based package manager
i A aptitude-common                 - architecture independent files for the apt

And adjust the display format with the -F (see the manual for the list of format directives) and/or --disable-columns option:

$ aptitude -q search --disable-columns -F '%p %v' '?narrow(?installed, ?version(^0\.8\.13-5ubuntu5$))'
aptitude 0.8.13-5ubuntu5
aptitude-common 0.8.13-5ubuntu5

Or you can use hold here instead of search to hold the corresponding packages:

aptitude hold '~S ~i ~V ^24\.2\.6-1$'

interactively, you'd start aptitude, go to Views -> New Flat Package List (or make the flat package list the default in Options -> Preferences which I always do as well as ticking the Advance to the next item after changing the state of a package), then press l (that's a lower case L) to limit the list, and enter the search query.

Then you can mark the packages in that limited list as held by pressing = on each of them.

With apt list, you could extract the package names for the matching versions and feed to xargs -r apt-mark hold:

apt list --installed |
  grep -Po '^[^\s/]+(?=\S* 24\.2\.6-1 )' |
  xargs -r apt-mark hold

But you'll notice this warning:

WARNING: apt does not have a stable CLI interface. Use with caution in scripts.

Which means it may no longer work in future versions.

To get the package list, you'd be better of running dpkg-query (what dpkg -l runs under the hood).

dpkg-query -f='${Package} ${Version}\n' -W |
  perl -lane 'print $F[0] if $F[1] eq "24.2.6-1"' |
  xargs -r apt-mark hold

Note that grep, like aptitude's -V (or grep or awk's ~ or /.../ operator) does a regular expression match.

Your grep 24.2.6-1 would return all lines that contain a string (anywhere, not just in the version field) that matches 24.2.6-1 where . in there matches any single character, so it would return lines that contain 2024-206-123 for instance.

Using grep -Fw 24.2.6-1 which would be like grep '\<24\.2\.6-1\>' would be an improvement but would still match on lines containing 1.24.2.6-1.2 for instance. Since in current versions of apt, the version field is delimited by space characters, grep -F ' 24.2.6-1 ' would further improved it, though would still not be as reliable and future-proof as explicitly requesting a match on the version field like aptitude's ~V regexp or ?version(regexp) does.


Also, to clear a potential confusion (common among people coming from the Microsoft world):

I sometimes find myself wanting to use the output of apt list as a list of arguments to apt, i.e. a space separated list of package names.

apt commands can often take a list of package names as separate arguments, but usually not a space separated list of package names.

apt install 'zsh mksh ksh'

Or:

system('apt', 'install', 'zsh mksh ksh');

Which would be respectively shell and perl syntax to invoke apt, with install as subcommand and a zsh mksh ksh argument containing a space separated list of package names would not work.

What you want is:

apt install zsh mksh ksh

same in perl:

system('apt', 'install', 'zsh', 'mksh', 'ksh');

Where each package name makes up a separate argument to the apt command.

It just so happens that while in the perl syntax, you need a , to separate arguments to the system() function and quotes around string literals, in shell syntax, a space is enough and quotes there are not needed as everything is a string in the shell language syntax.

In the end, both shell and perl will invoke the execve() system call as execve("/usr/bin/apt", argv, envp) where argv is a NULL-delimited array of memory addresses to NUL-delimited arrays of bytes (C-strings) which we could represent as ["apt", "install", "zsh", "mksh", "ksh", 0] and that has no space character whatsoever anywhere.

Above, we used xargs (cross arguments) to take that newline-delimited of packages from stdin and pass them across as arguments to apt-mark.

xargs by default splits on blanks and newlines and understand some form of quoting (similar to the syntax of the Mashey shell from the 70s which it is contemporary to, but not to that of modern shells).

That should be safe as Debian package and version names should be guaranteed not to contain whitespace nor quotes nor backslashes and only have ASCII characters.

With the GNU implementation of xargs (the one found on Debian), you can pass -d '\n' to xargs to split on newline only.

Alternatively, you could use the IFS-splitting+glob operator of your shell with:

apt-mark hold $(command-that-generates-a-list-of-packages)

That command's output would be split on characters of $IFS (which by default contains space, tab and newline (and NUL in zsh)), and then (except in zsh) be subject to globbing.

That should still be safe as Debian package names and versions should also be guaranteed not to contain globbing operators (?, *, [...] at least), though again, you can do:

IFS=$'\n'
set -o noglob # unneeded in zsh

To split on newline (aka linefeed) only and disable globbing, or in zsh, use the f parameter expansion flag to explicitly split on linefeed:

apt-mark hold ${(f)"$(cmd...)"}

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.