22

Is there a name-parsing tool that is part of the official RPM tool package?

I have a list of filenames. Each is the filename of an RPM package. I don't have the actual packages, just the filenames. For each I need to extract the package name and version ($NAME and $VERSION). The reason I need this is I am writing a script that then makes sure that "yum install $VERSION" installs $VERSION. This is part of a system that builds packages and verifies they are properly uploaded.

The list of filenames looks like:

$ cat /tmp/packages.txt
/home/builder/packages/testing-dev/CentOS/6/x86_64/emacs-mercurial-2.8-3.el6.x86_64.rpm
/home/builder/packages/testing-dev/CentOS/6/x86_64/emacs-mercurial-el-2.8-3.el6.x86_64.rpm
/home/builder/packages/testing-dev/CentOS/6/x86_64/mercurial-2.8-3.el6.x86_64.rpm
/home/builder/packages/testing-dev/CentOS/6/x86_64/mercurial-hgk-2.8-3.el6.x86_64.rpm
/home/builder/packages/testing-dev/CentOS/6/x86_64/python-redis-2.8.0-2.el6.noarch.rpm
/home/builder/packages/testing-dev/CentOS/6/x86_64/redis-2.6.16-1.el6.1.x86_64.rpm
/home/builder/packages/testing-dev/CentOS/6/x86_64/sei_dnsmaster-1.0-99.el6.x86_64.rpm

I found the following code which is a BASH function that does the task:

