#!/usr/bin/perl -w =head1 NAME apt-sources - A utility for working with apt-sources. =cut =head1 SYNOPSIS apt-sources add|delete|list [regexp|entry] The first term upon the command line is the action to complete: add Add a new entry. delete Delete (comment-out) entries matching a pattern. list List entries, optionally only those matching a regexp. =cut =head1 ABOUT This is a simple utility to list/delete entries from the Debian apt-sources. =cut =head1 LICENSE Copyright (c) 2013 by Steve Kemp. All rights reserved. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut use strict; use warnings; use English; use File::Copy; use Getopt::Long; # # Get any command line arguments. # my %CONFIG; exit if ( !GetOptions( "file=s", \$CONFIG{ 'file' } ) ); # # Our handlers # my %dispatch = ( "list" => { sub => \&do_list }, "add" => { sub => \&do_add, root => 1 }, "delete" => { sub => \&do_delete, root => 1 } ); # # Get our action, if none is specified default to list. # my $action = shift || "list"; # # Lookup the entry in our dispatch-table # my $entry = $dispatch{ lc($action) }; if ( !$entry ) { print "Unknown action \"$action\". Valid actions are:\n"; print "\t" . join( ", ", sort( keys(%dispatch) ) ) . "\n"; exit(1); } # # Do we need to be root? # if ( $entry->{ 'root' } ) { if ( $UID != 0 ) { print "You must be root to run this action\n"; exit(1); } } # # OK call the handler # $entry->{ 'sub' }->( join( " ", @ARGV ) ); # # All done. # exit(0); =begin doc List entries. Optionally limit to those matching a regexp. =end doc =cut sub do_list { my ($regexp) = (@_); my @files = get_files(); # for each file. foreach my $file ( sort (@files) ) { open( my $handle, "<", $file ) or die "Failed to read $file - $!"; while ( my $line = <$handle> ) { # skip comments next if ( $line =~ /^#/ ); # skip empty lines. next unless ( length($line) > 0 ); next if ( $line =~ /^[ \t\n]+$/ ); # regexp? if ($regexp) { next unless ( $line =~ /$regexp/i ); } print $line; } close($handle); } exit 0; } =begin doc Delete any mention of a sources.list entry matching the given pattern. (Where delete means "comment out".) =end doc =cut sub do_delete { my ($pattern) = (@_); # # Ensure a pattern was specified. # if ( !$pattern ) { print "You must specify a pattern to delete.\n"; exit(1); } # # OK get the files. # my @files = get_files(); foreach my $file (@files) { # # See if any we should replace the file. # my $replace; open( my $handle, "<", $file ) or die "Failed to open file - $file - $!"; while ( my $line = <$handle> ) { # skip comments next if ( $line =~ /^#/ ); # skip empty lines. next unless ( length($line) > 0 ); next if ( $line =~ /^[ \t\n]+$/ ); # OK so a real entry needs replacing. $replace = 1 if ( $line =~ /$pattern/i ); } close($handle); # # OK do the replacement. # if ($replace) { # 1. rename the current file to .bak File::Copy::move( $file, "$file.bak" ); # 2. open and replace appropriately. open( my $input, "<", "$file.bak" ) or die "Failed to open file for input - $file.bak - $!"; open( my $output, ">", $file ) or die "Failed to open file for output $file - $!"; while ( my $line = <$input> ) { # replace the line - unless it is a comment. if ( ( $line !~ /^#/ ) && ( $line =~ /$pattern/i ) ) { $line = "# $line"; } print $output $line; } close($output); close($input); } } } sub do_add { my ($addition) = (@_); # # Ensure an entry was specified. # if ( !$addition ) { print "You must specify a source to add.\n"; exit(1); } # # Ensure the addition looks sane. # if ( $addition !~ /^deb(-src)?\s+/ ) { print "This doesn't look like a sane apt line\n"; exit(1); } # # The file the user specified. # my $file = $CONFIG{ 'file' }; if ( !$file ) { # # Generate an filename for use beneath /etc/apt/sources.list.d/ # # Attempt to use the hostname. # if ( $addition =~ /:\/\/([^\/]+)\// ) { my $domain = $1; # strip any ftp/www prefix $domain =~ s/^(www\.|ftp\.)//g; $file = $domain . ".list"; } else { print <", $file ) or die "Failed to write to file $file - $!"; print $handle "\n"; print $handle "# Added by apt-sources on " . scalar(localtime) . "\n"; print $handle $addition; print $handle "\n"; close($handle); exit(0); } =begin doc Return an array of all sources.list files. =end doc =cut sub get_files { # # The per-repo files. # my @files = glob("/etc/apt/sources.list.d/*.list"); # # The master file. # if ( -e "/etc/apt/sources.lists" ) { push( @files, "/etc/apt/sources.list" ); } return (@files); }