Partie 3 : Scripts système
Niveau avancé et optionnel pour passionnés en avance
Un script est un fichier texte qui contient des instructions à destination d'un interpréteur comme python, bash, powershell, etc...
Partie A : Scripts en python
Pour gérer un système avec python, il faut utiliser les modules sys, os et d'autres modules spécialisés comme ceux du package psutil qui s'appuie sur os et sys.
- Avant de commencer
Les codes ci-dessous afficheront les résultats dans une console. Ils s'appuient largement sur la fonction "print" avec paramétrage C-style que vous n'avez peut être jamais encore rencontré.
Le principe est le suivant, on a une chaîne de caractères à afficher "..bla..bla..bla.. " avec des valeurs de variables à "injecter" à certains entroits de la chaîne, on fait alors suivre la chaîne du symbole % puis des valeurs de variables à injecter dans la chaîne entre parenthèses. Dans la chaîne on insère des symboles % suivi d'un formatage souhaité à l'emplacement souhaité des éléments à injecter.
a="bonjour"
b="hello"
"%3sbablabla%4s" % (a,b) donnera "bonblablablahell"
Pour comprendre le principe, lire les exemples ci-dessous:
Exemples
''' on veut afficher hello sur 10 caractères, on formate %10s (le s indique une chaîne de caractères, type string), ici ça laissera des blancs à gauche parce que hello ne fait pas 10 caractères)'''
''' on veut afficher "hello" sur 10 caractères avec des blancs à droite, on formate %-10s ( signe - pour blancs à droite), on veut ensuite injecter "you" sur 3 caractères, on formate %3s, la chaîne sera "%-10s %3s" suivi de % puis des valeurs à injecter entre parenthèses et séparées par des virgules:'''
# on veut afficher un nombre flottant ( type f) sur 5 caractères %5f avec deux décimales %5.2f et un 0 à gauche %05.2f:
# ci-dessous on affiche des contacts placés dans une liste , chaque contact dans la liste est dans un dictionnaire:
- Module OS
Le module OS permet à Python d’interagir avec le système d'exploitation.
Dans l'exemple qui suit vous allez créer des fichiers et des dossiers puis les parcourir en les affichant.
- Exercice A1:
- Avec le logiciel vscodium, créer un fichier python appelé lister.py et l'enregistrer dans le dossier /home/nsi/Documents/Python_codes
- Importer le module os ( rappel écriture: import nom_module)
- Importer la fonction Path du module path ( rappel écriture: from module import fonction)
On va définir deux fonctions:
create_dir(d_base, nom) qui permet de créer le sous-dossier représenté par la variable nom dans le dossier représenté par la variable d_base
Exemple: create_dir("/home/nsi", "dossier1") crée le sous-dossier dossier1 dans /home/nsi
create_fic( rep, nom ) qui permet de créer le fichier représenté par la variable nom dans le dossier représenté par la variable rep
Exemple: create_fic("/home/nsi/dossier1", "fichier1" ) crée le fichier fichier1 dans le dossier dossier1
- Ajouter le code ci-dessous qui déclare les deux fonctions:
def create_dir(d_base, nom):
try:
d = os.path.join( d_base, nom)
os.mkdir(d)
return d
except:
print("il existe déjà le dossier mec!")
d = os.path.join( d_base, nom)
return d
def create_fic( rep, nom ):
fichier = os.path.join( rep, nom )
with open(fichier, 'w+') as fic:
fic.write( "fichier : %s \n" % nom )
- Ajouter le code ci-dessous qui demande la création de la variable dir_base qui contiendra le chemin où créer les dossiers et les fichiers:
# on choisit comme dossier courant celui ou est placé le script python, on copie son chemin dans la variable dir_base
dir_base = "/home/nsi/Documents/Python_codes"
- Créer une liste vide appelée rep
- Ajouter le code ci-dessous qui demande la création du dossier 'Dossier1' dans dir_base et de l'ajouter à la liste rep
rep.append( create_dir(dir_base, 'Dossier1') )
- Créer la demande de création du dossier 'Dossier2' dans dir_base et l'ajouter à la liste rep
- Créer la demande de création du dossier 'Dossier3' dans dir_base et l'ajouter à la liste rep
- Créer une liste vide appelée fic
- Ajouter le code ci-dessous qui demande la création du fichier 'fichier1' dans le premier dossier cité dans la liste rep et de l'ajouter à la liste fic
fic.append( create_fic(rep[0], "fichier1" ) )
- Créer la demande de création du fichier 'fichier2' dans le deuxième dossier de la liste rep et l'ajouter à la liste fic
- Créer la demande de création du fichier 'fichier2_bis' à nouveau dans le deuxième dossier de la liste rep et l'ajouter à la liste fic
- Créer la demande de création du fichier 'fichier3' dans le troisième dossier de la liste rep et l'ajouter à la liste fic
- Ajouter le code ci-dessous qui demande la création du sous dossier 'Sous_Dossier1' dans le deuxième dossier de la liste rep et d'y ajouter
le fichier sfichier1
srep = []
srep.append( create_dir(rep[1], 'Sous_Dossier1') )
fic.append( create_fic(srep[0], "sfichier1" ) )
- Ajouter le code ci-dessous qui demande le parcours des dossiers et sous-dossiers avec affichage des fichiers présents: méthode walk(..) de os:
print("=== Parcours avec os.walk ===")
for e in os.walk( dir_base ):
print( e )
- Ajouter le code ci-dessous qui demande le parcours des dossiers et sous-dossiers avec affichage des fichiers présents: méthode walkfiles() de path:
print("=== Parcours avec path ===")
for e in Path(dir_base).walkfiles():
print(e)
- Tester le code complet. Si vous ouvrez le dossier Python_codes, vous devez voir l'action du script: la création de dossiers et de fichiers dans ces dossiers:
Les trois prochains chapitres sont des essais de code python qui vous permettent de voir que python peut accéder à tout ou presque sur un système d'exploitation ce qui en fait un langage potentiellement dangereux comme tous les langages de script orientés système.
- Analyse de la mémoire RAM
Le module psutil dispose de deux fonctions:
virtual_memory() retourne un tuple avec les infos sur l'occupation de la RAM.
swap_memory() renvoie des infos sur la mémoire vive prise sur le disque dur ( swap)
- Exercice A2:
- Testez le code ci-dessous :
import psutil
print(psutil.virtual_memory())
print(psutil.swap_memory())
Vous pouvez voir l'état de la mémoire mais ce n'est pas très parlant...
- Testez maintenant le code ci-dessous qui ajoute la mise en forme de l'affichage:
import psutil
from psutil._common import bytes2human
def pprint_ntuple(nt):
for name in nt._fields:
value = getattr(nt, name) # value contiendra le nom de l'attribut name de l'objet nt
if name != 'percent':
# conversion en multiples d'octets
value = bytes2human(value)
# affichage sur 10 caractères avec espace vide à droite pour name
# affichage sur 7 caractères avec espace vide à gauche pour value
print('%-10s : %7s' % (name, value))
print('MEMORY\n------')
pprint_ntuple(psutil.virtual_memory())
print('\nSWAP\n----')
pprint_ntuple(psutil.swap_memory())
L'affichage devient:
On peut améliorer avec print('%-10s : %7s' % (name.capitalize(), value)), l'affichage devient:
- Analyse des périphériques de stockage
psutil propose plusieurs fonctions dont:
disk_partitions() : retourne un tuple avec les périphériques et leur point de montage dans le système de fichiers, la fonction prend le paramètre all = True pour voir tous les périphériques (on le réglera sur False parce que nous ne sommes pas root).
disk_usage (chemin): retourne pour un périphérique, le volume total, utilisé, libre et le pourcentage d'usage.
- Exercice A3:
Tester le code ci-dessous:
import sys
import os
import psutil
from psutil._common import bytes2human
# déco affichage
templ = "%-17s %8s %8s %8s %5s%% %9s %s"
print(templ % ("Device", "Total", "Used", "Free", "Use ", "Type","Mount"))
# pour chaque partition
for part in psutil.disk_partitions(all=False):
# si windows
if os.name == 'nt':
if 'cdrom' in part.opts or part.fstype == '':
# si c'est le lecteur cdrom on zappe
continue
# lecture de la partition et affichage infos
usage = psutil.disk_usage(part.mountpoint)
print(templ % (
part.device,
bytes2human(usage.total),
bytes2human(usage.used),
bytes2human(usage.free),
int(usage.percent),
part.fstype,
part.mountpoint))
Conclusion: le disque dur est découpé en deux partitions:
- la partition sda3 de 18.8Go qui contient la racine / et tout son contenu, cette partition est formatée en ext4, les fichiers occupent 16.4 Go
- la partition sda2 de 512 Mo qui contient le code de démarrage du PC virtuel.
- Analyse des processus
Le package psutil propose la classe Process qui contient les données de chaque processus sous forme d'attributs.
Voici quelques méthodes:
pids(): retourne la liste des processus en cours d'exécution.
process_iter(): permet de parcourir les détails des processus en cours d'exécution.
onshot(): accélère l'accès aux infos en fonction de l'OS.
asdict() : retourne les infos du processus sous la forme d'un dictionnaire.
- Exercice A4:
Tester le code ci-dessous :
import sys
import psutil
# On crée deux listes pour stocker les infos
ad_pids = []
procs = []
# pour chaque objet processus en mémoire
for p in psutil.process_iter():
with p.oneshot():
try:
# demande de toutes les infos sur le processus p
mem = p.memory_full_info()
# crée un dico avec le chemin vers programme lié au processus
p._info = p.as_dict(["cmdline"])
# ajoute à la liste
procs.append(p)
except psutil.AccessDenied:
# ajoute les pid des processus non accessibles
ad_pids.append(p.pid)
except psutil.NoSuchProcess:
pass
# en tête pour affichage décoré
print("PID"," ", "Cmdline")
print("=" * 70)
#pour les 91 premiers processus de la liste procs
for p in procs[:91]:
# ne garder que le premier champ de la liste des infos s'il existe sinon affiche accès refusé
cmd = "".join(p._info["cmdline"]) if not p._info["cmdline"] == [] else "access denied"
# créer et affiche une ligne avec le PID et le chemin du prog
line = "{} {}".format(p.pid, cmd)
print(line)
# afficher le nombre des processus à accès refusé
if ad_pids:
print("accès refusé pour %s pids" % (len(ad_pids)),file=sys.stderr)
- Lancement de script en ligne de commande
Jusqu'à présent vous lanciez vos scripts depuis le logiciel de création ( vscodium, Thonny, edupython, etc..). Dans cette partie, on va voir comment le lancer en ligne de commande puis comment le rendre exécutable pour le lancer en autonomie.
- Ouvrir un terminal
- Se déplacer dans le dossier qui contient le script avec la commande cd, avec le chemin absolu cela donne cd /home/nsi/documents/Python_codes
- Saisir la commande ls -l pour lister le contenu du dossier et voir les droits, vous pouvez voir que lister.py n'est pas exécutable ( pas le droit x)
- Pour lancer le programme lister.py en demandant de l'interpréter à python3, il faut:
- Saisir python3 lister.py comme montré ci-dessous:
Pour rendre un script autonome, il faut ajouter en première ligne du script ( pas de lignes vides avant), dans le fichier .py, le chemin vers l'interpréteur (précédé de #! ): ici #! /usr/bin/python3.10
- Donner ensuite le droit d'exécution à l'utilisateur nsi sur le fichier lister.py:
- Saisir sudo chmod u+x lister.py
- Vérifier avec la commande ls-l que les droits sont rwx pour nsi
- Lancer le script avec la commande ./lister.py
Pour pouvoir lancer le script depuis n'importe où dans le système de fichier ( ici on est dans le dossier qui le contient) , il faut :
- soit ajouter le dossier /home/nsi/Documents/Python_codes dans le "PATH" ( nom donné à la variable qui contient les chemins vers les fichiers exécutables du système)
- soit déplacer le script dans un des dossiers cités dans "PATH".
- soit créer un lien symbolique vers /home/nsi/Documents/Python_codes/lister.py dans un des dossiers de "PATH"
La commande echo $PATH vous donne la liste des dossiers répertoriés dans le path des fichiers exécutables.
$PATH est une variable d'environnement ( cette notion est expliquée dans la partie script Bash)
- Saisissez la commande echo $PATH
Pour ajouter un nouveau chemin à PATH le temps d'une session, on saisit:
- Saisissez l'ajout de /home/nsi/Documents/Python_codes dans PATH comme montré ci-dessous et vérifier avec echo $PATH
- Revenez à la racine du dossier personnel de l'utilisateur nsi avec la commande : cd ~ ou cd /home/nsi
- Lister le contenu avec ls , lister.py n'est pas présent.
- Lancer le script en saisissant simplement lister.py, vous pouvez voir que le shell trouve bien le fichier et l'exécute:
Remarque
Le chemin /home/nsi/Documents/Python_codes est dans PATH pour la durée de la session, pour l'ajouter définitivement il faudrait copier ce chemin dans /home/nsiuser/.bashrc. En ligne de commande avec l'éditeur nano ça donnerait nano .bashrc pour éditer le contenu de .bashrc ( ne pas le faire ici, c'est inutile)
Une autre méthode est de créer un raccourci ( lien physique) vers le fichier lister.py dans un des dossiers connus de PATH. Le répertoire de prédilection sera /usr/local/bin
- Se déplacer dans /home/nsi/Documents/Python_codes avec cd /home/nsi/Documents/Python_codes
- Créer maintenant un fichier lister dans /usr/local/bin lié à lister.py avec la commande ln ( commande ln pour link) ainsi:
sudo ln lister.py /usr/local/bin/lister ( on est dans /home/nsi/Documents/Python_codes, il est inutile de donner le chemin vers lister.py)
Dans le dossier /usr/local/bin, il existe maintenant un fichier appelé lister qui est lié physiquement au fichier /home/nsi/Documents/Python_codes/lister.py
vous pouvez voir le nombre 2 qui apparaît juste après les droits c'est le nombre de liens vers un fichier.
Un lien physique permet de donner plusieurs noms et chemin d'accès à un même fichier en pointant sur un numéro de fichier (en interne, Linux enregistre les fichiers sur la base d'un numéro (appelé numéro d'index ou inode) et pas sur la base d'un nom). Un fichier peut donc avoir plusieurs noms, et existera tant qu'il a au moins un nom.
- Maintenant, vous pouvez lancer le script de n'importe où en saisissant le mot lister, c'est devenu un programme en apparence "exécutable", en réalité il est toujours interprété par python mais de manière transparente:
- Rendez-vous dans le répertoire /usr/local/bin avec cd /usr/local/bin
- Saisir sudo rm lister pour supprimer le fichier lister du dossier ( la commande rm pour ReMove) :
- Le nombre de lien vers lister.py redevient 1:
- Partie B: scripts shell
Les scripts shell sont des fichiers exécutables qui permettent de lancer successivement plusieurs commandes avec un interpréteur shell comme Bash ou autre. Pour créer un script shell, il faut éditer un fichier, y entrer les commandes et le rendre exécutable.
- Exercice B1:
Saisissez les commandes proposées dans les explications ci-dessous et faites une capture ce que vous obtenez.
Il existe plusieurs interpréteurs shells sur un système Linux. Le fichier /etc/shells contient une liste de tous les interpréteurs shells disponibles :
- Saisissez la commande cat /etc/shells pour afficher la liste des interpréteurs disponibles sur votre système.
- Saisissez: printenv SHELL pour connaître celui qui est choisi par défaut sur le système, ici c'est le shell GNU appelé bash:
Si vous voulez lister avec ls -l les fichiers d'un répertoire qui en contient beaucoup, vous vous retrouvez avec un affichage qui défile rapidement jusqu'aux derniers fichiers , il faut alors utiliser la barre de défilement pour remonter aux premiers fichiers listés du répertoire.
Exemple : on veut afficher la liste des fichiers de configuration du système qui sont dans le répertoire /etc
- Saisissez ls -l /etc
Une solution pour afficher la liste des fichiers, page par page, est de rediriger la sortie de la commande d'affichage avec un tube ( caractère | ) vers l'entrée de l'utilitaire less qui permet d'afficher un contenu page par page, ligne par ligne, etc..
- Saisissez ls -l /etc | less
- Appuyez sur la touche clavier entrée pour ajouter une ligne à l'affichage.
- Appuyez sur la touche clavier défilement page ( encadrés en rouge ci-dessous) pour afficher page par page.
- Appuyez sur la touche clavier Q pour quitter l'utilitaire less.
- Premier script
- Exercice B2:
On veut maintenant enregistrer la commande précédente dans un script
- Ouvrir un terminal, vous êtes dans /home/nsi
- Saisissez nano liste_repertoire.sh dans le terminal. L'extension sh est pour se rappeler que c'est un script shell ( comme .bat ou .ps sous Windows)
- Saisissez les commandes ci-dessous dans le fichier:
Explications:
La première ligne commence par #, elle sera perçue par l'interpréteur bash comme un commentaire, par contre le noyau Linux utilise les deux premiers caractères d'un fichier pour en connaître sa nature, ici #! est la suite de caractères qui précise que c'est un fichier de script, la suite est le chemin de l'interpréteur bash à appeler pour l'interprétation ici /bin/bash.
Le "$1" en jaune ( dollar puis 1) est une variable qui représente le premier argument qui suit la commande liste_repertoire.sh lors de son appel.
Par exemple si on lance la commande liste_repertoire.sh /etc alors "$1" vaut /etc, la commande ls est alors interprétée comme : ls -al /etc | less ce qui liste le contenu de /etc avec less
Si vous aviez lancé la commande liste_repertoire.sh / alors "$1" vaut /
La commande serait interprétée comme : ls -al / | less ce qui listerait le contenu de la racine / avec less
- Quittez nano avec ctrl+x , valider les modifications avec O puis Entrée
- Saisissez chmod u+x liste_repertoire.sh pour rendre votre script exécutable pour vous.
- Saisissez la commande : ./liste_repertoire.sh /etc pour lister le contenu de /etc
- Le script s'exécute, appuyez sur la touche page vers bas puis sur Q pour quitter le script.
- Renommez maintenant le fichier avec la commande mv pour ( move) : mv ./liste_repertoire.sh ./liste_repertoire
- Saisissez la commande : ./liste_repertoire /etc vous listez toujours /etc
Comme dit plus haut l'utilité de l'extension .sh est pour préciser à un humain que l'on a affaire à un script shell.
Variables d'environnement:
Vous avez vu $PATH qui est une variable d'environnement qui contient la liste des dossiers qui contiennent les fichiers exécutables. Chaque fois qu'une session shell est lancée, un processus est mis en place pour collecter et rassembler les informations qui devraient être à la disposition du shell et de ses processus enfant. Ce processus lit les données d'un grand nombre de fichiers qui se trouvent sur le système et les stockent dans des variables appelées variables d'environnement.
Les variables d'environnement sont définies pour le shell en cours d'utilisation et héritées par tous les processus lancés depuis le shell.
Les variables d'environnement sont un moyen pour transmettre des données à des processus.
On peut afficher la valeur de toutes les variables d'environnement avec la commande: env et il y en a beaucoup:
On peut afficher la valeur d'une des variables avec la commande: printenv nom de la variable
Structure de test if then else
Le script précédent est fonctionnel mais il a une lacune, si l'utilisateur omet de préciser quel répertoire il veut lister avec less le script génère une erreur. On peut le rendre mieux conçu avec une structure de test if ... else.
La structure s'écrit ainsi en langage de script bash:
if [ condition1 ]
then
commandes à lancer si condition1 vraie
elif [ condition 2 ]
then
commandes à lancer si condition2 vraie
else
commande à lancer si les conditions 1 et 2 ne sont pas vraies
fi
- Exercice B3:
- Modifiez le script avec nano comme ci-dessous, quittez/enregistrez : ctrl+x, puis O puis Entrée
Explications:
Un caractère précédé d'un dollar dans un script est une variable. La variable $# contient le nombre d'arguments passé à une commande, ici on se sert de cette valeur pour tester si le nombre d'arguments n'est pas égal à 1
avec le mot clé -ne 1: not equal to 1 ( si la condition est vraie c'est que $# contient soit zéro argument ou plus d'un). Si ce if est vrai, on affiche un message avec echo puis on quitte le script avec exit 1
Voir plus bas pour les diverses notations des arguments passés à une commande.
- Testez en omettant le répertoire à lister: ./liste_repertoire ou ./liste_repertoire.sh ( si vous avez gardé l'extension .sh)
- Testez maintenant en précisant le répertoire /var à lister: ./liste_repertoire /var
Notation des arguments passés à une commande
On considère l'exemple de commande suivant: ./liste_repertoire /var
$# est une variable qui contient le nombre d'arguments, ci-dessus $# = 1 ( c'est /var)
$0 est une variable qui représente le nom du script ci-dessus: ./liste_repertoire
$1 est une variable qui représente le premier argument ci-dessus: /var
$2 est une variable qui représente le deuxième argument qui n'existe pas ici
"$@": insère dans un script chaque argument sous la forme d'un mot séparé: "$1" "$2" "$3" ... "$9" ( on peut avoir jusqu'à 9 arguments passés à un script)
"$*" : créé un mot unique avec tous les arguments " $1 $2 $3....$9 "
- Exercice B4:
- Créez le script arguments.sh avec le contenu suivant:
- Rendez le script exécutable puis testez le en saisissant ./arguments.sh coucou toi
Notations des opérateurs de test:
il y a plusieurs écritures possibles.
Autre écriture des tests ( plus proches du langage C) :
- Créez le script compare.sh avec le contenu suivant et testez le:
Dans un script bash, un point virgule permet de séparer deux instructions pour pouvoir les écrire sur une même ligne:
comme ci-dessus if (( "$a" < "$b" )) ; then au lieu de:
if (( "$a" < "$b" ))
then
echo "$a est inférieur à $b"
Voici un script qui utilise deux nombres passés en arguments du script et les compare:
Structure répétitive ( boucle): for .... in... do .... done
Une boucle for... in permet de parcourir un dossier pour effectuer des traitements sur les éléments de ce dossier
Exemple, le script ci-dessous:
- Se positionne dans le dossier nommé Monrep1 du repertoire Monrep de l'utilisateur nsiuser
- Créée le dossier "Monrep2" s'il n'existe pas encore ( option -p)
- Parcourt avec une boucle for tous les fichiers d'extension .py du dossier Monrep1, pour chacun de ces fichiers, il affiche (echo) le nom du fichier puis le déplace ( mv) dans le dossier Monrep2. Dans la boucle for chaque fichier est représenté par la variable $f, les fichiers d'extensions .py sont les seuls retenus parce que l'on demande au for de parcourir le résultat d'un tri réalisé avec `ls *.py` ici la commande ls doit être entourée de simples quotes ` ( ce caractère du clavier s'obtient avec altGr + 7)
- Exercice B5:
- Modifier le script ci-dessus pour se déplacer dans le dossier Python_codes
- demander la création du répertoire mes_scripts avec l'option -p.
- Afficher avec echo tous les fichiers d'extension .py
- copier tous les fichiers d'extension .py dans mes_scripts ( commande cp au lieu de mv)
- Tester votre script.
- Vérifiez le contenu de Python_codes et de mes_scripts
Created with the Personal Edition of HelpNDoc: Free PDF documentation generator