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 pardf -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
- CONDITION
]
 : 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
- Adapté de la question : Store output diskspace df -h JSON
- Page du manuel pour df
኿