cClaude.rocks ☕ Le blog

[Nouvelles technologies, sciences et coups de gueule…]

Menu

La commande df fournit la quantitĂ© d’espace occupĂ© des systĂšmes de fichiers, mais l’affichage n’est pas trĂšs pratique pour les scripts.

Présentation de la commande df

L’usage typique Ă©tant :

df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/root        29G  8.0G   20G  30% /
devtmpfs        454M     0  454M   0% /dev
tmpfs           487M     0  487M   0% /dev/shm
tmpfs           487M   50M  438M  11% /run
tmpfs           5.0M  4.0K  5.0M   1% /run/lock
tmpfs           487M     0  487M   0% /sys/fs/cgroup
/dev/mmcblk0p1  253M   49M  204M  20% /boot
tmpfs            98M     0   98M   0% /run/user/1000

L’option -p permet d’avoir un affichage plus prĂ©dictible sur six colonnes, mais avec une prĂ©sentation moins sympa.

df -Ph

ඏ

df au format JSON

À partir de cette Ă©criture, il est possible d’opĂ©rer avec jq une analyse du rĂ©sultat et reformater cela format JSON.

df -Ph | jq --raw-input --slurp '[ split("\n") | .[] | if test("^/") then gsub(" +"; " ") | split(" ") | {filesystem: .[0], total: .[1], used: .[2], avail: .[3], percent: .[4], mount: .[5]} else empty end ]'
  • Explications

    Voici le code sur plusieurs lignes, c’est clair, non ?

    df -Ph |
      jq --raw-input --slurp '[
      split("\n") |
      .[] |
      if test("^/") then
        gsub(" +"; " ") |
        split(" ") |
        {filesystem: .[0], total: .[1], used: .[2], avail: .[3], percent: .[4], mount: .[5]}
      else
        empty
      end
    ]'
    

    Détaillons encore :

    • df -Ph : C’est la commande initiale que l’on va analyser, notez que vous devez garde l’option -P, mais vous pouvez par exemple utiliser les unitĂ©s du systĂšme international --si en remplaçant par df -P --si.
    • | : indique que le rĂ©sultat sera utilisĂ© comme valeur d’entrĂ©e pour la commande suivante.
    • jq --raw-input --slurp 
 : la commande jq et ces paramĂštres

    Détail des paramÚtres de la commande jq :

    • --raw-input ou -R : Indique que ce qui est pris en entrĂ©e n’est pas du JSON mais du texte brut,
    • --slurp ou-s : Indique qu’il faut faire un tableau avec ce qu’on prend en entrĂ©e.
    • Ce qui se trouve entre ' 
 ' est le code jq qui sera exĂ©cutĂ©.

    Détail du code jq :

    [
      split("\n") |
      .[] |
      if test("^/") then
        gsub(" +"; " ") |
        split(" ") |
        {filesystem: .[0], total: .[1], used: .[2], avail: .[3], percent: .[4], mount: .[5]}
      else
        empty
      end
    ]
    
    • [ : Commence le tableau des rĂ©sultats.
    • split("\n") : met chaque ligne dans une chaĂźne de caractĂšre, cela construit au passage un nouveau tableau .
    • | : passe le rĂ©sultat Ă  la commande suivante.
    • .[] : traite Ă©lĂ©ment par Ă©lĂ©ment (donc ligne par ligne).
    • | : passe le rĂ©sultat Ă  la commande suivante.
    • if CONDITION then RESULTAT_VRAI else RESULTAT_FAUX end : en fonction d’une CONDITION on continue le traitement soit en gĂ©nĂ©rant RESULTAT_VRAI, soit en gĂ©nĂ©rant RESULTAT_FAUX.
      • CONDITION test("^/") : si la chaĂźne (donc la ligne) commence par ^ le caractĂšre / c’est vrai, sinon c’est faux.
      • RESULTAT_VRAI gsub(" +"; " ") | split(" ") | {filesystem: .[0], total: .[1], used: .[2], avail: .[3], percent: .[4], mount: .[5]} : dĂ©tail plus bas.
      • RESULTAT_FAUX empty : on ajoute rien
    • ] : Termine le tableau des rĂ©sultats.

    Détail du cas RESULTAT_VRAI qui est effectué sur chaque ligne :

    gsub(" +"; " ") |
    split(" ") |
    {filesystem: .[0], total: .[1], used: .[2], avail: .[3], percent: .[4], mount: .[5]}
    
    • gsub(" +"; " ") : Supprime tous les espaces en double, il ne restera plus qu’un seul espace entre les zones de texte.
    • | : passe le rĂ©sultat Ă  la commande suivante.
    • split(" ") : Construit un tableau contenant chaque zone de texte (donc on attend un tableau avec 6 Ă©lĂ©ments (indices de 0 Ă  5).
    • | : passe le rĂ©sultat Ă  la commande suivante.
    • {filesystem: .[0], total: .[1], used: .[2], avail: .[3], percent: .[4], mount: .[5]} : Construit l’élĂ©ment JSON attendu.

ඏ

Nettoyons les résultats qui ne sont pas nécessaires

On a déjà « nettoyé » quelques valeurs puisque les lignes ne commençant par / ont été ignorées, correspondant à des périphériques virtuels dont la taille, nous importe peu ainsi que la premiÚre ligne. Il reste encore de périphériques de type /dev/loop qui sont des artefacts permettant de rendre des fichiers ordinaires visibles en tant que périphériques de bloc (des disques). Ils sont généralement utilisés pour monter des images de disque (pour plus de détail : Loop device sur Wikipédia).

df -Ph | jq -R -s '[ split("\n") | .[] | if (test("^/") and (test("^/dev/loop") | not)) then gsub(" +"; " ") | split(" ") | {filesystem: .[0], total: .[1], used: .[2], avail: .[3], percent: .[4], mount: .[5]} else empty end ]'

Il reste encore le cas du caractùre % qui n’est pas un nombre facile à traiter, on va donc supprimer le caractùre et remplacer la chaüne par un nombre, comme suit :

df -Ph | jq -R -s '[ split("\n") | .[] | if (test("^/") and (test("^/dev/loop") | not)) then gsub(" +"; " ") | split(" ") | {filesystem: .[0], total: .[1], used: .[2], avail: .[3], percent: (.[4] | sub("%"; "") | tonumber), mount: .[5]} else empty end ]'
  • Vue dĂ©taillĂ©e
    df -Ph |
      jq -R -s '[
      split("\n") |
      .[] |
      if (test("^/") and (test("^/dev/loop") | not)) then
        gsub(" +"; " ") |
        split(" ") |
        {filesystem: .[0], total: .[1], used: .[2], avail: .[3], percent: (.[4] | sub("%"; "") | tonumber), mount: .[5]}
      else
        empty
      end
    ]'
    
  • RĂ©sultat
    [
      {
        "filesystem": "/dev/root",
        "total": "29G",
        "used": "8.0G",
        "avail": "20G",
        "percent": 30,
        "mount": "/"
      },
      {
        "filesystem": "/dev/mmcblk0p1",
        "total": "253M",
        "used": "49M",
        "avail": "204M",
        "percent": 20,
        "mount": "/boot"
      }
    ]
    

