← All scripts

// Bash scripts

Arch Update with AUR Cleanup

Runs a system and AUR update and, along the way, finds orphaned AUR-only packages nobody needs anymore.

Arch LinuxAURPacketmanagement
Version1.2.5
Updated2026-07-06
Tested onArch Linux x86_64
LicenceMIT
Requiresbash 5, pacman, an AUR-helper (default: yay)
Size247 lines · 7.5 KB
SHA256 2c6f60417d45ada6e06f5a7aa4884bcc439528ee35e8499328218f19516907dd Verify after downloading: echo "2c6f60417d45ada6e06f5a7aa4884bcc439528ee35e8499328218f19516907dd arch-update.sh" | sha256sum -c

Read it first, then run it. The complete source is further down on this page. There is deliberately no curl … | bash one-liner here.

Use at your own risk — no liability for data loss or system damage. See the terms of use.

What the script is for

On Arch-based systems, a full update usually happens in two steps: official repos via pacman, AUR packages via a helper like yay. Over time, AUR packages that were pulled in as dependencies pile up and end up unused. Without regularly checking, they just sit there.

What it does

  • Updates system and AUR packages via an AUR helper (default: yay).
  • Finds installed packages that don't come from official repos (presumably AUR).
  • Splits them into "still needed" and "not required by anything anymore"

(Required By: None).

  • Offers to remove the ones that are no longer needed.
  • For the AUR packages still needed, checks whether they can even still be found

(AUR, or meanwhile in official repos) — and warns if not.

  • Runs as a dry run by default; changes only happen with --apply.

How it works

The interesting part is how the script determines "AUR-only": it compares the list of installed foreign packages against what the AUR helper knows as AUR packages.

mapfile -t installed_foreign < <(LC_ALL=C pacman -Qqm | sort)
mapfile -t aur_known < <(LC_ALL=C "$aur_helper" -Slq | sort)
mapfile -t aur_only < <(
  comm -23 \
    <(printf '%s\n' "${installed_foreign[@]}") \
    <(printf '%s\n' "${aur_known[@]}")
)

LC_ALL=C isn't a minor detail here, it's necessary: pacman -Qi only outputs field names like "Required By" consistently in English. If the system is set to a different language, parsing the dependency info would silently produce wrong results — exactly the kind of bug you only notice after something needed has already been removed.

Why I automated this

Because AUR-only packages quietly pile up over time, and otherwise I'd only go through them by hand every once in a while.

Usage

bash arch-update.sh --help
bash arch-update.sh               # dry run, only shows what would happen
bash arch-update.sh --apply       # actually runs the update and cleanup

Source

arch-update.sh
#!/usr/bin/env bash
#
#  arch-update.sh
#  Zweck: System-/AUR-Update anstoßen und verwaiste AUR-only-Pakete finden.
#
#  Autor:   Matthias Meister — https://scripts.web.mm-core.de
#  Version: 1.0.0
#
#  Lizenz:  MIT — Copyright (c) 2026 Matthias Meister
#           Volltext: https://scripts.web.mm-core.de/lizenz/
#
#  Exit-Codes:
#    0 = alles gut
#    1 = Problem gefunden (z. B. AUR-Paket nirgends mehr auffindbar)
#    2 = Aufruffehler (falsche Option, Voraussetzung fehlt)

set -euo pipefail

readonly SCRIPT_NAME="${0##*/}"
readonly VERSION="1.0.0"

# ---- Defaults -----------------------------------------------------------
dry_run=1          # Trockenlauf ist Standard, siehe --apply
do_update=1
do_cleanup=1
do_check=1
use_color=1
aur_helper="yay"
exit_code=0

# ---- Ausgabe --------------------------------------------------------------
setup_colors() {
  if [[ "$use_color" -eq 1 && -t 1 ]]; then
    c_info=$'\033[36m'; c_warn=$'\033[33m'; c_err=$'\033[31m'; c_ok=$'\033[32m'; c_off=$'\033[0m'
  else
    c_info=""; c_warn=""; c_err=""; c_ok=""; c_off=""
  fi
}

info() { printf '%s[Info]%s %s\n' "${c_info}" "${c_off}" "$*"; }
ok()   { printf '%s[OK]%s %s\n'   "${c_ok}"   "${c_off}" "$*"; }
warn() { printf '%s[Warnung]%s %s\n' "${c_warn}" "${c_off}" "$*" >&2; }
err()  { printf '%s[Fehler]%s %s\n'  "${c_err}"  "${c_off}" "$*" >&2; }

usage() {
  cat <<EOF
${SCRIPT_NAME} ${VERSION}
System- und AUR-Update anstoßen, dabei verwaiste AUR-only-Pakete finden
und optional entfernen.

Verwendung:
  ${SCRIPT_NAME} [OPTIONEN]

Optionen:
  --apply                Änderungen wirklich ausführen (Update, Entfernen).
                          Ohne diese Option läuft alles nur als Trockenlauf.
  --dry-run               Nur anzeigen, was passieren würde (Standard).
  --no-update              System-/AUR-Update überspringen.
  --no-cleanup             Suche nach verwaisten AUR-only-Paketen überspringen.
  --no-check               Prüfung noch benötigter AUR-Pakete überspringen.
  --aur-helper=PROGRAMM     AUR-Helfer verwenden (Standard: yay).
  --no-color                Ausgabe ohne Farbcodes (z. B. für Cron/Logs).
  -h, --help                 Diese Hilfe anzeigen.
  --version                    Version anzeigen.

Beispiele:
  ${SCRIPT_NAME} --help
  ${SCRIPT_NAME}                          # zeigt nur, was passieren würde
  ${SCRIPT_NAME} --apply                  # führt Update und Aufräumen wirklich aus
  ${SCRIPT_NAME} --apply --no-update       # nur aufräumen, kein Systemupdate
  ${SCRIPT_NAME} --aur-helper=paru --apply

Exit-Codes:
  0 = alles gut
  1 = Problem gefunden (z. B. AUR-Paket nirgends mehr auffindbar)
  2 = Aufruffehler
EOF
}

# ---- Argumente --------------------------------------------------------
parse_args() {
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --apply) dry_run=0 ;;
      --dry-run) dry_run=1 ;;
      --no-update) do_update=0 ;;
      --no-cleanup) do_cleanup=0 ;;
      --no-check) do_check=0 ;;
      --aur-helper=*) aur_helper="${1#*=}" ;;
      --no-color) use_color=0 ;;
      -h|--help) usage; exit 0 ;;
      --version) printf '%s %s\n' "$SCRIPT_NAME" "$VERSION"; exit 0 ;;
      *)
        err "Unbekannte Option: $1"
        usage
        exit 2
        ;;
    esac
    shift
  done
}

# ---- Voraussetzungen ----------------------------------------------------
check_prerequisites() {
  if ! command -v pacman >/dev/null 2>&1; then
    err "pacman wurde nicht gefunden. Dieses Skript ist für Arch-basierte Systeme gedacht."
    exit 2
  fi

  if [[ "$do_update" -eq 1 || "$do_cleanup" -eq 1 || "$do_check" -eq 1 ]]; then
    if ! command -v "$aur_helper" >/dev/null 2>&1; then
      err "AUR-Helfer '${aur_helper}' wurde nicht gefunden."
      err "Installieren oder anderen Helfer mit --aur-helper=PROGRAMM angeben."
      exit 2
    fi
  fi
}

# ---- System-/AUR-Update --------------------------------------------------
do_system_update() {
  info "Aktualisiere System- und AUR-Pakete mit '${aur_helper} -Syu' ..."
  if [[ "$dry_run" -eq 1 ]]; then
    info "Trockenlauf: würde ausführen: ${aur_helper} -Syu"
    return 0
  fi
  "$aur_helper" -Syu
  ok "Update abgeschlossen."
}

# ---- Verwaiste AUR-only-Pakete finden ------------------------------------
# Vergleicht installierte "fremde" Pakete (nicht in offiziellen Repos, also
# vermutlich aus dem AUR) mit dem, was der AUR-Helfer als AUR-Paketliste
# kennt. LC_ALL=C erzwingt englische, stabil parsbare pacman-Ausgabe -
# sonst hängt das Parsen von "Required By" von der Systemsprache ab.
find_aur_only_packages() {
  local -a installed_foreign aur_known aur_only
  mapfile -t installed_foreign < <(LC_ALL=C pacman -Qqm | sort)

  if [[ "${#installed_foreign[@]}" -eq 0 ]]; then
    info "Keine fremden (nicht-offiziellen) Pakete installiert."
    return 0
  fi

  mapfile -t aur_known < <(LC_ALL=C "$aur_helper" -Slq | sort)
  mapfile -t aur_only < <(
    comm -23 \
      <(printf '%s\n' "${installed_foreign[@]}") \
      <(printf '%s\n' "${aur_known[@]}")
  )

  if [[ "${#aur_only[@]}" -eq 0 ]]; then
    info "Keine AUR-only-Pakete gefunden."
    return 0
  fi

  info "AUR-only-Pakete gefunden (${#aur_only[@]}):"
  printf '  %s\n' "${aur_only[@]}"

  local -a unneeded=() needed=()
  local pkg required_by
  for pkg in "${aur_only[@]}"; do
    required_by=$(LC_ALL=C pacman -Qi "$pkg" | awk -F': ' '/^Required By/ {print $2; exit}')
    if [[ "$required_by" == "None" ]]; then
      unneeded+=("$pkg")
    else
      needed+=("$pkg")
    fi
  done

  if [[ "$do_cleanup" -eq 1 ]]; then
    cleanup_unneeded "${unneeded[@]}"
  fi
  if [[ "$do_check" -eq 1 ]]; then
    check_needed "${needed[@]}"
  fi
}

# ---- Nicht mehr benötigte AUR-Pakete entfernen ---------------------------
cleanup_unneeded() {
  local -a unneeded=("$@")
  if [[ "${#unneeded[@]}" -eq 0 ]]; then
    info "Keine unbenötigten AUR-Pakete (ohne Required By)."
    return 0
  fi

  info "Unbenötigte AUR-Pakete (von nichts abhängig):"
  printf '  %s\n' "${unneeded[@]}"

  if [[ "$dry_run" -eq 1 ]]; then
    info "Trockenlauf: würde entfernen mit: pacman -Rns ${unneeded[*]}"
    return 0
  fi

  # pacman -Rns fragt selbst noch einmal interaktiv nach - das bleibt als
  # zweite Sicherheitsstufe zusätzlich zu --apply bestehen.
  sudo pacman -Rns "${unneeded[@]}"
  ok "Entfernt: ${unneeded[*]}"
}

# ---- Noch benötigte AUR-Pakete prüfen ------------------------------------
check_needed() {
  local -a needed=("$@")
  if [[ "${#needed[@]}" -eq 0 ]]; then
    info "Keine noch benötigten AUR-Pakete zu prüfen."
    return 0
  fi

  info "Noch benötigte AUR-Pakete - Verfügbarkeit prüfen:"
  local pkg
  for pkg in "${needed[@]}"; do
    local found_aur=0 found_repo=0
    LC_ALL=C "$aur_helper" -Ss "^${pkg}$" 2>/dev/null | grep -q "^aur/${pkg} " && found_aur=1 || true
    LC_ALL=C pacman -Ss "^${pkg}$" 2>/dev/null | grep -q "^extra/${pkg} " && found_repo=1 || true

    if [[ "$found_aur" -eq 1 ]]; then
      ok "  ${pkg}: weiterhin im AUR verfügbar."
    elif [[ "$found_repo" -eq 1 ]]; then
      ok "  ${pkg}: mittlerweile in offiziellen Repos verfügbar."
    else
      warn "  ${pkg}: weder im AUR noch in offiziellen Repos gefunden."
      warn "  -> GitHub prüfen oder manuell neu bauen."
      exit_code=1
    fi
  done
}

main() {
  parse_args "$@"
  setup_colors
  check_prerequisites

  if [[ "$dry_run" -eq 1 ]]; then
    info "Trockenlauf aktiv (Standard). Für echte Änderungen: --apply"
  fi

  if [[ "$do_update" -eq 1 ]]; then
    do_system_update
  fi

  if [[ "$do_cleanup" -eq 1 || "$do_check" -eq 1 ]]; then
    find_aur_only_packages
  fi

  exit "$exit_code"
}

main "$@"
Ad