Compresser les pages man et info

Les programmes de lecture de pages man et peuvent traiter de manière transparente des pages compressées avec gzip et bzip, fonctionnalité intéressante pour gagner en espace disque tout en conservant votre documentation. Néanmoins, les choses ne sont pas aussi simple : les répertoires man ont tendance à contenir des liens, physiques et symboliques, qui empêchent les idées simples comme l'appel récursif de gzip. Une meilleur façon de faire est d'utiliser le script ci-dessous.

cat > /usr/bin/compressdoc << "EOF"
#!/bin/bash
# VERSION: 20031029.0025
#
# Compresse (avec bzip2 ou gzip) toutes les pages man dans un ensemble de
# répertoires et met à jour les liens symboliques - Par Marc Heerdink <marc @ koelkast.net>
# Modifiez pour être capable de compresser les fichiers avec gzip ou bzip2
# suivant une option et pour gérer tous les liens symboliques proprement par
# Mark Hymers <markh @ linuxfromscratch.org>
#
# Modifié 20030930 par Yann E. Morin <yann.morin.1998 @ anciens.enib.fr>
# pour accepter la compression/décompression, pour gérer correctement les liens
# physiques, pour permettre la modification de liens physiques en liens
# symboliques, pour spécifier le niveau de compression, pour analyser man.conf
# pour toutes les occurrences de MANPATH, pour permettre une sauvegarde, pour
# autoriser la conservation de la version la plus récente d'une page.
#
# TODO:
#        - choisir une méthode de compression par défaut suivant la
#          disponibilité des outils : gzip ou bzip2;
#        - offrir une option pour choisir automatiquement la meilleure méthode
#          de compression sur une base page par page (c'est-à-dire, vérifier
#          lequel des outils de compression, entre gzip/bzip2/autre, est le plus
#          performant, et ceci page par page);
#        - lorsque la variable d'environnement MANPATH existe, l'utilisez plutôt
#          que /etc/man.conf (utile pour les utilisateurs souhaitant
#          (dé)compresser leurs man pages;
#        - offrir une option pour restaurer une sauvegarde précédente;
#        - ajouter d'autres outils de compression (compress, zip, etc?).
#          Nécessaire?

# Assez logiquement, cette fonction affiche de l'aide.
function help ()
{
  if [ -n "$1" ]; then
    echo "Option inconnue : $1"
  fi
  ( echo "Usage: $0 <méthode_compression> [options] [dirs]" && \
  cat << EOT
Où méthode_compression est :
  --gzip, --gz, -g
  --bzip2, --bz2, -b
                Compresse en utilisant gzip ou bzip2.

  --decompress, -d
		Décompresse les pages man.

  --backup      Spécifie qu'une sauvegarde .tar doit être faire pour chaque
		répertoire.
		Au cas où une sauvegarde existe déjà, elle est sauvegardée dans
		.tar.old avant de créer la nouvelle sauvegarde. Si une
		sauvegarde .tar.old existe, elle est supprimée avant de
		sauvegarder l'ancienne sauvegarde. En mode sauvegarde, aucune
		autre action n'est effectuée.

Et où les options sont :
  -1 to -9, --fast, --best
                Le niveau de compression, telle que gérée par gzip et bzip2. Si
		elle n'est pas spécifiée, utilise le niveau de compression par
		défaut de la méthode donnée (-6 pour gzip, et -9 pour bzip2).
		Inutilisée en mode sauvegarde et en mode décompression.

  --force, -F   Force la (re-)compression, même si l'ancien utilisait la même
		méthode. Utile lors d'un changement de niveau de compression.
		Par défaut, une page ne sera pas re-compressée si elle se
		termine avec le même suffixe que la méthode utilisée
                (.bz2 pour bzip2, .gz pour gzip).

  --soft, -S    Modifie les liens physiques en liens symboliques. A utiliser
		avec précaution car le premier fichier rencontré sera utilisé
		comme référence. Inutilisée en mode sauvegarde.

  --hard, -H    Modifie les liens symboliques en liens physiques. Inutilisée en
		mode sauvegarde.

  --conf=dir, --conf dir
		Spécifie l'emplacement de man.conf. Par défaut, /etc.

  --verbose, -v Mode verbeux, affiche le nom du répertoire en cours de
		traitement. Doublez l'option pour qu'elle soit encore plus
		verbeuse et pour qu'elle affiche le nom du fichier en cours de
		traitement.

  --fake, -f    Mode émulation. Affiche les paramètres réels que compman
		utilisera.

  dirs          Une liste de chemins absolus séparés par des espaces menant aux
		répertoires man.
                Si vide, et seulement dans ce cas, analyse ${MAN_CONF}/man.conf
		pour toutes les occurrences de MANPATH.

Note sur la compression
  Il y a eu une discussion sur blfs-support concernant les niveaux de
  compression de gzip et bzip2 sur les pages man, en prenant en compte le
  système de fichiers hôte, l'architecture, etc... En résumé, la conclusion
  était que gzip était plus efficace sur les 'petits' fichiers, que bzip2
  l'était sur les 'gros' fichiers, petit et gros dépendant beaucoup du contenu
  des fichiers.

  Voir le message original de Mickael A. Peters, intitulé "Bootable Utility CD",
  et daté de 20030409.1816(+0200), ainsi que les messages consécutifs:
  http://linuxfromscratch.org/pipermail/blfs-support/2003-April/038817.html

  Sur mon système (x86, ext3), les pages man faisaient 35564kiB avant compression. gzip -9
  les a compressé pour arriver à 20372kiB (57,28%), bzip2 -9 arrivait à 19812kiB
  (55,71%). Cela représente un gain de 1,57%. YMMV.

  Ce qui n'a pas été pris en considération est le temps de décompression. Mais
  cela a-t'il aussi un sens ? Vous gagnez en rapidité d'accès avec des pages man
  non compressées ou vous gagnez de l'espace disque contre un léger
  contre-temps. En fait, mon P4-2.5GHz ne me permet même pas de l'apprécier... :-)
EOT
) | less
}

# Cette fonction vérifie que la page man est unique parmi les versions bzip2,
# gzip et non compressés.
#  $1 le répertoire où réside le fichier
#  $2 le nom du fichier de la page man
# Renvoit 0 (true) si le fichier est le dernier et doit être pris en
# considération et 1 (false) si le fichier n'est pas le dernier (et a donc été
# supprimé).
function check_unique ()
{
  # NB. Lorsqu'il y a des liens physiques vers ce fichier, ils ne sont _pas_
  # supprimés. En fait, si ce sont des liens physiques, ils ont tous la même
  # date/heure, les préparant à la suppression plus tard.

  # Construit la liste de toutes les pages man de même nom
  DIR=$1
  BASENAME=`basename "${2}" .bz2`
  BASENAME=`basename "${BASENAME}" .gz`
  GZ_FILE="$BASENAME".bz2
  BZ_FILE="$BASENAME".bz2

  # Recherche, et conserve, le plus récent
  LATEST=`(cd "$DIR"; ls -1rt "${BASENAME}" "${GZ_FILE}" "${BZ_FILE}" 2>/dev/null | tail -1)`
  for i in "${BASENAME}" "${GZ_FILE}" "${BZ_FILE}"; do
    [ "$LATEST" != "$i" ] && rm -f "$DIR"/"$i"
  done

  # Au cas où le fichier spécifié est le dernier, renvoit 0
  [ "$LATEST" = "$2" ] && return 0
  # Si le fichier n'est pas le dernier, renvoit 1
  return 1
}