Si vous préférez avoir un résultat utilisant le systÚme métrique international, le changement est mineur :

df -P --si | jq -R -s '[ split("\n") | .[] | if (test("^/") and (test("^/dev/loop") | not)) then gsub(" +"; " ") | split(" ") | {filesystem: .[0], total: .[1], used: .[2], avail: .[3], percent: (.[4] | sub("%"; "") | tonumber), mount: .[5]} else empty end ]'

L’utilisation du systĂšme mĂ©trique international aura l’avantage de simplifier vos calculs, puisqu’on est sur une base 1000 et non plus 1024.


ඏ

Adaptation avec le type de systÚme de fichier :

Petit souci cette fois, car il va falloir tenir compte d’une colonne supplĂ©mentaire :

df -P --si -T | jq -R -s '[ split("\n") | .[] | if (test("^/") and (test("^/dev/loop") | not)) then gsub(" +"; " ") | split(" ") | {filesystem: .[0], type: .[1], total: .[2], used: .[3], avail: .[4], percent: (.[5] | sub("%"; "") | tonumber), mount: .[6]} else empty end ]'

Ce format reste Ă  mon sens insatisfaisant, car on se retrouve avec des chaĂźnes du type "31G", "8.6G", "265M", "51M" qui ne sont pas simples Ă  comparer.