function parse_rpm() { RPM=$1;B=${RPM##*/};B=${B%.rpm};A=${B##*.};B=${B%.*};R=${B##*-};B=${B%-*};V=${B##*-};B=${B%-*};N=$B;echo "$N $V $R $A"; }

for i in $(</tmp/packages.txt) ; do
    parse_rpm $i
done

It works. Mostly. There are some exceptions:

$ parse_rpm CentOS/6/x86_64/sei_dnsmaster-1.0-99.el6.x86_64.rpm
sei_dnsmaster 1.0 99.el6 x86_64

Notice that it didn't get the version correctly (it should be 1.0-99)

I'm wondering (1) if there is a tool in the rpmdev package that does this correctly. (2) If not, is there an official regex I could use. (3) What is the python equivalent of that regex?

Thanks in advance!

1
  • Can you clarify where you are getting your input from and the format it takes please.
    – user9517
    Nov 26, 2013 at 8:38

10 Answers 10

29

You don't need to do any of this; RPM has a query format argument which will let you specify exactly the data you want to receive. It will even output without line endings if you don't specify them.

For instance:

rpm --queryformat "%{NAME} %{VERSION} %{RELEASE} %{ARCH}" -q coreutils
rpm --queryformat "The version of %{NAME} is %{VERSION}\n" -q coreutils

rpm --queryformat "%{NAME} %{VERSION} %{RELEASE} %{ARCH}" -qp file.rpm

The complete list of variables you can use can be obtained with:

rpm --querytags

Note that in the case of RELEASE, output like 84.el6 is normal and expected, since this is actually how RPM packages are versioned when packaged by or for a distribution.

6
  • 3
    That only works with installed packages. I want to manipulate filenames. $ rpm --queryformat "%{NAME} %{VERSION} %{RELEASE} %{ARCH}" -q CentOS/6/x86_64/sei_dnsmaster-1.0-84.el6.x86_64.rpm package CentOS/6/x86_64/sei_dnsmaster-1.0-84.el6.x86_64.rpm is not installed
    – TomOnTime
    Nov 25, 2013 at 20:52
  • @TomOnTime Wait a minute... So you don't care what's actually in the package? Nov 25, 2013 at 21:40
  • 4
    I wish I'd known that sooner. RPM tools only deal with the package contents; the filename is completely irrelevant (and this answer won't work for you). Nov 25, 2013 at 23:57
  • 1
    Have fun parsing, for instance: libopenssl0_9_8-32bit-0.9.8j-0.26.1_0.50.1.x86_64.delta.rpm
    – MikeyB
    Nov 26, 2013 at 17:35
  • 8
    @TomOnTime - "That only works with installed packages" Not true - you missed the -p option in the third example: rpm --queryformat "%{NAME} %{VERSION} %{RELEASE} %{ARCH}" -qp file.rpm
    – Sam Elstob
    Nov 29, 2013 at 12:51
16

I've been told the official way to do what I'm seeking is in Python:

from rpmUtils.miscutils import splitFilename

(n, v, r, e, a) = splitFilename(filename)

I've written a short Python program that does what I need. I will offer the script to the rpmdev project for inclusion.

2
  • 1
    The Debian package naming rules are so simple and straghtforward - I do not know how the rpm world has ended up in such a mess. Please could you paste your script into the answer here? Dec 14, 2016 at 13:19
  • @PaulHedderly You can take a look at the source code for splitFilename. It's pretty much just delimiters parsed from right to left, with the rest being the package name. Jan 13, 2023 at 18:24
4

I worked out regular expressions that fit all the data I was able to test them with. I had to use a mixture of greedy and non-greedy matches. That said, here is my perl and python versions:

Perl:

#! /usr/bin/perl

foreach (@ARGV) {
    ($path, $name, $version, $release, $platform,
      @junk) = m#(.*/)*(.*)-(.*)-(.*?)\.(.*)(\.rpm)#;
    $verrel = $version . '-' . $release;

    print join("\t", $path, $name, $verrel, $version, $rev, $platform), "\n";
}

Python:

#! /usr/bin/python

import sys
import re

for x in sys.argv[1:]:
    m = re.search(r'(.*/)*(.*)-(.*)-(.*?)\.(.*)(\.rpm)', x)
    if m:
        (path, name, version, release, platform, _) = m.groups()
        path = path or ''
        verrel = version + '-' + release
        print "\t".join([path, name, verrel, version, release, platform])
    else:
        sys.stderr.write('ERROR: Invalid name: %s\n' % x)
        sys.exit(1)

I'd rather have a regex that comes from the RPM project. The one that I invented above will have to do do for now.

3
  • This is mostly similar than my solution (but avoid .* unless you REALLY want to match anything). Nice to see that you found by yourself, though !
    – mveroone
    Nov 26, 2013 at 15:33
  • 2
    The filename strikes me as a bad way to get this information. It may work for a specific set of vendor-provided RPMs (so you may be OK as long as your vendor standardizes third-party stuff and never changes their naming format), but I've seen plenty of creatively-named RPM files. The Acrobat Reader I grabbed from Adobe a few seconds ago is AdbeRdr9.5.5-1_i486linux_enu.rpm) which breaks your regex parsing above.
    – voretaq7
    Nov 26, 2013 at 17:40
  • True. But Adbe wouldn't work for any solution because it breaks the yum filename standard. (Technically the question should be about yum filenames, not RPM filenames).
    – TomOnTime
    Nov 18, 2016 at 14:31
2

Rpm files can have some funky file names in extreme cases, but generally you can split the NVR on the hyphens. The catch is the N (name) portion of the NVR may contain hyphens and underscores, but the V(version) and R(release) are guaranteed to not have any extraneous hyphens. So you can start by trimming off the VR portion to derive a Name.

$ RPM=/home/builder/packages/testing-dev/CentOS/6/x86_64/emacs-mercurial-2.8-3.el6.x86_64.rpm
$ echo ${RPM%-*-*}
/home/builder/packages/testing-dev/CentOS/6/x86_64/emacs-mercurial

Building on that you can isolate the Version and Release portion.

echo ${RPM#${RPM%-*-*}-*}
2.8-3.el6.x86_64.rpm

Just split the hyphen again to isolate the part you need. And obviously clean out the arch and rpm file extension strings, which is a given. Just giving you an idea of how it could be approached in bash.

2

IMHO the simplest shell way is:

ls | rev | cut -d/ -f1 | cut -d- -f3- | rev

That is: reverse each line, using slash cut just the first part (emanelif), then using hyphen cut all but the first two parts (i.e. leave behind ESAELER including emanelif eth fo tser and NOISREV) and reverse the enil back.

With your example file:

$ cat /tmp/packages.txt | rev | cut -d/ -f1 | cut -d- -f3- | rev
emacs-mercurial
emacs-mercurial-el
mercurial
mercurial-hgk
python-redis
redis
sei_dnsmaster
$

To get other parts is excercise on reading cut(1).

1
  • Best and most easiest answer so far. Sometime people tend to over engineer which we must avoid.
    – BTR Naidu
    Mar 1, 2022 at 13:13
1

Use the -q --queryformat options from rpm as said before, if you want to do this on a non installed package you can specify the rpm with the -p option, like this:

rpm -q -p ./Downloads/polysh-0.4-1.noarch.rpm --queryformat "%{NAME} %{VERSION} %{RELEASE} %{ARCH}\n"
polysh 0.4 1 noarch

e.g.

$ ls ./Downloads/*.rpm
./Downloads/adobe-release-x86_64-1.0-1.noarch.rpm
./Downloads/nautilus-dropbox-1.6.0-1.fedora.x86_64.rpm
./Downloads/playonlinux-yum-4-1.noarch.rpm
./Downloads/skype-4.2.0.11-fedora.i586.rpm
./Downloads/dbview-1.0.4-2.1.x86_64.rpm
./Downloads/openmotif22-libs-2.2.4-192.1.3.x86_64.rpm
./Downloads/polysh-0.4-1.noarch.rpm

gives me

adobe-release-x86_64 1.0 1 noarch
dbview 1.0.4 2.1 x86_64
nautilus-dropbox 1.6.0 1.fc10 x86_64
openmotif22-libs 2.2.4 192.1.3 x86_64
playonlinux-yum 4 1 noarch
polysh 0.4 1 noarch
skype 4.2.0.11 fc16 i586

so just splitting the filename is wrong!

for filename in """<paste list here>""".split():
    print splitFilename(filename)

('./Downloads/adobe-release-x86_64', '1.0', '1', '', 'noarch')
('./Downloads/nautilus-dropbox', '1.6.0', '1.fedora', '', 'x86_64')
('./Downloads/playonlinux-yum', '4', '1', '', 'noarch')
('./Downloads/skype', '4.2.0.11', 'fedora', '', 'i586')
('./Downloads/dbview', '1.0.4', '2.1', '', 'x86_64')
('./Downloads/openmotif22-libs', '2.2.4', '192.1.3', '', 'x86_64')
('./Downloads/polysh', '0.4', '1', '', 'noarch')

so pay attention, this is not the correct details of the rpm, e.g. 1.fedora is actually 1.fc10 in the rpm.

1
  • I see the confusion. Not only is the RPM not installed, I don't have it on this machine. I'm processing lists of packages and file names. This is for something that manages repo inventories; it doesn't have the actual packages.
    – TomOnTime
    Dec 4, 2013 at 3:47
0

If you're familiar with Regular Expressions and/or Perl, that's quite easy.

 ls | head | perl -p -e 'm#([^\-]+?)-(.*).rpm$#; print "$1 $2\n";$_=""' 

or the regex alone :

m#([^\-]+?)-(.*).rpm$#

If you split it that's :

  • anything but a Hyphen, at least one character : [^\-]+ (escaped because hyphen have a special meaning in character groups)
  • stop the match after the first hyphen (and not the last) : [^\-]+?
  • add this to a capture group : ([^\-]+?)
  • Then a hyphen : ([^\-]+?)-
  • then anything else in another capture group (but the trailing .rpm) : ([^\-]+?)-(.*).rpm$ (the dollar means "end of line")
  • enclose than in a practical matching format : m#([^\-]+?)-(.*).rpm$#

Done ! Just get both parts in the variables $1and $2

Comment on the first one-liner :

I was in a directory with many rpm files, hence the ls.

perl -p is equivalent to ;

perl -e 'while(<STDIN>){ chomp($_);  [YOUR CODE HERE] ; print($_); }' 

Which explain that I had to put a null-string in $_ to avoid perl printing back the line after I have extracted and custom-printed it. Note that I could have used substitutions to aoid this little 'hack'.

1
  • This does not work at all on hundreds of RPM names, e.g. module-init-tools-3.9-21.el6_4.x86_64.rpm.
    – Nemo
    Oct 20, 2014 at 0:10
0

You can utilize dnf info. Here is an example Bash script to get values and to set as a variable:

function dnfinfo() {
   dnf info "$(echo "${1}" | sed 's/\.rpm$//g')"
}

function splitname() {
   eval $(
     dnfinfo "${1}" | \
     grep "^Arch\|^Name\|^Release\|^Version" | \
     sort | \
     awk -F": " {'print "\""$2"\""'} | \
     tr "\n" " " | \
     awk {'print "xarch="$1"~xname="$2"~xrel="$3"~xver="$4'} | \
     tr "~" "\n"
   )
}

splitname "tcpdump-4.9.2-5.el8.x86_64.rpm"
echo "${xname} ${xver} ${xrel} ${xarch}"

It will give a result even if the package is not installed.

0

By far the easiest way is, to create a regex which will do one thing, work ;-) Substring Regex w're disc.:

s/^(?:\w{1})(?:-?\w+?\S+?-?){1,}?(\d+[\:|\.]\d+\S+)/$1/g

That's the substring regex in perl !

Example:

echo  bonnie-++sdsdsœ@@œ=gcc-++++_sded-sdsdsd-fcc-gcc-c++-4.8.5-36.0.1.el7_6.2.x86_64|perl  -pe 's/^(?:\w{1})(?:-?\w+?\S+?-?){1,}?(\d+[\:|\.]\d+\S+)/$1/g'

Output: 4.8.5-36.0.1.el7_6.2.x86_64

So the version is right. The regex covers 99,9% of RPM packages names in a single line ;-)

Short explanation: (?:) Non-capturing group (i don't want this part -> use it for speed. Always)

\w+: Word Characters (including _)

? Optional match

\S+: Non-Space character

{1,} Not-capture-match the whole group 1 or more

Worx for you and for me !

0

All of "answers" above that attempt to obtain RPM information from the filename are incorrect. The correct way is to introspect (inside) the rpm file for this information. People who wrote several yum ansible modules still don't understand this, and we just reported it as a bug to Red Hat, and they are still trying to resolve it.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .