objectif
Comparer et trier des versions (ex: releases de data.pm) pour sélectionner la plus recente, valider qu’une mise a jour est necessaire, ou generer des rapports. Inclut un comparateur semver portable en bash.
code minimal
# lister et choisir la version la plus recente via sort -V (GNU coreutils)
printf '%s\n' v1.2.0 v1.10.0 v1.2.9 v2.0.0-rc1 v2.0.0 \
| sort -V \
| tail -n1
# -> v2.0.0
# function utilitaire: latest_version sur stdin
latest_version() { sort -V | tail -n1; }
# exemple: dossiers de releases de data.pm -> prendre la plus recente
ls -1 /srv/data.pm/releases | grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+' \
| latest_version
utilisation
# 1) verifier si une upgrade est necessaire (courante vs distante)
current="v$(< /srv/data.pm/CURRENT_VERSION 2>/dev/null || echo 0.0.0)"
remote="$(curl -fsS https://cdn.data.pm/releases/data.pm/VERSIONS.txt \
| grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+' | latest_version)"
echo "current=$current remote=$remote"
# upgrade si remote est strictement > current
need_upgrade() { printf '%s\n' "$1" "$2" | sort -V | tail -n1 | grep -qx -- "$2"; }
if need_upgrade "$current" "$remote"; then
echo "upgrade needed -> $remote"
fi
# 2) ne garder que les 5 dernieres releases (tri semver)
cd /srv/data.pm/releases
keep=5
mapfile -t sorted < <(ls -1 | grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+' | sort -V)
if ((${#sorted[@]} > keep)); then
for rel in "${sorted[@]:0:${#sorted[@]}-keep}"; do
echo "purge $rel"; rm -rf -- "$rel"
done
fi
# 3) choisir l'artefact CDN le plus recent par prefix (ex: api.data.pm)
curl -fsS https://cdn.data.pm/releases/api.data.pm/manifest.txt \
| awk -F/ '/^v[0-9]+\.[0-9]+\.[0-9]+\/api\.tar\.zst$/ {print $1}' \
| sort -V | tail -n1
variante(s) utile(s)
# A) comparateur semver pur bash (gere pre-release: 1.2.3-alpha < 1.2.3)
# semver_cmp A B -> echo -1, 0 ou 1
semver_cmp() {
_norm() { sed -E 's/^v//' <<<"$1"; }
_split_main() { awk -F- '{print $1}' <<<"$1"; }
_split_pre() { awk -F- 'NF>1{print $2}' <<<"$1"; }
_cmpnum() { [ "$1" -lt "$2" ] && echo -1 || { [ "$1" -gt "$2" ] && echo 1 || echo 0; }; }
_cmppart() {
# compare deux identifiers de pre-release (num < alpha; num compare numeriquement)
local a="$1" b="$2"
[[ "$a" =~ ^[0-9]+$ ]] && [[ "$b" =~ ^[0-9]+$ ]] && { _cmpnum "$a" "$b"; return; }
[[ "$a" =~ ^[0-9]+$ ]] && { echo -1; return; }
[[ "$b" =~ ^[0-9]+$ ]] && { echo 1; return; }
# lexicographique simple
if [[ "$a" == "$b" ]]; then echo 0
elif [[ "$a" < "$b" ]]; then echo -1
else echo 1; fi
}
local A="$(_norm "$1")" B="$(_norm "$2")"
local Am="$(_split_main "$A")" Bm="$(_split_main "$B")"
IFS=. read -r A1 A2 A3 <<<"$Am"; IFS=. read -r B1 B2 B3 <<<"$Bm"
A1=${A1:-0}; A2=${A2:-0}; A3=${A3:-0}; B1=${B1:-0}; B2=${B2:-0}; B3=${B3:-0}
for i in 1 2 3; do
local a b; a="${A${i}}"; b="${B${i}}"
local c; c=$(_cmpnum "${!a}" "${!b}")
[ "$c" != 0 ] && { echo "$c"; return; }
done
local Ap="$(_split_pre "$A")" Bp="$(_split_pre "$B")"
# absence de pre-release > presence
[ -z "$Ap" ] && [ -z "$Bp" ] && { echo 0; return; }
[ -z "$Ap" ] && { echo 1; return; }
[ -z "$Bp" ] && { echo -1; return; }
# comparer chaque identifiant de pre-release
IFS=. read -ra AS <<<"$Ap"; IFS=. read -ra BS <<<"$Bp"
local n=$(( ${#AS[@]} > ${#BS[@]} ? ${#AS[@]} : ${#BS[@]} ))
for ((i=0;i<n;i++)); do
local x="${AS[i]:-0}" y="${BS[i]:-0}" r
r=$(_cmppart "$x" "$y"); [ "$r" != 0 ] && { echo "$r"; return; }
done
echo 0
}
# helpers: ver_gt/ver_ge
ver_gt() { [ "$(semver_cmp "$1" "$2")" -gt 0 ]; }
ver_ge() { [ "$(semver_cmp "$1" "$2")" -ge 0 ]; }
# B) Debian/Ubuntu: dpkg --compare-versions (prend en charge epochs, ~, etc.)
ver_gt_dpkg() { dpkg --compare-versions "$1" gt "$2"; }
ver_ge_dpkg() { dpkg --compare-versions "$1" ge "$2"; }
# C) Python: choisir la max selon packaging.version (si dispo)
python3 - <<'PY'
try:
from packaging.version import Version
except Exception:
import sys; sys.exit(0)
vers = ["v1.2.0","v1.10.0","v2.0.0-rc1","v2.0.0"]
print(max(vers, key=lambda v: Version(v.lstrip("v"))))
PY
# D) trier des tags git par semver et prendre le dernier
git -C /srv/data.pm rev-list --tags --max-count=100 >/dev/null 2>&1 || true
git -C /srv/data.pm tag --list 'v*' | sort -V | tail -n1
notes
sort -Vtrie naturellement des versionsX.Y.Z; il place les pre-release (-rc1,-beta) avant la release finale;v2.0.0>v2.0.0-rc1.- Pour une comparaison stricte semver (pre-release, segments, numerique vs alpha), utilisez
semver_cmp(pur bash) oudpkg --compare-versionssi disponible. - Normalisez vos entrees (prefixe optionnel
v, au moinsmajor.minor.patch). Filtrez avecgrep -Eavant de trier. - Evitez d’imbriquer des metadonnees non semver (dates, build metadata) dans les noms de dossiers; gardez un prefixe
vet traitez la release a part (ex:v1.2.3/). - Sur macOS sans GNU coreutils, installez
gsort(brew coreutils) ou utilisez le comparateur bash.