Une solution consiste Ă  utiliser l’option -k pour tout passer en « kilo-bytes » et comme nous avons maintenant des valeurs numĂ©rique, il est conseille de changer de type.

df -P --si -T -k | jq -R -s '[ split("\n") | .[] | if (test("^/") and (test("^/dev/loop") | not)) then gsub(" +"; " ") | split(" ") | {filesystem: .[0], type: .[1], total: (.[2] | tonumber), used: (.[3] | tonumber) , avail: (.[4] | tonumber), percent: (.[5] | sub("%"; "") | tonumber), mount: .[6]} else empty end ]'

Ce qui donne :

[
  {
    "filesystem": "/dev/root",
    "type": "ext4",
    "total": 29609436,
    "used": 8315500,
    "avail": 20058496,
    "percent": 30,
    "mount": "/"
  },
  {
    "filesystem": "/dev/mmcblk0p1",
    "type": "vfat",
    "total": 258095,
    "used": 49243,
    "avail": 208853,
    "percent": 20,
    "mount": "/boot"
  }
]

Cependant, c’est clairement moins lisible par un ĂȘtre humain.

Considérations pour l'automatisation

Lors des tests, j’ai constatĂ© que la commande df pouvait ĂȘtre assez lente lorsque des disques rĂ©seaux sont montĂ©s.

L’usage de --exclude-type=cifs ou -x cifs est donc à envisager, en fonction de vos besoins.

df -P --si -T -k -x cifs -x tmpfs -x squashfs | jq -R -s '[ split("\n") | .[] | if (test("^/") and (test("^/dev/loop") | not)) then gsub(" +"; " ") | split(" ") | {filesystem: .[0], type: .[1], total: (.[2] | tonumber), used: (.[3] | tonumber) , avail: (.[4] | tonumber), percent: (.[5] | sub("%"; "") | tonumber), mount: .[6]} else empty end ]'
  • Vue dĂ©taillĂ©e
    df -P --si -T -k -x cifs -x tmpfs -x squashfs |
      jq -R -s '[
      split("\n") |
      .[] |
      if (test("^/") and (test("^/dev/loop") | not)) then
        gsub(" +"; " ") |
        split(" ") |
        {filesystem: .[0], type: .[1], total: (.[2] | tonumber), used: (.[3] | tonumber) , avail: (.[4] | tonumber), percent: (.[5] | sub("%"; "") | tonumber), mount: .[6]}
      else
        empty
      end
    ]'
    

ඏ

Avoir la version utilisable pour les calculs et lisible rapidement par un humain

