Chapitre 816 minDébutant
Fichiers, CSV et JSON : Import-Csv, ConvertFrom-Json et jq
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- manipuler fichiers et dossiers en PowerShell ;
- lire et produire un CSV sans perdre les accents ni le délimiteur ;
- lire et écrire du JSON avec
ConvertFrom-JsonetConvertTo-Json; - traiter un CSV avec
awket un JSON avecjqen Bash.
4 exercices corrigés
Un script d'administration passe son temps à lire et à écrire des fichiers : des journaux en texte brut, des listes de comptes en CSV (des valeurs séparées par un délimiteur, une ligne par enregistrement) et des configurations ou des réponses d'API en JSON (des objets imbriqués écrits en texte). PowerShell transforme directement un CSV ou un JSON en objets ; Bash les traite avec cut, awk et l'outil jq. Ce chapitre montre les deux approches, et les pièges d'encodage et de délimiteur qui font perdre des heures.
Les chemins et les fichiers en PowerShell
| Tâche | Commande |
|---|---|
| Construire un chemin sans se tromper de barre oblique | Join-Path $PSScriptRoot 'config.json' |
| Tester l'existence d'un fichier ou d'un dossier | Test-Path 'D:\Exports', Test-Path $f -PathType Leaf (fichier seulement) |
| Le dossier parent, le nom seul | Split-Path $f -Parent, Split-Path $f -Leaf |
| Lister les fichiers d'une arborescence | Get-ChildItem D:\Partages -Recurse -File -Filter *.pdf |
| Créer un dossier (sans erreur s'il existe) | New-Item -Path D:\Exports -ItemType Directory -Force |
| Copier, déplacer, renommer | Copy-Item, Move-Item, Rename-Item |
| Supprimer, d'abord en simulation | Remove-Item D:\Temp\* -Recurse -WhatIf |
Chaque fichier renvoyé par Get-ChildItem est un objet avec Name, FullName, Length (la taille en octets), LastWriteTime, Extension. Les fichiers de plus de 30 jours d'un dossier de journaux, avec leur taille en Mo :
$limite = (Get-Date).AddDays(-30)
Get-ChildItem -Path 'C:\Scripts\Logs' -File |
Where-Object LastWriteTime -lt $limite |
Select-Object Name, LastWriteTime, @{ Name = 'TailleMo'; Expression = { [math]::Round($_.Length / 1MB, 2) } }
Lire et écrire du texte
$lignes = Get-Content -Path 'C:\Scripts\serveurs.txt' # un tableau, une ligne par élément
$texte = Get-Content -Path 'C:\Scripts\config.json' -Raw # une seule chaîne, sauts de ligne compris
Get-Content -Path 'C:\Scripts\Logs\export.log' -Tail 20 # les 20 dernières lignes
Get-Content -Path 'C:\Scripts\Logs\export.log' -Wait # suivre le fichier en direct, comme tail -f
Set-Content -Path rapport.txt -Value 'Première ligne' -Encoding UTF8 # remplace le contenu
Add-Content -Path rapport.txt -Value 'Ligne ajoutée' -Encoding UTF8 # ajoute à la fin
Les fichiers CSV avec Import-Csv et Export-Csv
Un CSV a une première ligne d'en-têtes, puis une ligne par enregistrement. Le délimiteur est souvent le point-virgule en France, car Excel en français utilise la virgule comme séparateur décimal. Voici postes.csv, l'inventaire simplifié de quelques postes de Sorbier :
Nom;Service;OS;RAMGo
PC-0001;Comptabilite;Windows 11;16
PC-0002;Commercial;Windows 10;8
PC-0003;Comptabilite;Windows 11;8
PC-0004;Logistique;Windows 10;4
PC-0005;Direction;Windows 11;32
PC-0006;Logistique;Windows 11;16
Import-Csv transforme chaque ligne en objet dont les propriétés portent le nom des colonnes :
$postes = Import-Csv -Path .\postes.csv -Delimiter ';' -Encoding UTF8
$postes | Where-Object OS -eq 'Windows 10'
Nom Service OS RAMGo
--- ------- -- -----
PC-0002 Commercial Windows 10 8
PC-0004 Logistique Windows 10 4
$postes | Group-Object OS | Select-Object Name, Count
Name Count
---- -----
Windows 11 4
Windows 10 2
Windows 10 n'est plus maintenu depuis le 14 octobre 2025 : ces deux postes sont à migrer. Cette liste est exactement ce que l'on joint à une demande de budget.
Pour écrire un CSV, on envoie des objets à Export-Csv :
$postes | Where-Object OS -eq 'Windows 10' |
Select-Object Nom, Service |
Export-Csv -Path .\a-migrer.csv -Delimiter ';' -NoTypeInformation -Encoding UTF8
-NoTypeInformationévite, dans Windows PowerShell 5.1, une première ligne#TYPE System.Management.Automation.PSCustomObjectqui perturbe Excel. PowerShell 7 ne l'écrit plus par défaut ; le paramètre ne fait pas de mal.-Delimiter ';'produit un fichier qu'Excel en français ouvre directement en colonnes.-UseCultureprend le séparateur des réglages régionaux de la machine.- Pour qu'Excel affiche correctement les accents, le fichier doit avoir un BOM :
-Encoding UTF8en 5.1,-Encoding utf8BOMen 7. -Appendajoute des lignes à un CSV existant, à condition que les colonnes soient les mêmes.
ConvertFrom-Csv et ConvertTo-Csv font la même chose sur du texte en mémoire, sans fichier : pratique pour traiter la sortie d'un ancien programme qui écrit du CSV, comme schtasks /query /fo csv.
Les fichiers JSON
Le JSON (JavaScript Object Notation) représente des objets imbriqués : des paires "clé": valeur entre accolades, des listes entre crochets. C'est le format des API REST (chapitre 10) et un bon format de configuration pour un script. Voici config.json, à côté du script de surveillance des disques :
{
"seuilAttention": 15,
"seuilCritique": 10,
"serveurs": ["srv-ad", "srv-ad2", "srv-fichiers"],
"alerte": { "destinataire": "informatique@sorbier.example" }
}
$config = Get-Content -Path (Join-Path $PSScriptRoot 'config.json') -Raw | ConvertFrom-Json
$config.seuilCritique # 10
$config.serveurs[1] # srv-ad2
$config.alerte.destinataire # informatique@sorbier.example
-Raw est indispensable : sans lui, Get-Content envoie les lignes une par une. Contrairement à un CSV, le JSON garde les types : seuilCritique est bien un nombre.
Dans l'autre sens, ConvertTo-Json transforme des objets en texte JSON :
[pscustomobject]@{ Serveur = 'srv-ad'; LibreGo = 31; Lecteurs = @('C', 'D') } | ConvertTo-Json
{
"Serveur": "srv-ad",
"LibreGo": 31,
"Lecteurs": [
"C",
"D"
]
}
C'est la sortie de PowerShell 7, indentée de deux espaces ; Windows PowerShell 5.1 indente autrement, avec le même contenu. -Compress écrit tout sur une ligne : {"Serveur":"srv-ad","LibreGo":31,"Lecteurs":["C","D"]}.
Le texte et les CSV en Bash
Les outils Linux traitent du texte ligne par ligne. Sur le même postes.csv :
cut -d';' -f1,3 postes.csv # colonnes 1 et 3
tail -n +2 postes.csv # tout sauf la première ligne (l'en-tête)
awk -F';' '$3 == "Windows 10" { print $1 " (" $2 ")" }' postes.csv
PC-0002 (Commercial)
PC-0004 (Logistique)
awk est le couteau suisse des fichiers en colonnes : -F';' fixe le séparateur, $1, $2… désignent les colonnes, NR le numéro de ligne, et un bloc END s'exécute à la fin. Compter les postes par système, et additionner la mémoire :
awk -F';' 'NR > 1 { n[$3]++ } END { for (os in n) print os ": " n[os] }' postes.csv | sort
awk -F';' 'NR > 1 { total += $4 } END { print total " Go" }' postes.csv
Windows 10: 2
Windows 11: 4
84 Go
L'enchaînement sort | uniq -c | sort -rn compte les occurrences d'une valeur et les classe de la plus fréquente à la moins fréquente. uniq ne regroupe que des lignes voisines, d'où le premier sort :
tail -n +2 postes.csv | cut -d';' -f2 | sort | uniq -c | sort -rn
2 Logistique
2 Comptabilite
1 Direction
1 Commercial
À égalité de nombre, sort -rn classe les lignes entières dans l'ordre inverse, d'où Logistique avant Comptabilite.
Le JSON en Bash avec jq
Bash ne sait pas lire le JSON. L'outil jq, à installer (sudo apt install jq : Debian 13 fournit jq 1.7.1), le filtre et le transforme. Avec le config.json ci-dessus :
jq '.seuilCritique' config.json # 10
jq -r '.alerte.destinataire' config.json # informatique@sorbier.example
jq -r '.serveurs[]' config.json # une ligne par serveur
jq '.serveurs | length' config.json # 3
L'option -r (raw) affiche les chaînes sans leurs guillemets : indispensable pour utiliser le résultat dans une variable Bash. Sans elle, jq '.alerte.destinataire' affiche "informatique@sorbier.example", guillemets compris.
Sur une liste d'objets, comme postes.json :
[
{ "nom": "PC-0002", "service": "Commercial", "os": "Windows 10", "ramGo": 8 },
{ "nom": "PC-0005", "service": "Direction", "os": "Windows 11", "ramGo": 32 },
{ "nom": "PC-0004", "service": "Logistique", "os": "Windows 10", "ramGo": 4 }
]
jq -r '.[] | select(.os == "Windows 10") | .nom' postes.json # PC-0002 puis PC-0004
jq '[.[] | select(.ramGo >= 8)] | length' postes.json # 2
jq -r '.[] | "\(.nom);\(.service)"' postes.json # une ligne CSV par poste
jq -r --arg os "Windows 11" '.[] | select(.os == $os) | .nom' postes.json # PC-0005
| Filtre jq | Rôle |
|---|---|
.cle, .a.b | Lire une propriété, une propriété imbriquée |
.[] | Parcourir chaque élément d'une liste |
.[0] | Le premier élément |
| | Envoyer le résultat au filtre suivant, comme le pipe du shell |
select(condition) | Ne garder que ce qui vérifie la condition |
[ … ] | Rassembler les résultats dans une liste |
length | Longueur d'une liste ou d'une chaîne |
"\(.a) \(.b)" | Construire une chaîne |
--arg nom valeur | Passer une variable du shell, utilisée comme $nom dans le filtre |
Passe toujours une valeur du shell par --arg plutôt qu'en la collant dans le filtre : une valeur contenant un guillemet casserait le filtre, comme une injection.
Exercices
Exercice 1 sur 4 Interroger un CSV Réussi À revoir
Avec $postes, l'import de postes.csv de ce chapitre, écris les commandes PowerShell qui :
- comptent les postes du service Comptabilite ;
- affichent les postes qui ont moins de 16 Go de mémoire ;
- calculent la mémoire totale du parc ;
- exportent les postes de la Logistique dans
logistique.csv, avec un point-virgule et des accents lisibles dans Excel, sous PowerShell 7.
Donne le résultat des questions 1 à 3.
Voir la réponseMasquer la réponse de l'exercice 1
# 1.
@($postes | Where-Object Service -eq 'Comptabilite').Count
# 2.
$postes | Where-Object { [int]$_.RAMGo -lt 16 }
# 3.
($postes | Measure-Object -Property RAMGo -Sum).Sum
# 4.
$postes | Where-Object Service -eq 'Logistique' |
Export-Csv -Path .\logistique.csv -Delimiter ';' -NoTypeInformation -Encoding utf8BOM
- 2 (PC-0001 et PC-0003).
- PC-0002 (8), PC-0003 (8) et PC-0004 (4). Sans la conversion
[int], la comparaison de chaînes donnerait un résultat faux :"8" -lt "16"est faux, car le caractère8vient après1. - 84.
Measure-Objectconvertit lui-même les chaînes en nombres pour la somme : 16 + 8 + 8 + 4 + 32 + 16. utf8BOMajoute la marque d'ordre des octets qu'attend Excel ; sous Windows PowerShell 5.1, on écrirait-Encoding UTF8.
Tu avais trouvé ?
Exercice 2 sur 4 Le même calcul en Bash Réussi À revoir
Sur postes.csv, écris la commande Bash qui affiche le nom des postes ayant 16 Go de mémoire ou plus, puis celle qui affiche le nombre de postes par service, du plus grand nombre au plus petit. Donne la sortie de la première.
Voir la réponseMasquer la réponse de l'exercice 2
awk -F';' 'NR > 1 && $4 >= 16 { print $1 }' postes.csv
tail -n +2 postes.csv | cut -d';' -f2 | sort | uniq -c | sort -rn
PC-0001
PC-0005
PC-0006
awk compare $4 comme un nombre parce que la valeur ressemble à un nombre : il n'a pas le piège des chaînes de PowerShell. NR > 1 saute l'en-tête, sans quoi awk comparerait le texte RAMGo à 16.
Tu avais trouvé ?
Exercice 3 sur 4 Lire une configuration Réussi À revoir
Le fichier config.json de ce chapitre est lu par un script PowerShell et par un script Bash. Écris, dans chaque langage, la ligne qui range le deuxième serveur de la liste dans une variable $second, puis la ligne qui parcourt tous les serveurs en affichant « Contrôle de srv-… ».
Voir la réponseMasquer la réponse de l'exercice 3
$config = Get-Content -Path .\config.json -Raw | ConvertFrom-Json
$second = $config.serveurs[1]
foreach ($s in $config.serveurs) { "Contrôle de $s" }
second=$(jq -r '.serveurs[1]' config.json)
jq -r '.serveurs[]' config.json | while IFS= read -r s; do
echo "Contrôle de $s"
done
Les deux langages comptent à partir de 0 : l'indice 1 désigne srv-ad2. Côté Bash, -r est indispensable, sinon $second contiendrait "srv-ad2" avec ses guillemets.
Tu avais trouvé ?
Exercice 4 sur 4 L'export qui perd ses accents Réussi À revoir
Sur srv-ad, où seul Windows PowerShell 5.1 est installé, un technicien exporte l'annuaire avec Get-ADUser -Filter * -Properties Department | Select-Object Name, Department | Export-Csv annuaire.csv. Dans le fichier, « Élise Bérard » devient « ?lise B?rard », toutes les valeurs sont entre guillemets dans une seule colonne d'Excel, et la première ligne commence par #TYPE. Explique les trois problèmes et corrige la commande.
Voir la réponseMasquer la réponse de l'exercice 4
- Les accents : sans
-Encoding,Export-Csvde Windows PowerShell 5.1 écrit en ASCII, qui ne connaît pas les lettres accentuées et les remplace par?. La perte est définitive. - Une seule colonne : le délimiteur par défaut est la virgule, alors qu'Excel en français attend un point-virgule.
- La ligne
#TYPE: Windows PowerShell 5.1 écrit le type des objets en tête de fichier, sauf avec-NoTypeInformation.
Get-ADUser -Filter * -Properties Department | Select-Object Name, Department |
Export-Csv -Path annuaire.csv -Delimiter ';' -NoTypeInformation -Encoding UTF8
En 5.1, -Encoding UTF8 écrit un BOM, ce qui permet à Excel de reconnaître l'UTF-8 et d'afficher « Élise Bérard ».
Tu avais trouvé ?