Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

One can select testing packages on a gentoo stable system by adding a line with the following syntax to keywords list:

cat /etc/portage/package.keywords

=dev-python/ipython-0.13.2 ~amd64
# and many lines later
=dev-python/ipython-0.14.1 ~amd64
# and many lines later
>=dev-python/ipython-0.13.4 ~amd64

This file will grow within the time and sooner or later one can not remember which lines are obsolete.

How can I tidy up the list with a script from time to time?

A line should be deleted,

  • if the testing version is already stabilized
  • >= was used for the same package
  • = was used for the same package with smaller version number
share|improve this question
If you're still watching this see my Answer. – eyoung100 Dec 5 '14 at 22:14

I wrote a small python script that takes care of this problem. The logic looks at each line in the file package.accept_keywords and only acts on lines that start with = or <=. These lines have a maximum bound version so we can check if they are needed anymore. Lines without a qualifier or a >= are left as-is as we cannot know if they are obsolete.

The lines that we care about are then parsed and the installed version of the package is checked. If the installed version is newer than the keyworded version, or is not longer installed at all, the keyword is considered obsolete. If the installed package is the same version as the keyworded version then the installed package is checked to see if it is still keyworded. If it has been stabilized, the line is obsoleted, otherwise it is retained.

#!/bin/env python

import re
import portage

vartree = portage.db[portage.root]['vartree']

with open('/etc/portage/package.accept_keywords') as f:
    for x in f:
        # eat newline
        x = x.rstrip()
        # we only want lines with a bounded max version
        if re.match('^(=|<=)',x):
            # get the package cpv atom -- strip the =|<= and the trailing keyword(s)
            cpv_masked = re.sub('[<=]','',x.split(' ',1)[0])
            cat, pkg, ver, rev = portage.catpkgsplit(cpv_masked)
            # get cpv for all installed versions of the package
            cpv_installed = vartree.dep_match(cat+'/'+pkg)
            for cpv in cpv_installed:
                cmp = portage.pkgcmp(portage.pkgsplit(cpv), portage.pkgsplit(cpv_masked))
                # if the installed version is not newer than the masked version
                if (cmp <= 0):
                    # check if this version is still keyworded
                    cpv_keywords = vartree.dbapi.aux_get(cpv, ['KEYWORDS'])
                    # keep keyword if the package has no keywords (**)
                    if not cpv_keywords[0]:
                    # check if the installed package is still keyworded
                    for cpv_keyword in cpv_keywords[0].split(' '):
                        if cpv_masked_keyword == cpv_keyword:
                            # it is, keep the atom and move on to the next one
            # keep atoms that have an unbounded max version

This will print the new keywords file to standard out. Note: do not redirect the output back into /etc/portage/package.accept_keywords or you will clobber the file and lose everything.

This will go a long way toward cleaning up your keywords file and for your other concerns, sorting the file and then examining it for multiple lines for the same package will help resolve most of what is left.

share|improve this answer

Start with eix -tT. Install app-portage/eix to get that.

share|improve this answer
I do not understand how eix -tT solves this. Can you explain it a bit more please? – Jonas Stein Jul 13 '14 at 20:35
I think you'd have to pastebin some output and point to line numbers of parts you don't understand. – lkraav Jul 13 '14 at 20:49
I wish there was more info. This leads to "tips and tricks" and the link to the original blog post is dead. This helps a bit. The package has been active recently. The homepage link points to eix.berlios.de but this doesn't exist. Where are the manpages for that online(with all the options)? – On a eu. Jul 22 '14 at 20:08

Adding to Ikraav's answer:

After using eix -tT, remove the comparison operators and the package version number. Your File can also be written as:

dev-python/ipython ~amd64
# and many lines later
package-cat/package ~arch

This will guarantee that you will always get the testing versions of dev-python/ipython and package-cat/package

share|improve this answer
the ~amd64 in my question might be misleading. The version numbers should stay untouched. I do not want to get always the latest version, but find redundant lines. – Jonas Stein Dec 10 '14 at 16:54
Using the package without the version removes the duplicate entries. Consider Package A requires >= Package C Version 1.0.0, and Package B requires C Version 1.0.1. In practice, Package A and B are both Satisfied by any version Greater than 1, and if all versions > 1.0.0 are in ~arch then the version numbers are irrelevant. The only other way to fix this is to mask all versions of Package C then unmask the greatest Version after the portage world update. – eyoung100 Dec 10 '14 at 17:48
I'm old school, and I manually edit all my Portage config files, mainly because I learned how to do it before portage did it automatically. The reason you have duplicates is because portage won't remove the line when a newer version supersedes an older one. – eyoung100 Dec 10 '14 at 17:56

You know you can convert the package.* files in directories, right?

Then you can organize your atoms in several files, eg, in my system I got the following (well, not really, i am not at my laptop now. But you het the idea):




I found this really useful to help me have the files updated.

share|improve this answer

Here is a small script that filters entries from /etc/portage/package.* files that are not installed anymore. Additionally it removes all comment lines directly above the removed entry. (e.g. as generated by autounmask). If comments are separated by a blank line it will only remove the lower comments. The script does not remove duplicate entries.

Please note that portage-utils need to be installed and the postsync hook /etc/portage/postsync.d/q-reinitialize must be activated in order to get this script working.

#!/usr/bin/env python3

import argparse
import sys
import os
from subprocess import call
import contextlib

if __name__ != '__main__':
    raise Exception("ust be used as a main module with a parameter as the input file")

parser = argparse.ArgumentParser(description="cleanup /etc/portage/package.* files")
parser.add_argument("infile", help="an input file to clean")
parser.add_argument("--out", dest="outfile", help="the output is written to this file. if not specified, the output is written to stdout.")
parser.add_argument("--inplace", action='store_true', help="overwrite the in file. if specified, --out is ignored.")

args = parser.parse_args()

def checkInstalled(package):
    with open(os.devnull, 'w') as devnull:
        status = call('qlist -IC "' + str(package.split()[0].strip()) + '"', shell=True, stdout=devnull)
        return status == 0

def getOutFile(args):
    if args.inplace:
        fh = open(args.infile, 'w')
    elif args.outfile != None:
        fh = open(args.outfile, 'w')
        fh = sys.stdout
        yield fh
        if fh is not sys.stdout:

commentBuffer = []
lines = []

with open(args.infile, 'r') as f:
    lines = f.readlines()

with getOutFile(args) as out:
    for line in lines: 
        if line.lstrip().startswith("#"):
            if line.strip() == "" or checkInstalled(line):
                if  commentBuffer:
            commentBuffer = []
share|improve this answer

There is an official package now for this task called app-portage/portpeek.

It can

  • find obsolete USE flags and
  • obsolete KEYWORDS and
  • clean the files, if -f (fix) is added as parameter.
share|improve this answer

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.