Bringing YUM History to APT: A Simple Solution for Debian/Ubuntu Users
If you’ve ever worked with Red Hat-based systems, you’ve probably fallen in love with yum history – that handy command that lets you see exactly what packages were installed, when, and even rollback changes. It’s one of those features that makes package management feel civilized.
But switch to a Debian or Ubuntu system, and suddenly you’re left wondering: “What did I install last week?” or “When did I add that sketchy PPA package?”
While APT keeps detailed logs in /var/log/apt/history.log, parsing these manually is about as fun as debugging shell scripts at 2 AM. That’s why I created apt-history – a simple bash script that brings YUM-style history management to APT-based systems.
What It Does
The script transforms those cryptic APT logs into something actually useful:
bash
# See your last 10 package transactions apt-history list 10 # Get detailed info about what happened in transaction 5 apt-history info 5 # Find when you installed that package you can't remember apt-history search firefox # Get a nice summary of your package management activity apt-history summary
#!/bin/bash
# apt-history - A yum history alternative for apt-based systems
# Usage: apt-history [command] [options]
VERSION="1.0"
SCRIPT_NAME="apt-history"
# Color codes
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Help function
show_help() {
cat << EOF
$SCRIPT_NAME v$VERSION - APT History Manager (yum history alternative)
USAGE:
$SCRIPT_NAME [COMMAND] [OPTIONS]
COMMANDS:
list Show all installation/removal transactions
list [N] Show last N transactions
info [ID] Show detailed info about transaction ID
summary Show installation summary statistics
installed [date] Show packages installed on specific date (YYYY-MM-DD)
removed [date] Show packages removed on specific date (YYYY-MM-DD)
search [package] Search for package in history
timeline Show installation timeline
rollback-info [ID] Show what would be needed to rollback to transaction ID
OPTIONS:
-h, --help Show this help message
-v, --version Show version
--no-color Disable colored output
--reverse Show in reverse chronological order
EXAMPLES:
$SCRIPT_NAME list 10 # Show last 10 transactions
$SCRIPT_NAME info 5 # Show details of transaction 5
$SCRIPT_NAME installed 2024-01-15 # Show packages installed on Jan 15, 2024
$SCRIPT_NAME search firefox # Search for firefox in history
$SCRIPT_NAME timeline # Show installation timeline
EOF
}
# Check if colors should be disabled
if [[ "$*" == *"--no-color"* ]]; then
RED=''
GREEN=''
YELLOW=''
BLUE=''
NC=''
fi
# Parse transaction ID from log entry
get_transaction_id() {
local line_num=$1
echo "$(printf "%03d" $line_num)"
}
# Get formatted date
format_date() {
local date_str="$1"
if [[ "$date_str" =~ ^Start-Date:\ (.+)$ ]]; then
echo "${BASH_REMATCH[1]}"
else
echo "$date_str"
fi
}
# List transactions
list_transactions() {
local limit=$1
local reverse_flag=""
if [[ "$*" == *"--reverse"* ]]; then
reverse_flag="tac"
else
reverse_flag="cat"
fi
echo -e "${BLUE}ID${NC} ${GREEN}Date/Time${NC} ${YELLOW}Command${NC}"
echo "----------------------------------------------------------------"
local counter=1
# Process all apt history files
for log_file in /var/log/apt/history.log*; do
if [[ -f "$log_file" ]]; then
if [[ "$log_file" == *.gz ]]; then
zcat "$log_file"
else
cat "$log_file"
fi
fi
done | grep -E "^Start-Date|^Commandline" | paste - - | $reverse_flag | while read -r line; do
if [[ -n "$limit" && $counter -gt $limit ]]; then
break
fi
start_date=$(echo "$line" | cut -f1 | sed 's/Start-Date: //')
command=$(echo "$line" | cut -f2 | sed 's/Commandline: //')
printf "${BLUE}%03d${NC} ${GREEN}%-25s${NC} ${YELLOW}%s${NC}\n" $counter "$start_date" "$command"
((counter++))
done
}
# Show transaction info
show_transaction_info() {
local target_id=$1
if [[ -z "$target_id" ]]; then
echo -e "${RED}Error: Transaction ID required${NC}"
return 1
fi
local counter=1
local found=false
# Process all apt history files
for log_file in /var/log/apt/history.log*; do
if [[ -f "$log_file" ]]; then
if [[ "$log_file" == *.gz ]]; then
zcat "$log_file"
else
cat "$log_file"
fi
fi
done | awk '
/^Start-Date:/ {
if (entry) print entry;
entry = $0;
next
}
/^End-Date:/ {
entry = entry "\n" $0;
print entry "\n---";
entry = ""
}
{
if (entry) entry = entry "\n" $0
}
END {
if (entry) print entry
}' | while read -r -d '---' transaction; do
if [[ $counter -eq $target_id ]]; then
echo -e "${BLUE}Transaction ID: $target_id${NC}"
echo "$transaction" | while IFS= read -r line; do
if [[ "$line" =~ ^Start-Date: ]]; then
echo -e "${GREEN}Start Date:${NC} ${line#Start-Date: }"
elif [[ "$line" =~ ^End-Date: ]]; then
echo -e "${GREEN}End Date:${NC} ${line#End-Date: }"
elif [[ "$line" =~ ^Commandline: ]]; then
echo -e "${GREEN}Command:${NC} ${line#Commandline: }"
elif [[ "$line" =~ ^Install: ]]; then
echo -e "${GREEN}Installed:${NC}"
echo "${line#Install: }" | tr ',' '\n' | sed 's/^ */ /'
elif [[ "$line" =~ ^Remove: ]]; then
echo -e "${RED}Removed:${NC}"
echo "${line#Remove: }" | tr ',' '\n' | sed 's/^ */ /'
elif [[ "$line" =~ ^Upgrade: ]]; then
echo -e "${YELLOW}Upgraded:${NC}"
echo "${line#Upgrade: }" | tr ',' '\n' | sed 's/^ */ /'
fi
done
found=true
break
fi
((counter++))
done
if [[ "$found" != true ]]; then
echo -e "${RED}Transaction ID $target_id not found${NC}"
return 1
fi
}
# Show summary statistics
show_summary() {
echo -e "${BLUE}APT Transaction Summary${NC}"
echo "======================"
local total_transactions=0
local install_count=0
local remove_count=0
local upgrade_count=0
for log_file in /var/log/apt/history.log*; do
if [[ -f "$log_file" ]]; then
if [[ "$log_file" == *.gz ]]; then
zcat "$log_file"
else
cat "$log_file"
fi
fi
done | while IFS= read -r line; do
if [[ "$line" =~ ^Start-Date: ]]; then
((total_transactions++))
elif [[ "$line" =~ ^Install: ]]; then
local packages=$(echo "${line#Install: }" | tr ',' '\n' | wc -l)
((install_count += packages))
elif [[ "$line" =~ ^Remove: ]]; then
local packages=$(echo "${line#Remove: }" | tr ',' '\n' | wc -l)
((remove_count += packages))
elif [[ "$line" =~ ^Upgrade: ]]; then
local packages=$(echo "${line#Upgrade: }" | tr ',' '\n' | wc -l)
((upgrade_count += packages))
fi
done | tail -4 | {
read total_line
read install_line
read remove_line
read upgrade_line
echo -e "${GREEN}Total Transactions:${NC} $total_transactions"
echo -e "${GREEN}Packages Installed:${NC} $install_count"
echo -e "${RED}Packages Removed:${NC} $remove_count"
echo -e "${YELLOW}Packages Upgraded:${NC} $upgrade_count"
}
}
# Search for package in history
search_package() {
local package_name="$1"
if [[ -z "$package_name" ]]; then
echo -e "${RED}Error: Package name required${NC}"
return 1
fi
echo -e "${BLUE}Search results for: $package_name${NC}"
echo "================================="
local counter=1
for log_file in /var/log/apt/history.log*; do
if [[ -f "$log_file" ]]; then
if [[ "$log_file" == *.gz ]]; then
zcat "$log_file"
else
cat "$log_file"
fi
fi
done | awk '
/^Start-Date:/ {
if (entry) print entry;
entry = $0;
next
}
/^End-Date:/ {
entry = entry "\n" $0;
print entry "\n---";
entry = ""
}
{
if (entry) entry = entry "\n" $0
}
END {
if (entry) print entry
}' | while read -r -d '---' transaction; do
if echo "$transaction" | grep -qi "$package_name"; then
echo -e "${BLUE}Transaction $counter:${NC}"
echo "$transaction" | grep "Start-Date:" | sed "s/Start-Date: / ${GREEN}Date:${NC} /"
echo "$transaction" | grep -i "$package_name" | sed "s/^/ /"
echo
fi
((counter++))
done
}
# Show installation timeline
show_timeline() {
echo -e "${BLUE}Installation Timeline${NC}"
echo "===================="
for log_file in /var/log/apt/history.log*; do
if [[ -f "$log_file" ]]; then
if [[ "$log_file" == *.gz ]]; then
zcat "$log_file"
else
cat "$log_file"
fi
fi
done | grep -E "^Start-Date|^Install:" | paste - - | sort -k2 |
while read -r line; do
date=$(echo "$line" | cut -f1 | sed 's/Start-Date: //')
packages=$(echo "$line" | cut -f2 | sed 's/Install: //' | tr ',' '\n' | wc -l)
echo -e "${GREEN}$date${NC}: $packages packages installed"
done
}
# Main script logic
case "$1" in
"list"|"")
if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then
list_transactions "$2"
else
list_transactions
fi
;;
"info")
show_transaction_info "$2"
;;
"summary")
show_summary
;;
"search")
search_package "$2"
;;
"timeline")
show_timeline
;;
"installed"|"removed")
# These would need more complex implementation
echo -e "${YELLOW}Feature not yet implemented${NC}"
;;
"rollback-info")
echo -e "${YELLOW}Feature not yet implemented${NC}"
;;
"-h"|"--help"|"help")
show_help
;;
"-v"|"--version")
echo "$SCRIPT_NAME v$VERSION"
;;
*)
echo -e "${RED}Unknown command: $1${NC}"
echo "Use '$SCRIPT_NAME --help' for usage information"
exit 1
;;
esac
Why This Matters
Package management history isn’t just academic curiosity – it’s practical system administration. When something breaks after an update, or when you need to audit what’s been installed on a server, having easy access to this information can save hours of detective work.
The script handles all the messy bits: parsing compressed log files, formatting dates properly, and presenting everything with colored output that doesn’t hurt your eyes.
The Best Part
It’s just a single bash script. No dependencies, no installation complexity – just download, make executable, and you’re done. It even handles edge cases like rotated log files and provides helpful error messages when things go wrong.
While it doesn’t implement every YUM history feature (rollbacks are tricky without RPM’s transaction system), it covers the 90% use case of “show me what happened and when.”
Sometimes the best tools are the simple ones that solve a specific problem well. This script won’t win any architecture awards, but it will make your life a little easier – and isn’t that what good software should do?
The full script is available above and works on any modern Debian or Ubuntu system. Drop it in /usr/local/bin/ and start exploring your package history today.