Shell : exécution de commandes
Shell : exécution de commandes
C’est l’outil le plus puissant de cette formation, et le seul dont la puissance se retourne contre vous en cas d’erreur. Les autres lisent, calculent, cherchent ; celui-ci agit sur une machine. Un diagnostic système mal formulé reste un diagnostic ; une commande de nettoyage mal formulée efface des fichiers.
Cette leçon présente donc l’usage et les garde-fous dans le même mouvement, parce qu’ils ne se séparent pas. La règle qui gouverne tout le reste : le modèle ne doit jamais pouvoir exécuter une commande que vous n’avez pas explicitement autorisée à l’avance.
Concept et architecture
La différence avec Code Interpreter est celle qui compte le plus, et elle tient en un mot : l’isolation. Code Interpreter s’exécute dans un bac à sable sans réseau, jetable, où le pire scénario est un calcul faux. Shell s’exécute dans un environnement que vous configurez — potentiellement votre machine, potentiellement un serveur — où le pire scénario est une opération irréversible.
Cette liberté est le but recherché : sans elle, pas de déploiement ni de maintenance automatisés. Elle impose seulement de choisir consciemment le périmètre d’exécution, et un container dédié aux droits limités est presque toujours le bon choix face à un accès direct à l’hôte.
from openai import OpenAI
client = OpenAI()
# L'outil Shell est utilise via le Agents SDK
# car il nécessite un environnement d'execution contrôle
from agents import Agent, Runner
agent = Agent(
name="devops-assistant",
model="gpt-5.6-terra",
instructions=(
"Tu es un assistant DevOps. Tu exécutés des commandes systeme "
"pour diagnostiquer et résoudre des problèmes d'infrastructure. "
"Explique chaque commande avant de l'exécuter."
),
tools=[{"type": "shell"}]
)
Cas d’usage : diagnostic système
# L'agent peut exécuter des commandes de diagnostic
agent = Agent(
name="diagnostic",
model="gpt-5.6-terra",
instructions=(
"Diagnostique l'état du systeme en exécutant les commandes appropriées. "
"Commence par les vérifications basiques (disque, memoire, CPU) "
"puis approfondis selon les symptômes."
),
tools=[{"type": "shell"}]
)
# L'agent exécutera des commandes comme :
# df -h -> espace disque
# free -m -> memoire
# top -bn1 -> charge CPU
# systemctl status nginx -> etat des services
Sécuriser l’exécution
Trois garde-fous se complètent, et aucun ne suffit seul. La liste blanche est le socle : on énumère ce qui est permis plutôt que ce qui est interdit, car une liste noire oublie toujours une variante — et il en suffit d’une. Le timeout empêche qu’une commande bloquée immobilise le système. Le répertoire de travail restreint limite les dégâts d’une commande légitime pointée au mauvais endroit.
Un quatrième, non technique, vaut les trois autres : pour toute opération destructive ou irréversible, exigez une confirmation humaine. Un modèle qui se trompe le fait avec la même assurance que lorsqu’il a raison.
# Pattern : fonction wrapper avec validation
import subprocess
import shlex
COMMANDES_AUTORISEES = {
"diagnostique": ["df", "free", "top", "uptime", "ps", "netstat", "curl", "ping"],
"deploiement": ["git", "docker", "npm", "pip"],
"fichiers": ["ls", "cat", "head", "tail", "wc", "grep", "find"],
}
COMMANDES_INTERDITES = ["rm", "dd", "mkfs", "shutdown", "reboot", "kill", "passwd"]
def executer_commande_securisee(commande: str, categorie: str = "diagnostique") -> dict:
"""Exécute une commande avec validation de securite."""
# Parser la commande
parties = shlex.split(commande)
executable = parties[0]
# Verifier les interdictions
if executable in COMMANDES_INTERDITES:
return {
"erreur": f"Commande interdite : {executable}",
"raison": "Cette commande est dans la liste des commandes bloquees"
}
# Verifier les autorisations
autorisees = COMMANDES_AUTORISEES.get(categorie, [])
if executable not in autorisees:
return {
"erreur": f"Commande non autorisee dans la categorie '{categorie}'",
"autorisees": autorisees
}
# Executer avec timeout
try:
result = subprocess.run(
parties,
capture_output=True,
text=True,
timeout=30,
cwd="/tmp" # Répertoire de travail restreint
)
return {
"stdout": result.stdout,
"stderr": result.stderr,
"code_retour": result.returncode
}
except subprocess.TimeoutExpired:
return {"erreur": "Timeout : la commande a depasse 30 secondes"}
Implémenter comme fonction personnalisée
L’implémenter vous-même en function calling présente d’ailleurs un avantage qu’il ne faut pas voir comme un pis-aller : la validation vit dans votre code. Vous décidez de la liste blanche, du journal, de la confirmation, et vous pouvez tout auditer. Pour un outil de ce niveau de risque, garder la main sur le point de contrôle est préférable, même quand une version native devient disponible.
tools = [
{
"type": "function",
"name": "executer_commande",
"description": (
"Exécuter une commande shell sur le serveur. "
"Commandes autorisees : diagnostic systeme (df, free, top, uptime, ps), "
"lecture de fichiers (ls, cat, head, tail, grep), "
"reseau (curl, ping, netstat). "
"Les commandes destructives sont interdites."
),
"parameters": {
"type": "object",
"properties": {
"commande": {
"type": "string",
"description": "La commande shell a exécuter"
},
"repertoire": {
"type": "string",
"description": "Répertoire de travail (défaut: /tmp)"
}
},
"required": ["commande"],
"additionalProperties": False
},
"strict": True
}
]
import json
def traiter_appels(response):
resultats = []
for item in response.output:
if item.type == "function_call" and item.name == "executer_commande":
args = json.loads(item.arguments)
resultat = executer_commande_securisee(
args["commande"],
categorie="diagnostique"
)
resultats.append({
"type": "function_call_output",
"call_id": item.call_id,
"output": json.dumps(resultat)
})
return resultats
# Boucle complete
response = client.responses.create(
model="gpt-5.6-terra",
input="Vérifie l'espace disque et la memoire disponible",
tools=tools
)
resultats = traiter_appels(response)
response_finale = client.responses.create(
model="gpt-5.6-terra",
input=response.output + resultats,
tools=tools
)
print(response_finale.output_text)
Workflows d’automatisation
Déploiement automatisé
Le déploiement est le cas d’usage le plus rentable, à condition de bien voir ce qu’on délègue. Le modèle n’invente pas la procédure : il enchaîne des étapes que vous avez définies, vérifie les sorties intermédiaires et s’arrête sur une anomalie. C’est un opérateur attentif, pas un décideur.
tools_deploy = [
{
"type": "function",
"name": "executer_etape_deploy",
"description": (
"Exécuter une étape de deploiement. "
"Etapes possibles : git_pull, tests, build, deploy, rollback. "
"Chaque étape est validée avant de passer a la suivante."
),
"parameters": {
"type": "object",
"properties": {
"etape": {
"type": "string",
"enum": ["git_pull", "tests", "build", "deploy", "rollback"]
},
"parametres": {
"type": "object",
"properties": {
"branche": {"type": "string"},
"environnement": {"type": "string", "enum": ["staging", "production"]}
}
}
},
"required": ["etape"],
"additionalProperties": False
},
"strict": True
}
]
def executer_etape(etape: str, parametres: dict) -> dict:
scripts = {
"git_pull": "cd /app && git pull origin {branche}",
"tests": "cd /app && npm test",
"build": "cd /app && npm run build",
"deploy": "cd /app && ./deploy.sh {environnement}",
"rollback": "cd /app && ./rollback.sh {environnement}"
}
commande = scripts[etape].format(**parametres)
return executer_commande_securisee(commande, "deploiement")
Monitoring et alertes
Le monitoring inverse la logique du reste de la leçon : les commandes y sont en lecture seule, donc sans danger, et c’est l’interprétation qui a de la valeur. Un modèle qui lit l’espace disque, la charge et les journaux d’erreurs voit les corrélations qu’un seuil isolé manque — c’est le point d’entrée le plus sûr pour commencer avec cet outil.
def verifier_sante_systeme() -> dict:
"""Vérifie la sante du systeme et retourne un rapport."""
checks = {}
# Espace disque
r = executer_commande_securisee("df -h /", "diagnostique")
checks["disque"] = r
# Memoire
r = executer_commande_securisee("free -m", "diagnostique")
checks["memoire"] = r
# Services
for service in ["nginx", "postgresql", "redis"]:
r = executer_commande_securisee(
f"systemctl is-active {service}",
"diagnostique"
)
checks[f"service_{service}"] = r
return checks
# L'IA analyse les résultats et propose des actions
response = client.responses.create(
model="gpt-5.6-terra",
input=f"Analyse ce rapport de sante systeme et identifie les problemes :\n"
f"{json.dumps(verifier_sante_systeme(), indent=2)}",
tools=tools
)
Points clés à retenir
- L’outil Shell connecte le modèle à votre infrastructure
- Implémentez des listes blanches et noires de commandes
- Utilisez des timeouts et des répertoires de travail restreints
- Le function calling permet d’implémenter Shell comme outil personnalisé
- Combinez Shell avec d’autres outils pour des workflows DevOps complets