C’est possible, mais c’est un peu lourd Ă  Ă©crire en gardant la solution en ligne de commande.

  • L’idĂ©e gĂ©nĂ©rale est la suivante
    df -P --si -T -k -x cifs -x tmpfs -x squashfs | jq -R -s -S '[
      split("\n") |
      .[] |
      if (test("^/") and (test("^/dev/loop") | not)) then
        gsub(" +"; " ") |
        split(" ") |
        {filesystem: .[0], type: .[1], total: (.[2] | tonumber), used: (.[3] | tonumber) , avail: (.[4] | tonumber), percent: (.[5] | sub("%"; "") | tonumber), mount: .[6]}
        | . + {
          "total-human": (
            if .total < 1000 then
              .total | tostring
            elif .total < 1000000 then
              (.total / 1000) | floor | tostring + "MB"
            else
              (.total / 1000000) | floor | tostring + "GB"
            end
          ),
          "used-human": (
            if .used < 1000 then
              .used | tostring
            elif .used < 1000000 then
              (.used / 1000) | floor | tostring + "MB"
            else
              (.used / 1000000) | floor | tostring + "GB"
            end
          ),
          "avail-human": (
            if .avail < 1000 then
              .avail | tostring
            elif .avail < 1000000 then
              (.avail / 1000) | floor | tostring + "MB"
            else
              (.avail / 1000000) | floor | tostring + "GB"
            end
          )
        }
      else
        empty
      end
    ]'
    

L’astuce consiste Ă  reprendre la ligne et Ă  ajouter des champs en utilisant les valeurs prĂ©alablement obtenues cela se fait grĂące Ă  la sĂ©quence | . +.

La version ultime

Vous allez devoir créer un fichier df-to-json.jq avec le contenu suivant :

  • df-to-json.jq
    def convert_number_to_human(n):
      n | if . < 1000 then
            . | tostring
          elif . < 1000000 then
            (./ 1000) | floor | tostring + "MB"
          elif . < 1000000000 then
            (./ 1000000) | floor | tostring + "GB"
          else
            (. / 1000000000) | floor | tostring + "TB"
          end
    ;
    
    def convert_df_to_json:
    [
      split("\n") |
      .[] |
      if (test("^/") and (test("^/dev/loop") | not)) then
        gsub(" +"; " ") |
        split(" ") |
        {filesystem: .[0], type: .[1], total: (.[2] | tonumber), used: (.[3] | tonumber) , avail: (.[4] | tonumber), percent: (.[5] | sub("%"; "") | tonumber), mount: .[6]}
        | . + {
          human: {
            total: convert_number_to_human( .total ),
            used: convert_number_to_human( .used ),
            avail: convert_number_to_human( .avail )
          }
        }
      else
        empty
      end
    ];
    
    convert_df_to_json
    

et vous l’utiliserez comme suit :

df -P --si -T -k -x tmpfs -x squashfs | jq -f 'df-to-json.jq'  -R -s -S

Bien évidement, vous devrez adapter pour tenir compte du chemin réel vers le fichier df-to-json.jq.

Et cela vous donnera quelque chose comme :

  • RĂ©sultat type
    [
      {
        "avail": 160723272,
        "filesystem": "/dev/nvn1p2",
        "human": {
          "avail": "160GB",
          "total": "244GB",
          "used": "71GB"
        },
        "mount": "/",
        "percent": 31,
        "total": 244568380,
        "type": "ext4",
        "used": 71352052
      },
      {
        "avail": 517892,
        "filesystem": "/dev/nvn1p1",
        "human": {
          "avail": "517MB",
          "total": "523MB",
          "used": "5MB"
        },
        "mount": "/boot/efi",
        "percent": 2,
        "total": 523248,
        "type": "vfat",
        "used": 5356
      },
      {
        "avail": 73966904,
        "filesystem": "/dev/sda1",
        "human": {
          "avail": "73GB",
          "total": "960GB",
          "used": "837GB"
        },
        "mount": "/mnt/data",
        "percent": 92,
        "total": 960379920,
        "type": "ext4",
        "used": 837558592
      },
      {
        "avail": 4763213456,
        "filesystem": "//192.168.126.12/Share",
        "human": {
          "avail": "4TB",
          "total": "22TB",
          "used": "18TB"
        },
        "mount": "/media/Share",
        "percent": 80,
        "total": 22886119912,
        "type": "cifs",
        "used": 18122906456
      }
    ]
    

ඏ

Références

኿


â„č 2006 - 2021 | 🏠 Retour Ă  l'accueil du domaine | 🏡 Retour Ă  l'accueil du blog