S.M.A.R.T-Werte einmalig per Skript systematisch erfassen um Festplatten zu verkaufen
Veröffentlicht am 12 November 2026 von oemmes — 12 min
(Ui, htmly unterstützt keine Bildbeschreibung?? Hier: Ein Stapel Festplatten, Planie nach oben zeigend, jede ist zur Platte darunter etwas versetzt/verdreht damit man von allen etwas Platine und die Anschlüsse sieht)
Ich hatte einige Festplatten zu verkaufen und wollte die vorher auf ihre S.M.A.R.T-Werte prüfen.
Die meisten Festplatten sind bei mir verschlüsselt oder sicher gelöscht, aber bei allem was da so im Regal rumliegt, bin ich mir nicht sicher.
Ich benötigte also eine Möglichkeit Festplatten zu prüfen ob die noch OK sind und ob dort noch Daten drauf liegen.
Da ich selbst nicht gut, nicht schnell und nicht gern Skripte schreiben kann, habe ich mir von Claude etwas Vibecoden lassen, das sah etwa so aus (inklusive Tippfehler):
kannst Du mir ein bash script schreiben was festplatten (außer die Systemplatte) auf SMART werte testet und die ausgibt? Hier reicht ein textfile mit Datum, Zeit, Seriennummer, Smart status (laufzeit / defekte sektoren / sonstige auffälligkeiten).
Wenn ich das nochmal ausführe werden die wete angehängt.
Ich habe viele Platten die ich nacheinander testen möchte, aber nur eine 2er Docking für solche platten.
Mien plan:
anstecken skript laufen lassen platten mit ausgegebener liste verkaufen
Danach fiel mir ein, dass eventuell noch Daten drauf sein könnten, daher:
und kannst du das skript verlängern, dass es 15 Minuten lang auf wiederherstellbare daten testet (wie photorec), damit ich bemerke ob ich platten noch nicht sicher gelöscht habe?
Wenn die platte nicht sicher gelöscht wurde, sollten mit 15 Minuten prüfzeit ja ausreichend sektoren geprüft sein um das festzustellen, oder?
Nach dem das Skript erstellt und getestet war, habe ich um eine Readme gebeten:
smart-check.sh
Prüft alle Festplatten außer der Systemplatte per SMART und testet per photorec, ob noch wiederherstellbare Daten darauf liegen (also nicht sicher gelöscht wurde). Nur lesender Zugriff. Gedacht für den Workflow mit einem Dock: Platten anstecken, Skript starten, Platten austauschen.
Voraussetzungen
sudo apt install smartmontools testdisk
Benutzung
sudo ./smart-check.sh # SMART + 15 Min. Datenrest-Scan
sudo ./smart-check.sh -t # zusätzlich SMART-Kurztest
sudo ./smart-check.sh -m 5 # Scan nur 5 Minuten
sudo ./smart-check.sh -m 0 # nur SMART, ohne Scan (ca. 1 Sekunde pro Platte)
| Option | Bedeutung | Standard |
|---|---|---|
| -m MIN | Dauer des Datenrest-Scans (0 = aus) | 15 |
| -t | SMART-Kurztest zusätzlich ausführen | aus |
| -n N | Scan auf N Bereiche der Platte verteilen (1 = nur Anfang) | 4 |
| -x MB | Scan abbrechen, wenn mehr als MB wiederhergestellt wurden | 1024 |
Variablen:
LOGFILE=/pfad/log.txt # anderes Logfile (Standard: smart-log.txt neben dem Skript)
EXTRA_EXCLUDE="sdb sdc" # weitere Platten ausschließen
Ablauf pro Dock-Belegung
- Zwei Platten ins Dock stecken, einschalten, kurz warten, bis sie erkannt sind
(
lsblkzeigt sie). sudo ./smart-check.shstarten. Beide Platten werden parallel geprüft, ein Lauf dauert etwa 15 Minuten.- Ergebnis im Terminal ansehen. Es wird zusätzlich an
smart-log.txtangehängt. - Platten tauschen und wiederholen.
Ergebnis lesen
- SMART-FAZIT: OK, WARNUNG (z. B. defekte oder ausstehende Sektoren, Fehler im Log) oder DEFEKT (SMART meldet Ausfall). Platten mit WARNUNG besser nicht oder nur mit Hinweis verkaufen.
- DATEN-FAZIT:
- NICHT SICHER GELÖSCHT: Es wurden noch Dateien gefunden. Platte vor dem Verkauf vollständig überschreiben (HDD: nwipe, SSD: blkdiscard oder Secure Erase des Herstellers).
- nichts gefunden: nur eine Stichprobe, kein Beweis. Bei sensiblen Daten trotzdem vollständig überschreiben.
- CRC-Fehler sind meist ein Kabel- oder Dock-Problem und kein Plattenfehler.
- "SMART NICHT LESBAR": Das Dock reicht SMART nicht durch. Platte direkt an SATA oder ein anderes Dock hängen.
Hinweise
- Wiederhergestellte Dateien liegen nur kurz in /var/tmp und werden sofort gelöscht. Pro Platte werden maximal 1 GB geschrieben (-x).
- Nach Strg+C räumt das Skript Loop-Geräte und Temp-Dateien selbst auf.
- Automatisch eingehängte Dock-Platten sind kein Problem, es wird nur gelesen.
- Bei 4 Bereichen laufen 4 photorec-Prozesse pro Platte. Das ist bei HDDs etwas langsamer als ein Scan am Stück, erfasst dafür aber mehr der Platte.!
Hier das Script:
#!/usr/bin/env bash
# smart-check.sh – prüft alle Festplatten (außer der Systemplatte):
# 1. SMART-Werte (Laufzeit, defekte Sektoren, Auffälligkeiten)
# 2. Datenreste: photorec-Scan (Standard 15 Min.), um zu sehen, ob die Platte
# noch wiederherstellbare Dateien enthält, also NICHT sicher gelöscht wurde
# Ergebnisse werden an ein Textfile angehängt. Es wird nichts auf die Platten
# geschrieben (nur lesender Zugriff).
#
# Benutzung: sudo ./smart-check.sh [-m MINUTEN] [-t] [-n BEREICHE] [-x MAX_MB]
# -m MIN Dauer des Datenrest-Scans in Minuten (Standard 15, 0 = überspringen)
# -t zusätzlich SMART-Kurztest (short self-test) ausführen
# -n N Scan verteilt auf N Bereiche der Platte (Standard 4, 1 = nur Anfang)
# -x MB Abbruch, wenn pro Platte mehr als MB wiederhergestellt wurden (Standard 1024)
#
# Logfile: standardmäßig smart-log.txt neben dem Skript
# anderes Ziel: sudo LOGFILE=/pfad/log.txt ./smart-check.sh
# Zusätzlich ausschließen: sudo EXTRA_EXCLUDE="sdc sdd" ./smart-check.sh
# Voraussetzung: sudo apt install smartmontools testdisk
MIN=15; SELFTEST=0; SLICES=4; MAXMB=1024
LOGFILE="${LOGFILE:-$(dirname "$(readlink -f "$0")")/smart-log.txt}"
# Dateitypen mit zuverlässigen Signaturen (wenige Fehltreffer)
TYPES="jpg png gif pdf zip doc 7z rar mov riff mp3 sqlite gz"
usage() { sed -n '2,19p' "$0" | sed 's/^# \{0,1\}//'; }
while getopts "m:tn:x:h" o; do
case $o in
m) MIN=$OPTARG ;;
t) SELFTEST=1 ;;
n) SLICES=$OPTARG ;;
x) MAXMB=$OPTARG ;;
*) usage; exit 1 ;;
esac
done
for v in MIN SLICES MAXMB; do
[[ ${!v} =~ ^[0-9]+$ ]] || { echo "Ungültiger Wert für $v: ${!v}"; exit 1; }
done
(( SLICES < 1 )) && SLICES=1
if [[ $EUID -ne 0 ]]; then echo "Bitte als root starten: sudo $0"; exit 1; fi
command -v smartctl >/dev/null || { echo "smartctl fehlt: sudo apt install smartmontools"; exit 1; }
if (( MIN > 0 )) && ! command -v photorec >/dev/null; then
echo "photorec fehlt: sudo apt install testdisk (oder mit -m 0 ohne Datenrest-Scan starten)"
exit 1
fi
# --- Systemplatte(n) ermitteln (/, /boot, /boot/efi inkl. LVM/LUKS) -----------
sys_disks=" ${EXTRA_EXCLUDE} "
for mp in / /boot /boot/efi; do
src=$(findmnt -no SOURCE "$mp" 2>/dev/null | head -n1 | sed 's/\[.*//')
[[ -b $src ]] || continue
for d in $(lsblk -rnso NAME,TYPE "$src" | awk '$2=="disk"{print $1}'); do
sys_disks+="$d "
done
done
targets=()
while read -r n; do
[[ $sys_disks == *" $n "* ]] && continue
targets+=("$n")
done < <(lsblk -dnro NAME,TYPE | awk '$2=="disk"{print $1}' | grep -Ev '^(loop|zram|ram|nbd|md)')
if (( ${#targets[@]} == 0 )); then
echo "Keine Platte außer der Systemplatte gefunden." | tee -a "$LOGFILE"
exit 0
fi
WORK=$(mktemp -d /var/tmp/smartcheck.XXXXXX)
: > "$WORK/loops.txt"
cleanup() {
trap - EXIT INT TERM
local p
for p in $(jobs -p); do kill "$p" 2>/dev/null; done
pkill -P $$ 2>/dev/null
sleep 1
if [[ -f $WORK/loops.txt ]]; then
while read -r lp; do [[ -n $lp ]] && losetup -d "$lp" 2>/dev/null; done < "$WORK/loops.txt"
fi
rm -rf "$WORK"
}
trap cleanup EXIT
trap 'echo; echo "Abgebrochen."; exit 130' INT TERM
# --- SMART-Hilfsfunktionen ------------------------------------------------------
declare -A DT
detect_dt() { # USB-Docks brauchen oft "-d sat"
local o
o=$(smartctl -i "/dev/$1" 2>&1)
if grep -qi "serial number" <<<"$o"; then DT[$1]=""; return; fi
o=$(smartctl -i -d sat "/dev/$1" 2>&1)
if grep -qi "serial number" <<<"$o"; then DT[$1]="-d sat"; else DT[$1]=""; fi
}
gt0() { [[ -n $1 && $1 =~ ^[0-9]+$ && $1 -gt 0 ]]; }
attr() {
awk -v id="$1" '/^ID#/{f=1;next} f&&NF==0{f=0} f&&$1==id{print $10;exit}' <<<"$out" \
| sed 's/[^0-9].*//'
}
field() { grep -m1 -iE "^($1):" <<<"$out" | sed 's/^[^:]*:[[:space:]]*//'; }
nvme() { field "$1" | sed 's/,//g; s/[^0-9].*//'; }
# --- Datenrest-Scan (photorec) für eine Platte ---------------------------------
PR_CMD="partition_none,fileopt,everything,disable"
for t in $TYPES; do PR_CMD+=",$t,enable"; done
PR_CMD+=",wholespace,search"
any_alive() { local p; for p in "$@"; do kill -0 "$p" 2>/dev/null && return 0; done; return 1; }
scan_disk() {
local name=$1 dev="/dev/$1" base="$WORK/$1"
local size mib=1048576 slice i off lp used limit=0
local -a loops=() pids=()
mkdir -p "$base"
size=$(blockdev --getsize64 "$dev" 2>/dev/null || echo 0)
# Platte in N Bereiche teilen (nur lesende Loop-Geräte), damit nicht nur der
# Plattenanfang geprüft wird
if (( SLICES > 1 && size > SLICES * 1024 * mib )); then
slice=$(( size / SLICES / mib * mib ))
for ((i = 0; i < SLICES; i++)); do
off=$(( i * slice ))
lp=$(losetup -f --show -r -o "$off" --sizelimit "$slice" "$dev" 2>/dev/null)
if [[ -z $lp ]]; then
for lp in "${loops[@]}"; do losetup -d "$lp" 2>/dev/null; done
loops=()
break
fi
echo "$lp" >> "$WORK/loops.txt"
loops+=("$lp")
done
fi
(( ${#loops[@]} == 0 )) && loops=("$dev") # Fallback: nur Plattenanfang
i=0
for lp in "${loops[@]}"; do
mkdir -p "$base/s$i"
TERM=vt100 timeout "$((MIN * 60))" photorec /d "$base/s$i/" /cmd "$lp" "$PR_CMD" >/dev/null 2>&1 &
pids+=($!)
i=$((i + 1))
done
# Platz auf der Systemplatte schützen
while any_alive "${pids[@]}"; do
used=$(du -sm "$base" 2>/dev/null | cut -f1)
if [[ -n $used ]] && (( used > MAXMB )); then
limit=1
kill "${pids[@]}" 2>/dev/null
break
fi
sleep 5
done
wait "${pids[@]}" 2>/dev/null
for lp in "${loops[@]}"; do
[[ $lp == /dev/loop* ]] && losetup -d "$lp" 2>/dev/null
done
local n mb types
n=$(find "$base" -type f ! -name report.xml 2>/dev/null | wc -l)
mb=$(du -sm "$base" 2>/dev/null | cut -f1)
types=$(find "$base" -type f ! -name report.xml 2>/dev/null | sed 's/.*\.//' \
| sort | uniq -c | sort -rn | head -5 | awk '{printf "%s:%s ", $2, $1}')
printf 'R_FILES=%s\nR_MB=%s\nR_LIMIT=%s\nR_SLICES=%s\nR_TYPES="%s"\n' \
"$n" "${mb:-0}" "$limit" "${#loops[@]}" "$types" > "$base.res"
rm -rf "$base" # wiederhergestellte Dateien sofort wieder löschen
}
# --- Ablauf ---------------------------------------------------------------------
{
echo "=================================================================="
echo "Lauf: $(date '+%Y-%m-%d %H:%M:%S') (Systemplatte ausgenommen:${sys_disks})"
echo "=================================================================="
} | tee -a "$LOGFILE"
echo "Gefundene Platten: ${targets[*]}"
for n in "${targets[@]}"; do detect_dt "$n"; done
if (( SELFTEST )); then
echo "Starte SMART-Kurztests ..."
for n in "${targets[@]}"; do smartctl ${DT[$n]} -t short "/dev/$n" >/dev/null 2>&1; done
fi
if (( MIN > 0 )); then
echo "Starte Datenrest-Scan auf allen Platten parallel (${MIN} Min., ${SLICES} Bereiche je Platte) ..."
for n in "${targets[@]}"; do scan_disk "$n" & done
start=$SECONDS
while [[ -n $(jobs -r) ]]; do
sleep 30
left=$(( (MIN * 60 - (SECONDS - start)) / 60 ))
(( left >= 0 )) && echo " ... läuft, noch ca. ${left} Min."
done
wait
fi
if (( SELFTEST )); then
echo "Warte auf Ende der SMART-Kurztests (max. 15 Min.) ..."
for ((w = 0; w < 90; w++)); do
busy=0
for n in "${targets[@]}"; do
smartctl ${DT[$n]} -c "/dev/$n" 2>&1 | grep -qi "in progress" && busy=1
done
(( busy )) || break
sleep 10
done
fi
# --- Berichte -------------------------------------------------------------------
SUMMARY=()
for name in "${targets[@]}"; do
dev="/dev/$name"
out=$(smartctl ${DT[$name]} -a "$dev" 2>&1); rc=$?
now=$(date '+%Y-%m-%d %H:%M:%S')
serial=$(field "Serial Number")
if [[ -z $serial ]]; then
block="[$now] $dev – SMART NICHT LESBAR (Dock/Controller unterstützt es evtl. nicht, oder Platte defekt)"$'\n'
block+="$(echo "$out" | head -n 5 | sed 's/^/ /')"$'\n'
status="UNBEKANNT"; serial="unbekannt"
else
model=$(field "Device Model|Model Number|Product")
cap=$(field "User Capacity|Total NVM Capacity" | sed -n 's/.*\[\(.*\)\].*/\1/p')
health=$(grep -m1 -iE "overall-health|SMART Health Status" <<<"$out" | sed 's/.*: *//')
if grep -q "NVMe" <<<"$out" && grep -q "Power On Hours" <<<"$out"; then
hours=$(nvme "Power On Hours"); realloc="-"; pending="-"; offunc="-"; crc="-"
temp=$(nvme "Temperature"); wear=$(nvme "Percentage Used")
errcount=$(nvme "Media and Data Integrity Errors")
else
hours=$(attr 9); realloc=$(attr 5); pending=$(attr 197)
offunc=$(attr 198); crc=$(attr 199); temp=$(attr 194); wear=""
errcount=$(grep -m1 "^ATA Error Count" <<<"$out" | sed 's/[^0-9]*\([0-9]*\).*/\1/')
fi
status="OK"; notes=()
if [[ $health =~ FAIL ]]; then status="DEFEKT"; notes+=("SMART-Gesamtstatus: FAILED"); fi
if gt0 "$realloc"; then [[ $status == OK ]] && status="WARNUNG"; notes+=("Reallocated Sectors: $realloc"); fi
if gt0 "$pending"; then [[ $status == OK ]] && status="WARNUNG"; notes+=("Pending Sectors: $pending"); fi
if gt0 "$offunc"; then [[ $status == OK ]] && status="WARNUNG"; notes+=("Offline Uncorrectable: $offunc"); fi
if gt0 "$crc"; then notes+=("UDMA-CRC-Fehler: $crc (oft nur Kabel/Dock)"); fi
if gt0 "$errcount"; then [[ $status == OK ]] && status="WARNUNG"; notes+=("Fehler im Error-Log: $errcount"); fi
if grep -E "^# *[0-9]+" <<<"$out" | grep -qiE "failure|failed|error"; then
[[ $status == OK ]] && status="WARNUNG"; notes+=("Fehlgeschlagener Selbsttest im Log")
fi
if gt0 "$wear" && [[ $wear -ge 80 ]]; then notes+=("SSD-Verschleiß: ${wear}%"); fi
if (( rc & 8 )); then status="DEFEKT"; notes+=("smartctl: Platte meldet bevorstehenden Ausfall"); fi
if (( rc & 16 )); then [[ $status == OK ]] && status="WARNUNG"; notes+=("smartctl: Pre-Fail-Attribut unter Schwellwert"); fi
if [[ -n $hours ]]; then
laufzeit="${hours} h ($((hours / 8760)) J $((hours % 8760 / 24)) T)"
else
laufzeit="unbekannt"
fi
block="[$now] $dev Seriennummer: $serial"$'\n'
block+=" Modell: $model ${cap:+($cap)}"$'\n'
block+=" SMART-Status: ${health:-unbekannt}"$'\n'
block+=" Laufzeit: $laufzeit"$'\n'
block+=" Defekte Sektoren: realloc=${realloc:-?} pending=${pending:-?} offline-uncorr=${offunc:-?}"$'\n'
block+=" Sonstiges: CRC-Fehler=${crc:-?} Fehlerlog=${errcount:-0} Temp=${temp:-?}°C"$'\n'
if ((${#notes[@]})); then
block+=" Auffälligkeiten: $(IFS=';'; echo "${notes[*]}" | sed 's/;/; /g')"$'\n'
else
block+=" Auffälligkeiten: keine"$'\n'
fi
block+=" >>> SMART-FAZIT: $status"$'\n'
fi
# Datenreste
sigs=$( { wipefs "$dev" 2>/dev/null | awk 'NR>1{print $3}'; lsblk -rno FSTYPE "$dev" 2>/dev/null; } \
| grep . | sort -u | paste -sd, -)
block+=" Signaturen: ${sigs:-keine Partitionstabelle/Dateisystem erkannt}"$'\n'
datenrest="nicht geprüft"
if (( MIN > 0 )) && [[ -f $WORK/$name.res ]]; then
R_FILES=0; R_MB=0; R_LIMIT=0; R_SLICES=1; R_TYPES=""
# shellcheck disable=SC1090
source "$WORK/$name.res"
if (( R_FILES > 0 )); then
datenrest="NICHT SICHER GELÖSCHT"
block+=" Datenrest-Scan: ${R_FILES} Dateien wiederherstellbar (~${R_MB} MB; ${R_TYPES% })"$'\n'
(( R_LIMIT )) && block+=" (Scan wegen ${MAXMB}-MB-Limit vorzeitig beendet)"$'\n'
else
datenrest="nichts gefunden"
block+=" Datenrest-Scan: keine Dateien gefunden (Stichprobe: ${MIN} Min., ${R_SLICES} Bereich(e); kein Beweis für vollständiges Löschen)"$'\n'
fi
elif (( MIN > 0 )); then
block+=" Datenrest-Scan: kein Ergebnis (Scan fehlgeschlagen?)"$'\n'
fi
block+=" >>> DATEN-FAZIT: $datenrest"$'\n'
block+="------------------------------------------------------------------"
printf '%s\n' "$block" | tee -a "$LOGFILE"
SUMMARY+=("$dev | $serial | SMART: $status | Daten: $datenrest")
done
{
echo "ZUSAMMENFASSUNG $(date '+%Y-%m-%d %H:%M:%S')"
printf ' %s\n' "${SUMMARY[@]}"
echo
} | tee -a "$LOGFILE"
echo "Log: $LOGFILE"