# OK, analyse les arguments de la ligne de commande et initialise à un état
# particulier : ne pas modifier les liens, analyser /etc/man.conf, être le plus
# silencieux, rechercher man.conf dans /etc et ne pas forcer la (re-)compression.
COMP_METHOD=
COMP_SUF=
COMP_LVL=
FORCE_OPT=
LN_OPT=
MAN_DIR=
VERBOSE_LVL=0
BACKUP=no
FAKE=no
MAN_CONF=/etc
while [ -n "$1" ]; do
  case $1 in
    --gzip|--gz|-g)
      COMP_SUF=.gz
      COMP_METHOD=$1
      shift
      ;;
    --bzip2|--bz2|-b)
      COMP_SUF=.bz2
      COMP_METHOD=$1
      shift
      ;;
    --decompress|-d)
      COMP_SUF=
      COMP_LVL=
      COMP_METHOD=$1
      shift
      ;;
    -[1-9]|--fast|--best)
      COMP_LVL=$1
      shift
      ;;
    --force|-F)
      FORCE_OPT=-F
      shift
      ;;
    --soft|-S)
      LN_OPT=-S
      shift
      ;;
    --hard|-H)
      LN_OPT=-H
      shift
      ;;
    --conf=*)
      MAN_CONF=`echo $1 | cut -d '=' -f2-`
      shift
      ;;
    --conf)
      MAN_CONF="$2"
      shift 2
      ;;
    --verbose|-v)
      let VERBOSE_LVL++
      shift
      ;;
    --backup)
      BACKUP=yes
      shift
      ;;
    --fake|-f)
      FAKE=yes
      shift
      ;;
    --help|-h)
      help
      exit 0
      ;;
    /*)
      MAN_DIR="${MAN_DIR} ${1}"
      shift
      ;;
    -*)
      help $1
      exit 1
      ;;
    *)
      echo "\"$1\" n'est pas un chemin absolu" 
      exit 1
      ;;
  esac
done

# Redirections
case $VERBOSE_LVL in
  0)
     # O, être silencieux
     DEST_FD0=/dev/null
     DEST_FD1=/dev/null
     VERBOSE_OPT=
     ;;
  1)
     # 1, être un peu verbeux
     DEST_FD0=/dev/stdout
     DEST_FD1=/dev/null
     VERBOSE_OPT=-v
     ;;
  *)
     # 2 et au-dessus, être très verbeux
     DEST_FD0=/dev/stdout
     DEST_FD1=/dev/stdout
     VERBOSE_OPT="-v -v"
     ;;
esac

# Note: sur ma machine, 'man --path' donne /usr/share/man deux fois, une fois
# avec un '/' en fin, une fois sans.
if [ -z "$MAN_DIR" ]; then
  MAN_DIR=`man --path -C "$MAN_CONF"/man.conf \
            | sed 's/:/\\n/g' \
            | while read foo; do dirname "$foo"/.; done \
            | sort -u \
            | while read bar; do echo -n "$bar "; done`
fi

# Si aucun MANPATH dans ${MAN_CONF}/man.conf, annuler tout
if [ -z "$MAN_DIR" ]; then
echo "Aucun répertoire spécifié et aucun répertoire trouvé avec \`man --path'"
  exit 1
fi

# Faux?
if [ "$FAKE" != "no" ]; then
  echo "Paramètres utilisés:"
  echo -n "Compression........: "
  case $COMP_METHOD in
    --bzip2|--bz2|-b) echo -n "bzip2";;
    --gzip|__gz|-g) echo -n "gzip";;
    --decompress|-d) echo -n "décompression";;
    *) echo -n "unknown";;
  esac
  echo " ($COMP_METHOD)"
  echo "Niveau de compression.: $COMP_LVL"
  echo "Suffixe de compression: $COMP_SUF"
  echo -n "Forcer la compression: "
  [ "foo$FORCE_OPT" = "foo-F" ] && echo "yes" || echo "no"
  echo "man.conf est..........: ${MAN_CONF}/man.conf"
  echo -n "Hard-links............: "
  [ "foo$LN_OPT" = "foo-S" ] && echo "convert to soft-links" || echo "leave as is"
  echo -n "Liens symboliques.....: "
  [ "foo$LN_OPT" = "foo-H" ] && echo "convert to hard-links" || echo "leave as is"
  echo "Sauvegarde............: $BACKUP"
  echo "Faux (oui!)...........: $FAKE"
  echo "Répertoires...........: $MAN_DIR"
  echo "Niveau de verbosité...: $VERBOSE_LVL"
  exit 0
fi

# Si aucune méthode n'a été spécifiée, affichez l'aide
if [ -z "${COMP_METHOD}" -a "${BACKUP}" = "no" ]; then
  help
  exit 1
fi

# En mode sauvegarde, faire uniquement la sauvegarde
if [ "$BACKUP" = "yes" ]; then
  for DIR in $MAN_DIR; do
    cd "${DIR}/.."
    DIR_NAME=`basename "${DIR}"`
    echo "Sauvegarde de $DIR..." > $DEST_FD0
    [ -f "${DIR_NAME}.tar.old" ] && rm -f "${DIR_NAME}.tar.old"
    [ -f "${DIR_NAME}.tar" ] && mv "${DIR_NAME}.tar" "${DIR_NAME}.tar.old"
    tar cfv "${DIR_NAME}.tar" "${DIR_NAME}" > $DEST_FD1
  done
  exit 0
fi

# Je sais que MAN_DIR n'a que des noms de chemins absolus
# Je dois prendre en considération les pages man localisées, donc je deviens
# récursif
for DIR in $MAN_DIR; do
  MEM_DIR=`pwd`
  cd "$DIR"
  for FILE in *; do
    # Corrige le cas où le répertoire est vide
    if [ "foo$FILE" = "foo*" ]; then continue; fi

    # Corrige le cas où les liens symboliques voient leur schéma de compression
    # changé (de non compressé à compressé, ou de bz2 à gz, ou de gz à bz2)
    # Corrige aussi le cas où plusieurs versions de la page sont présentes,
    # compressées ou non.
    if [ ! -L "$FILE" -a ! -e "$FILE" ]; then continue; fi

    # Ne compresse pas les fichiers whatis
    if [ "$FILE" = "whatis" ]; then continue; fi

    if [ -d "$FILE" ]; then
      cd "${MEM_DIR}"  # Retourne en arrière où nous avons lancé "$0", au cas où "$0"=="./compressdoc" ...
      # Nous devenons récursif pour ce répertoire
      echo "-> Entering ${DIR}/${FILE}..." > $DEST_FD0
      # Je ne dois pas passé --conf, car je spécifie le répertoire de travail
      # Mais je dois sortir en cas d'erreur
      "$0" ${COMP_METHOD} ${COMP_LVL} ${LN_OPT} ${VERBOSE_OPT} ${FORCE_OPT} "${DIR}/${FILE}" || exit 1
      echo "<- Sortie de ${DIR}/${FILE}." > $DEST_FD1
      cd "$DIR"  # Nécessaire pour la prochaine itération de la boucle

    else # !dir
      if ! check_unique "$DIR" "$FILE"; then continue; fi

      # Vérifie si le fichier est déjà compressé avec la méthode spécifiée
      BASE_FILE=`basename "$FILE" .gz`
      BASE_FILE=`basename "$FILE" .bz2`
      if [ "${FILE}" = "${BASE_FILE}${COMP_SUF}" -a "foo${FORCE_OPT}" = "foo" ]; then continue; fi

      # Si nous avons un lien symbolique
      if [ -h "$FILE" ]; then
        case "$FILE" in
          *.bz2)
            EXT=bz2 ;;
          *.gz)
            EXT=gz ;;
          *)
            EXT=none ;;
        esac

        if [ ! "$EXT" = "none" ]; then
          LINK=`ls -l "$FILE" | cut -d ">" -f2 | tr -d " " | sed s/\.$EXT$//`
          NEWNAME=`echo "$FILE" | sed s/\.$EXT$//`
          mv "$FILE" "$NEWNAME"
          FILE="$NEWNAME"
        else
          LINK=`ls -l "$FILE" | cut -d ">" -f2 | tr -d " "`
        fi

        if [ "$LN_OPT" = "-H" ]; then
          # Modifie le lien symbolique en lien physique
          rm -f "$FILE" && ln "${LINK}$COMP_SUF" "${FILE}$COMP_SUF"
          chmod --reference "${LINK}$COMP_SUF" "${FILE}$COMP_SUF"
        else
          # Modifie le lien physique en lien symbolique
          rm -f "$FILE" && ln -s "${LINK}$COMP_SUF" "${FILE}$COMP_SUF"
        fi
        echo "Modification du lien $FILE" > $DEST_FD1

      # Sinon, nous avons un fichier standard
      elif [ -f "$FILE" ]; then
        # Prenons en considération les liens physiques: construire la liste des
	# liens physiques allant sur le fichier que nous sommes en train de
	# {dé,}compresser.
        # NB. Ceci n'est pas optimum car le fichier sera éventuellement
	# compressé autant de fois qu'il a de liens compressés. Mais, pour
	# l'instant, c'est le moyen le plus sûr.
        inode=`ls -li "$FILE" | awk '{print $1}'`
        HLINKS=`find . \! -name "$FILE" -inum $inode`

        if [ -n "$HLINKS" ]; then
	  # Nous avons de liens physiques! A supprimer maintenant.
          for i in $HLINKS; do rm -f "$i"; done
        fi

	# Maintenant, occupons-nous du fichier qui n'a pas de liens physiques
	# Nous décompressons avant de re-compresser avec le niveau de
	# compression sélectionné précédemment...
        case "$FILE" in
          *.bz2)
            bunzip2 $FILE
            FILE=`basename "$FILE" .bz2`
          ;;
          *.gz)
            gunzip $FILE
            FILE=`basename "$FILE" .gz`
          ;;
        esac

	# Compresse le fichier avec le taux de compression indiqué si nécessaire
        case $COMP_SUF in
          *bz2)
            bzip2 ${COMP_LVL} "$FILE" && chmod 644 "${FILE}${COMP_SUF}"
	    echo "$FILE compressé " > $DEST_FD1
            ;;
          *gz)
            gzip ${COMP_LVL} "$FILE" && chmod 644 "${FILE}${COMP_SUF}"
            echo "$FILE compressé " > $DEST_FD1
            ;;
          *)
            echo "$FILE non compressé " > $DEST_FD1
            ;;
        esac

	# Si le fichier a des liens physiques, nous devons les recréer (soit en
	# physique soit en symbolique)
        if [ -n "$HLINKS" ]; then
          for i in $HLINKS; do
            NEWFILE=`echo "$i" | sed s/\.gz$// | sed s/\.bz2$//`
            if [ "$LN_OPT" = "-S" ]; then
  	      # Modifie ce lien symbolique en lien physique
              ln -s "${FILE}$COMP_SUF" "${NEWFILE}$COMP_SUF"
            else
  	      # Modifie ce lien physique en lien symbolique
              ln "${FILE}$COMP_SUF" "${NEWFILE}$COMP_SUF"
            fi
            chmod 644 "${NEWFILE}$COMP_SUF" # Really work only for hard-links. Harmless for soft-links
          done
        fi

      else
        # Il reste un problème où nous n'avons ni un lien symbolique ni un lien
	# physique
        # Evidemment, nous ne devrions jamais arriver là... :-(
	echo "Whaooo... \"${DIR}/${FILE}\" n'est ni un lien symbolique ni un
	lien physique. Merci de vérifier:"
        ls -l "${DIR}/${FILE}"
        exit 1
      fi
    fi
  done # for FILE
done # for DIR
EOF
chmod 755 /usr/bin/compressdoc

Maintenant, en tant qu'utilisateur root, vous pouvez lancer /usr/bin/compressdoc --bz2 pour compresser toutes les pages man de votre système. Vous pouvez aussi lancer /usr/bin/compressdoc --help pour obtenir une aide compréhensible sur ce que le script est capable de faire.

N'oubliez que certains programmes, comme le système X Window, XEmacs, installent aussi leur documentation dans des emplacements non standard (tels que /usr/X11R6/man, etc...). N'oubliez pas d'ajouter ces emplacements dans le fichier /etc/man.conf, comme une section MANPATH=/path.

Exemple:


    ...
    MANPATH=/usr/share/man
    MANPATH=/usr/local/man
    MANPATH=/usr/X11R6/man
    MANPATH=/opt/qt/doc/man
    ...

Habituellement, les systèmes d'installation de packages ne compressent pas les pages man/info, ce qui signifie que vous aurez besoin de lancer le script de nouveau su vous souhaitez conserver la taille de votre documentation le plus bas possible. De même, notez que lancer le script après avoir mis à jour un package est sûr : quand vous avez plusieurs versions d'une page (par exemple, une compressée et une non compressée), la plus récente est conservée et l'autre est supprimée.