[CLUE-Tech] Useful debian cleanup script?

Dale K. Hawkins dhawkins at cdrgts.com
Thu May 27 17:42:04 MDT 2004


I noticed there are some Debian users on the list.  I wrote a utility
that can help determine packages which are never used (not accessed) and
recommend them for cleanup.

It is not perfect, but it is useful.  I was hoping to get some feedback
on the script and then release it to the world.

You need ruby1.8 installed and grep-dctrl.

The general idea is to feed a list of package names to the program (via
the command-line or all packages are considered by default) and the
program spits out a list of packages which have not been accessed for
some number of days (60 is the default but see the -d option).

You can than feed that list back to 'apt-get remove' albeit cautiously.

Possible enhancements include a recursive depends feature.

This will not erase any files!

-Dale

-------------- next part --------------
#!/usr/bin/ruby
require 'pathname'
require 'getoptlong'

def usage
  $stderr.puts "usage: #{$0} [ -v ]* [ -r ] [ -d days(60) ] <pkg_name> [ ... ]"
  exit 1
end

days = 60
verbose = 0
recursive = false

opts = GetoptLong.new(["--days", "-d", GetoptLong::REQUIRED_ARGUMENT],
                      ["--verbose", "-v", GetoptLong::NO_ARGUMENT],
		      ["--recursive", "-r", GetoptLong::NO_ARGUMENT],
                      ["--help", "-h", GetoptLong::NO_ARGUMENT])

opts.each {|opt,val|
  case opt
  when "--days" then days = val
  when "--verbose" then verbose += 1
#  when "--recursive" then recursive = true
  else
    usage
  end
}

if days.to_i == 0
  $stderr.puts "illegal value given for days: #{days}"
end

days = days.to_i

if ARGV.empty?
  packages = IO.popen('grep-status -FStatus -sPackage -n  "install ok installed"').readlines
else
  packages = ARGV
end

class Pathname
  @@secondsPerDay = 60 * 60 * 24

  def parentInArray(paths)
    paths.any? { | p |
      fnmatch(p)
    }
  end

  def lastAccessed(t = Time.now)
    ((t - self.atime)/@@secondsPerDay).to_i
  end
end

class Package

  @@filesToIgnore = [
    '/usr/lib/menu/*',
    '/usr/share/applications/*',
    '/usr/share/man/*',
    '/usr/share/pixmaps/*',
    '/usr/share/mime-info/*',
    '/usr/lib/mime/packages/*',
    '/usr/share/doc-base/*',
  ]

  @@docDirs = [
    '/usr/share/doc/*',
  ]

  @@binaryDirs = [
    '/lib/*',
    '/usr/lib/*',
    '/bin/*',
    '/usr/bin/*',
  ]

  def initialize(name)
    @name = name
  end

  def getFiles
    if not defined? @files

      @files = IO.popen("grep-dctrl -FStatus ' installed' /var/lib/dpkg/status | grep-dctrl -PX -n -s Package #{@name}").collect { | pkg |
        IO.popen("dpkg --listfiles #{pkg}").collect { | fn |
          fn.chomp!
          if fn.empty?
            false
          else
            p = Pathname.new(fn.chomp)
          end
         }.select { | p |
          p && (p.file? && (! p.parentInArray(@@filesToIgnore)))
        }
      }.flatten

    end

    if @files.empty?
      raise "No regular files found (or package not installed)"
    end

    @files
  end

  def docFiles
    if not defined? @docFiles
      @docFiles = getFiles.select { | p |
        p.parentInArray(@@docDirs)
      }.sort {|x,y| y.atime <=> x.atime }
    end
    @docFiles
  end

  def binaryFiles
    if not defined? @binFiles
      @binFiles = getFiles.select { | p |
        p.parentInArray(@@binaryDirs)
      }.sort {|x,y| y.atime <=> x.atime }
    end
    @binFiles
  end

  def lastDocAccess
    if docFiles.empty?
      raise "No document files"
    else
      docFiles[0].lastAccessed
    end
  end

  def showlastDocumentAccess(number = 1)
    if docFiles.empty?
      raise "No doc files"
    else
      access_times = (0...([number, docFiles.size].min)).collect { | n |
        "#{docFiles[n]} was last accessed #{docFiles[n].lastAccessed} days ago"
      }
    end
  end

  def lastBinaryAccess
    if binaryFiles.empty?
      raise "No binary files"
    else
      binaryFiles[0].lastAccessed
    end
  end

  def showlastBinaryAccess(number = 1)
    if binaryFiles.empty?
      raise "No binary files"
    else
      access_times = (0...([number, binaryFiles.size].min)).collect { | n |
        "#{binaryFiles[n]} was last accessed #{binaryFiles[n].lastAccessed} days ago"
      }
    end
  end

  def getWhatDeps
    IO.popen("grep-status -n -s Package -FDepends -e #{@name}").readlines
  end

end


packages.each { | pn |

  pn.chomp!

  next if pn.empty?

  if verbose > 0
    puts "=============== #{pn} ==============="
  end

  success = false
  p = Package.new(pn)

  binDays = 10000
  begin
    binDays = p.lastBinaryAccess
    if verbose > 1
      puts p.showlastBinaryAccess(10).collect { | s |  "  " + s }
    end
    success = true
  rescue
    if verbose > 0
      $stderr.puts $!
    end
  end

  docDays = 10000
  begin
    docDays = p.lastDocAccess
    if verbose > 1
      puts p.showlastDocumentAccess(10).collect { | s |  "  " + s }
    end
    success = true
  rescue
    if verbose > 0
      $stderr.puts $!
    end
  end

  if success
    if [binDays, docDays].min > days
      puts "Recommend #{pn} for removal"
      if recursive
        puts p.getWhatDeps
      end
    else
      if verbose > 0
        puts "Keeping #{pn}"
      end
    end
  end


}



More information about the clue-tech mailing list