Skip to content

Instantly share code, notes, and snippets.

@brahimmachkouri
Created November 15, 2025 17:15
Show Gist options
  • Select an option

  • Save brahimmachkouri/761867dd75bbf52b45e8871efad4fa4f to your computer and use it in GitHub Desktop.

Select an option

Save brahimmachkouri/761867dd75bbf52b45e8871efad4fa4f to your computer and use it in GitHub Desktop.
Crée une archive tar.xz compressée de manière sécurisée en utilisant la compression parallèle XZ
#!/usr/bin/env bash
# cpxs.sh — tar -> xz parallèle, safe by default
# Usage: ./cxps.sh -s /chemin/du/repertoire [-o sortie.tar.xz] [-t threads] [-l level] [-e exclude] [-p] [-v] [-F] [-h]
# Voir exemples en bas.
set -euo pipefail
IFS=$'\n\t'
prog=$(basename "$0")
usage() {
cat <<EOF
$prog — Compression tar.xz parallèle (sécurisée)
Options :
-s DIR Répertoire source à archiver (obligatoire)
-o FILE Archive de sortie (défaut: <basename>_YYYYMMDD.tar.xz)
-t THREADS Threads pour xz. 0 = auto (défaut: 0)
-l LEVEL Niveau xz (1..9 ou e). Défaut: 6
-e PATTERN Exclure motif (peut être répété)
-p Afficher progression via pv (pv requis)
-v Vérifier l'archive après création
-F Force : autorise l'écrasement de la sortie existante
-h Aide
Exemples :
$prog -s /data/projet -o projet.tar.xz -t 0 -l 7 -p -v
$prog -s . -e '.git' -e 'node_modules' -o backup_$(date +%F).tar.xz
Note : le script NE SUPPRIME PAS le répertoire source.
EOF
exit 1
}
# Defaults
SRC=""
OUT=""
THREADS=0
LEVEL=6
EXCLUDES=()
SHOW_PV=0
VERIFY=0
FORCE=0
while getopts ":s:o:t:l:e:p vFh" opt; do
case "$opt" in
s) SRC=$OPTARG ;;
o) OUT=$OPTARG ;;
t) THREADS=$OPTARG ;;
l) LEVEL=$OPTARG ;;
e) EXCLUDES+=("$OPTARG") ;;
p) SHOW_PV=1 ;;
v) VERIFY=1 ;;
F) FORCE=1 ;;
h) usage ;;
\?) echo "Option invalide: -$OPTARG" >&2; usage ;;
:) echo "Option -$OPTARG requiert un argument." >&2; usage ;;
esac
done
if [ -z "$SRC" ]; then
echo "Erreur: répertoire source requis (-s)." >&2
usage
fi
if [ ! -e "$SRC" ]; then
echo "Erreur: source introuvable: $SRC" >&2
exit 2
fi
# Default output name
if [ -z "$OUT" ]; then
base=$(basename "$SRC")
OUT="${base}_$(date +%Y%m%d).tar.xz"
fi
# Resolve absolute paths (realpath/readlink -f fallback)
realpath_cmd() {
if command -v realpath >/dev/null 2>&1; then
realpath "$1"
else
readlink -f "$1"
fi
}
SRC_ABS=$(realpath_cmd "$SRC")
OUT_DIR=$(realpath_cmd "$(dirname "$OUT")")
OUT_ABS="$OUT_DIR/$(basename "$OUT")"
# Prevent output inside source
case "$OUT_DIR/" in
"$SRC_ABS/"* )
echo "Erreur : le fichier de sortie se trouve dans le répertoire source. Choisis un emplacement externe." >&2
exit 3
;;
esac
# Prevent overwrite unless forced
if [ -e "$OUT_ABS" ] && [ "$FORCE" -ne 1 ]; then
echo "Erreur : '$OUT_ABS' existe déjà. Supprime-le ou passe -F pour forcer l'écrasement." >&2
exit 4
fi
# Tools check
command -v tar >/dev/null 2>&1 || { echo "tar manquant"; exit 5; }
command -v xz >/dev/null 2>&1 || { echo "xz manquant"; exit 6; }
# pv optional only if requested
if [ "$SHOW_PV" -eq 1 ]; then
command -v pv >/dev/null 2>&1 || { echo "pv requis pour -p : installe 'pv' ou relance sans -p." >&2; exit 7; }
fi
XZ_BIN=$(command -v xz)
PXZ_BIN=$(command -v pxz || true)
# detect xz --threads support
XZ_SUPPORTS_THREADS=0
if "$XZ_BIN" --help 2>&1 | grep -Eq -- '--threads| -T'; then
XZ_SUPPORTS_THREADS=1
fi
# Estimate size (for pv and disk check)
EST_SIZE=""
if du -sb "$SRC" >/dev/null 2>&1; then
EST_SIZE=$(du -sb "$SRC" | awk '{print $1}')
fi
# Disk space check on output directory: need at least EST_SIZE (or 10% margin)
if [ -n "$EST_SIZE" ]; then
avail_bytes=$(df --output=avail -B1 "$OUT_DIR" | tail -n1)
# conservative margin: need EST_SIZE (uncompressed) + 5%
need=$(( EST_SIZE + EST_SIZE/20 ))
if [ "$avail_bytes" -lt "$need" ]; then
echo "Erreur: espace insuffisant dans $OUT_DIR (disponible: $avail_bytes, requis estimé: $need)." >&2
exit 8
fi
fi
# Build tar exclude args
TAR_EXCLUDE_ARGS=()
for p in "${EXCLUDES[@]}"; do
TAR_EXCLUDE_ARGS+=(--exclude="$p")
done
# Choose compressor array (as array to avoid eval)
COMPRESS_CMD=()
if [ "$XZ_SUPPORTS_THREADS" -eq 1 ]; then
if [ "$THREADS" -eq 0 ]; then
THREADS_ARG="--threads=0"
else
THREADS_ARG="--threads=$THREADS"
fi
COMPRESS_CMD=( "$XZ_BIN" "$THREADS_ARG" -"$LEVEL" -c )
else
if [ -n "$PXZ_BIN" ]; then
COMPRESS_CMD=( "$PXZ_BIN" -"$LEVEL" -c )
else
COMPRESS_CMD=( "$XZ_BIN" -"${LEVEL}" -c )
fi
fi
echo "Source : $SRC_ABS"
echo "Sortie : $OUT_ABS"
echo "Threads : $THREADS (0=auto)"
echo "Level : $LEVEL"
[ "${#EXCLUDES[@]}" -gt 0 ] && echo "Exclu : ${EXCLUDES[*]}"
[ "$SHOW_PV" -eq 1 ] && echo "Progression: activée (pv)"
[ "$VERIFY" -eq 1 ] && echo "Vérification: activée"
[ "$FORCE" -eq 1 ] && echo "Force overwrite : activée"
# Prepare tmpfile in same dir for atomic move
tmpdir="$OUT_DIR"
tmpfile="$(mktemp "${tmpdir}/$(basename "$OUT").tmp.XXXXXX")"
trap 'rc=$?; [ -f "$tmpfile" ] && rm -f "$tmpfile"; exit $rc' INT TERM EXIT
# Construct tar command (use -C dirname basename to avoid full paths)
TAR_CMD=(tar -cf - "${TAR_EXCLUDE_ARGS[@]}" -C "$(dirname "$SRC")" "$(basename "$SRC")")
# Run pipeline
if [ "$SHOW_PV" -eq 1 ] && [ -n "$EST_SIZE" ]; then
echo "Lancement : tar -> pv -> xz -> tmpfile"
"${TAR_CMD[@]}" | pv -s "$EST_SIZE" | "${COMPRESS_CMD[@]}" > "$tmpfile"
elif [ "$SHOW_PV" -eq 1 ]; then
echo "Lancement : tar -> pv -> xz -> tmpfile (taille inconnue)"
"${TAR_CMD[@]}" | pv | "${COMPRESS_CMD[@]}" > "$tmpfile"
else
echo "Lancement : tar -> xz -> tmpfile"
"${TAR_CMD[@]}" | "${COMPRESS_CMD[@]}" > "$tmpfile"
fi
# Move atomically to final destination (overwrite if forced)
mv -f "$tmpfile" "$OUT_ABS"
# remove trap cleanup (success)
trap - INT TERM EXIT
echo "Compression terminée : $OUT_ABS"
# SHA256
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$OUT_ABS" > "$OUT_ABS.sha256"
elif command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$OUT_ABS" > "$OUT_ABS.sha256"
fi
echo "SHA256 enregistré : $OUT_ABS.sha256"
# Vérification optionnelle
if [ "$VERIFY" -eq 1 ]; then
echo "Test xz..."
if ! "$XZ_BIN" -t "$OUT_ABS"; then
echo "Erreur : test xz a échoué." >&2
exit 9
fi
echo "Lecture test tar (vérification des headers)..."
if ! "$XZ_BIN" -dc "$OUT_ABS" | tar -tf - >/dev/null; then
echo "Erreur : lecture tar après décompression a échoué." >&2
exit 10
fi
echo "Vérification OK."
fi
echo "Terminé."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment