Last active
March 22, 2020 17:11
-
-
Save noscript/29a150a4225087219b90 to your computer and use it in GitHub Desktop.
Dpkg Time Machine
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# file: dpkg-since.py | |
# description: lists installed packages since the specified date | |
# changelog: | |
# * Feb 10 2015 - init release | |
# * Mar 24 2015 - new: packages sorted by installation time/date | |
# fix: ignored TIME argument | |
# * Apr 9 2015 - fix: exclude duplicates | |
# * Apr 27 2015 - new: recognize dates like '1 hour ago', 'yesterday' | |
# requires 'dateparser' | |
# - new: use new line or other splitter character | |
# * Jul 27 2019 - new: upgraded to python 3 | |
# * Feb 03 2020 - fix: clean up | |
# * Mar 22 2020 - change: removed 'dateparser' dependency, performance fixes | |
import sys | |
import getopt | |
from os.path import basename | |
from datetime import datetime | |
from glob import glob | |
import gzip | |
import re | |
import subprocess | |
def uniquify(seq): | |
seen = set() | |
seen_add = seen.add | |
return [x for x in seq if x not in seen and not seen_add(x)] | |
def print_usage(): | |
basename_ = basename(sys.argv[0]) | |
print('') | |
print('Usage:\n\t' + basename_ + ' [OPTIONS] DATE') | |
print('') | |
print('Options:') | |
print('\t-p, --print-date - prints the recognized date as the last line') | |
print("\t-n, --new-line - use new line as a splitter character") | |
print("\t-s, --splitter 'char' - set a custom splitter character") | |
print('\t-h, --help - display this help and exit') | |
print('') | |
print('Examples:') | |
print('\t' + basename_ + ' -p yesterday') | |
print('\t' + basename_ + ' -n 1 week ago') | |
print('\t' + basename_ + " -s ', ' 2015-02-14 16:40:35") | |
def main(): | |
print_date = False | |
splitter = " " | |
try: | |
opts, args = getopt.getopt(sys.argv[1:], "hps:n", ["help", "print-date", "splitter=", "new-line"]) | |
except getopt.GetoptError as err: | |
print(str(err)) | |
print_usage() | |
sys.exit(2) | |
for o, a in opts: | |
if o in ("-h", "--help"): | |
print_usage() | |
sys.exit() | |
elif o in ("-p", "--print-date"): | |
print_date = True | |
elif o in ("-s", "--splitter"): | |
splitter = a | |
if splitter == "\\n": | |
splitter = "\n" | |
elif o in ("-n", "--new-line"): | |
splitter = "\n" | |
else: | |
assert False, "invalid option" | |
if (not args): | |
print('missing date arguments') | |
print_usage() | |
sys.exit(1) | |
date_pattern = re.compile(r'(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})') | |
since_date_str = subprocess.check_output(['date', '-d', ' '.join(args), '+"%Y-%m-%d %H:%M:%S"']).decode()[1:-2] | |
since_date = datetime(*map(int, date_pattern.match(since_date_str).groups())) | |
packages = [] | |
for log in sorted(glob('/var/log/dpkg.log*'), reverse=True): | |
if log.endswith('.gz'): | |
with gzip.open(log, 'rt') as f: | |
lines = f.read().splitlines() | |
else: | |
with open(log, 'r') as f: | |
lines = f.read().splitlines() | |
for line in lines: | |
cols = line.split() | |
line_date = datetime(*map(int, date_pattern.match(' '.join(cols[:2])).groups())) | |
if line_date >= since_date: | |
# keep only installed ones | |
if cols[2] == 'install': | |
packages.append(cols[3]) | |
print(splitter.join(uniquify(packages))) | |
if print_date: | |
print("\n" + since_date.ctime() + "") | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment