Sometimes you want to quickly identify configuration differences between two hosts.
These could be different settings in configuration files, files that are present on one host but missing on the other, or any number of other differences. The following script helps you detect differences in files and directories between two hosts. Of course, the more files it has to process, the longer it will take.
#!/bin/bash
# Parameter prüfen
if [ "$#" -ne 5 ]; then
echo "Usage: $0 <DATEI|VERZEICHNIS> <REMOTE_HOST1> <REMOTE_USER1> <REMOTE_HOST2> <REMOTE_USER2>"
exit 1
fi
PFAD="$1"
REMOTE_HOST1="$2"
REMOTE_USER1="$3"
REMOTE_HOST2="$4"
REMOTE_USER2="$5"
# Teste SSH-Verbindung zu beiden Hosts
for HOST_USER in "$REMOTE_USER1@$REMOTE_HOST1" "$REMOTE_USER2@$REMOTE_HOST2"; do
echo "[INFO] Testing SSH connection to $HOST_USER..."
if ! ssh -o BatchMode=yes -o ConnectTimeout=5 "$HOST_USER" true 2>/dev/null; then
echo "[ERROR] SSH connection to $HOST_USER failed!"
exit 1
fi
echo "[INFO] SSH connection to $HOST_USER successful."
done
# Vergleichsfunktion für einzelne Dateien
vergleiche_datei() {
remote_datei="$1"
TMP_REMOTE1=$(mktemp)
TMP_REMOTE2=$(mktemp)
TMP_DIFF=$(mktemp)
# Hole und normiere Datei von Host1
if ! ssh "$REMOTE_USER1@$REMOTE_HOST1" test -f "$remote_datei"; then
echo "[MISSING] '$remote_datei' missing on $REMOTE_HOST1"
rm -f "$TMP_REMOTE1" "$TMP_REMOTE2" "$TMP_DIFF"
return
fi
ssh "$REMOTE_USER1@$REMOTE_HOST1" "grep -v '^\s*#' '$remote_datei' | grep -v '^\s*\$' | sed 's/^\s*//;s/\s*\$//' | sort" > "$TMP_REMOTE1"
# Hole und normiere Datei von Host2
if ! ssh "$REMOTE_USER2@$REMOTE_HOST2" test -f "$remote_datei"; then
echo "[MISSING] '$remote_datei' missing on $REMOTE_HOST2"
rm -f "$TMP_REMOTE1" "$TMP_REMOTE2" "$TMP_DIFF"
return
fi
ssh "$REMOTE_USER2@$REMOTE_HOST2" "grep -v '^\s*#' '$remote_datei' | grep -v '^\s*\$' | sed 's/^\s*//;s/\s*\$//' | sort" > "$TMP_REMOTE2"
if diff -u "$TMP_REMOTE1" "$TMP_REMOTE2" > "$TMP_DIFF"; then
echo "[EQUAL] $remote_datei"
else
echo "[DIFFERENCE] $remote_datei"
echo "============== Difference: $remote_datei =============="
cat "$TMP_DIFF"
echo "======================================================="
fi
rm -f "$TMP_REMOTE1" "$TMP_REMOTE2" "$TMP_DIFF"
}
# Wenn Verzeichnis → rekursiv vergleichen
if ssh "$REMOTE_USER1@$REMOTE_HOST1" test -d "$PFAD"; then
echo "[INFO] Directory mode: Searching '$PFAD' recursively on $REMOTE_HOST1..."
mapfile -t dateien < <(ssh "$REMOTE_USER1@$REMOTE_HOST1" "find '$PFAD' -type f")
for datei in "${dateien[@]}"; do
vergleiche_datei "$datei"
done
elif ssh "$REMOTE_USER1@$REMOTE_HOST1" test -f "$PFAD"; then
vergleiche_datei "$PFAD"
else
echo "[ERROR] '$PFAD' does not exist as file or directory on $REMOTE_HOST1."
exit 1
fi
Alles anzeigen
this will show things like:
bash verg.sh '/etc/hosts' slave1 root slave2 root
[INFO] Testing SSH connection to root@slave1...
[INFO] SSH connection to root@slave1 successful.
[INFO] Testing SSH connection to root@slave2...
[INFO] SSH connection to root@slave2 successful.
[DIFFERENCE] /etc/hosts
============== Difference: /etc/hosts ==============
--- /tmp/tmp.gj8S18rtQp 2025-07-04 12:50:43.569949236 +0200
+++ /tmp/tmp.0hZBYAtcp4 2025-07-04 12:50:46.013860959 +0200
@@ -1,10 +1,10 @@
127.0.0.1 localhost.localdomain localhost
192.168.178.11 slave1 slave1
-192.168.178.11 slave1 slave1
+192.168.178.12 slave2 slave2
192.168.178.12 slave2 slave2
192.168.178.13 slave3 slave3
192.168.178.3 omv omv
-::1 localhost ip6-localhost ip6-loopback slave1 slave1
+::1 localhost ip6-localhost ip6-loopback slave2 slave2
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
=======================================================
Things like /etc/apt/ work too.
bash verg.sh '/etc/apt/' slave1 root slave2 root
[INFO] Testing SSH connection to root@slave1...
[INFO] SSH connection to root@slave1 successful.
[INFO] Testing SSH connection to root@slave2...
[INFO] SSH connection to root@slave2 successful.
[INFO] Directory mode: Searching '/etc/apt/' recursively on slave1...
[DIFFERENCE] /etc/apt/sources.list
============== Difference: /etc/apt/sources.list ==============
--- /tmp/tmp.pw1we59d5e 2025-07-04 11:59:35.964741770 +0200
+++ /tmp/tmp.sJpHnF6GIn 2025-07-04 11:59:38.400653861 +0200
@@ -1,4 +1,3 @@
-deb http://deb.debian.org/debian-security/ bookworm-security main non-free-firmware
deb http://debian.charite.de/debian/ bookworm main contrib non-free non-free-firmware
deb http://debian.charite.de/debian/ bookworm-updates main contrib non-free non-free-firmware
deb-src http://debian.charite.de/debian/ bookworm main contrib non-free non-free-firmware
=======================================================
[EQUAL] /etc/apt/update-post-invoke-success.d/README
[EQUAL] /etc/apt/pre-invoke.d/README
[EQUAL] /etc/apt/update-pre-invoke.d/README
[EQUAL] /etc/apt/trusted.gpg.d/debian-archive-trixie-stable.asc
[EQUAL] /etc/apt/trusted.gpg.d/debian-archive-bookworm-security-automatic.asc
grep: /etc/apt/trusted.gpg.d/openmediavault-archive-keyring.gpg: binary file matches
grep: /etc/apt/trusted.gpg.d/openmediavault-archive-keyring.gpg: binary file matches
[EQUAL] /etc/apt/trusted.gpg.d/openmediavault-archive-keyring.gpg
grep: /etc/apt/trusted.gpg.d/openmediavault-keyring.gpg: binary file matches
grep: /etc/apt/trusted.gpg.d/openmediavault-keyring.gpg: binary file matches
[EQUAL] /etc/apt/trusted.gpg.d/openmediavault-keyring.gpg
grep: /etc/apt/trusted.gpg.d/openmediavault.key.chroot.gpg: binary file matches
grep: /etc/apt/trusted.gpg.d/openmediavault.key.chroot.gpg: binary file matches
[EQUAL] /etc/apt/trusted.gpg.d/openmediavault.key.chroot.gpg
[EQUAL] /etc/apt/trusted.gpg.d/debian-archive-trixie-automatic.asc
[EQUAL] /etc/apt/trusted.gpg.d/openmediavault.key.binary.asc
[EQUAL] /etc/apt/trusted.gpg.d/debian-archive-bookworm-automatic.asc
[EQUAL] /etc/apt/trusted.gpg.d/debian-archive-bullseye-automatic.asc
[EQUAL] /etc/apt/trusted.gpg.d/debian-archive-bullseye-stable.asc
[EQUAL] /etc/apt/trusted.gpg.d/debian-archive-bookworm-stable.asc
[EQUAL] /etc/apt/trusted.gpg.d/debian-archive-bullseye-security-automatic.asc
[EQUAL] /etc/apt/trusted.gpg.d/debian-archive-trixie-security-automatic.asc
[EQUAL] /etc/apt/post-invoke.d/20fix-start-stop-daemon
[EQUAL] /etc/apt/post-invoke.d/README
[EQUAL] /etc/apt/listchanges.conf.d/95openmediavault.conf
[EQUAL] /etc/apt/listchanges.conf.d/98openmediavault-mail.conf
[EQUAL] /etc/apt/sources.list~
[EQUAL] /etc/apt/sources.list.d/openmediavault-kernel-backports.list
[EQUAL] /etc/apt/sources.list.d/openmediavault.list
[EQUAL] /etc/apt/sources.list.d/openmediavault-os-security.list
[EQUAL] /etc/apt/sources.list.d/omvextras.list
[EQUAL] /etc/apt/sources.list.d/openmediavault-local.list
[EQUAL] /etc/apt/preferences.d/proxmox.pref
[EQUAL] /etc/apt/preferences.d/openmediavault-local.pref
[EQUAL] /etc/apt/preferences.d/omvextras.pref
[EQUAL] /etc/apt/preferences.d/openmediavault-kernel-backports.pref
[EQUAL] /etc/apt/listchanges.conf
[EQUAL] /etc/apt/apt.conf.d/01autoremove
[EQUAL] /etc/apt/apt.conf.d/00trustcdrom
[EQUAL] /etc/apt/apt.conf.d/20listchanges
[EQUAL] /etc/apt/apt.conf.d/20auto-upgrades
[EQUAL] /etc/apt/apt.conf.d/70debconf
[EQUAL] /etc/apt/apt.conf.d/00CDMountPoint
[EQUAL] /etc/apt/apt.conf.d/99openmediavault-apt-hooks
[EQUAL] /etc/apt/apt.conf.d/98openmediavault-periodic-custom
[EQUAL] /etc/apt/apt.conf.d/99openmediavault-localrepository
[EQUAL] /etc/apt/apt.conf.d/95openmediavault-periodic
[EQUAL] /etc/apt/apt.conf.d/99openmediavault-norecommends
[EQUAL] /etc/apt/apt.conf.d/95openmediavault-unattended-upgrades
[EQUAL] /etc/apt/apt.conf.d/99openmediavault-mkaptidx
[EQUAL] /etc/apt/apt.conf.d/98openmediavault-unattended-upgrade-mail
[EQUAL] /etc/apt/apt.conf.d/99dpkgnotify
[EQUAL] /etc/apt/apt.conf.d/00recommends
[EQUAL] /etc/apt/apt.conf.d/99openmediavault-nosuggests
[EQUAL] /etc/apt/apt.conf.d/95openmediavault-unattended-upgrade
[EQUAL] /etc/apt/update-post-invoke.d/README