---
title: Outil bash
url: https://platform.claude.com/docs/fr/agents-and-tools/tool-use/bash-tool
description: Permettez à Claude de demander des commandes shell que votre application exécute dans une session bash persistante et renvoie sous forme de résultats d'outils.
---

<Note>
  Pour savoir comment la « zero data retention » (rétention zéro des données), ou ZDR, s'applique à cette fonctionnalité, consultez [API et rétention des données](https://platform.claude.com/docs/fr/manage-claude/api-and-data-retention).
</Note>

L'outil bash est un [outil client](https://platform.claude.com/docs/fr/agents-and-tools/tool-use/how-tool-use-works) : Claude n'exécute pas lui-même les commandes. Lorsque vous incluez l'outil dans une requête, Claude répond avec un bloc `tool_use` qui indique la commande à exécuter. Votre application exécute cette commande dans une session bash qu'elle possède et renvoie la sortie dans un bloc `tool_result`.

Votre application maintient un seul processus bash actif d'un appel d'outil à l'autre, de sorte que l'état persiste entre les commandes. Le répertoire de travail, les variables d'environnement et tous les fichiers créés par une commande sont toujours présents pour la commande suivante.

La version actuelle de l'outil est `bash_20250124`. Pour la prise en charge des modèles, les en-têtes bêta et la version antérieure, consultez [Versions de l'outil](https://platform.claude.com/docs/fr/agents-and-tools/tool-use/bash-tool#tool-versions). Pour tous les outils fournis par Anthropic, consultez la [Référence des outils](https://platform.claude.com/docs/fr/agents-and-tools/tool-use/tool-reference).

## Cas d'utilisation

* **Flux de travail de développement :** exécuter des commandes de build, des tests et des outils de développement
* **Automatisation système :** exécuter des scripts, gérer des fichiers, automatiser des tâches
* **Traitement de données :** traiter des fichiers, exécuter des scripts d'analyse, gérer des jeux de données
* **Configuration d'environnement :** installer des paquets, configurer des environnements

## Démarrage rapide

<CodeGroup>
  ```bash cURL
  curl https://api.anthropic.com/v1/messages \
    -H "content-type: application/json" \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -d '{
      "model": "claude-opus-5-5",
      "max_tokens": 1024,
      "tools": [
        {
          "type": "bash_20250124",
          "name": "bash"
        }
      ],
      "messages": [
        {
          "role": "user",
          "content": "List all Python files in the current directory."
        }
      ]
    }'
  ```

  ```bash CLI
  ant messages create \
    --model claude-opus-5-5 \
    --max-tokens 1024 \
    --tool '{type: bash_20250124, name: bash}' \
    --message '{role: user, content: List all Python files in the current directory.}'
  ```

  ```python Python
  client = anthropic.Anthropic()

  response = client.messages.create(
      model="claude-opus-5-5",
      max_tokens=1024,
      tools=[{"type": "bash_20250124", "name": "bash"}],
      messages=[
          {"role": "user", "content": "List all Python files in the current directory."}
      ],
  )

  print(response)
  ```

  ```typescript TypeScript
  const client = new Anthropic();

  const response = await client.messages.create({
    model: "claude-opus-5-5",
    max_tokens: 1024,
    tools: [{ type: "bash_20250124", name: "bash" }],
    messages: [
      {
        role: "user",
        content: "List all Python files in the current directory."
      }
    ]
  });

  console.log(response);
  ```

  ```csharp C#
  var client = new AnthropicClient();

  var response = await client.Messages.Create(
      new()
      {
          Model = Model.ClaudeOpus5_5,
          MaxTokens = 1024,
          Tools = [new ToolBash20250124()],
          Messages =
          [
              new()
              {
                  Role = Role.User,
                  Content = "List all Python files in the current directory.",
              },
          ],
      }
  );

  Console.WriteLine(response);
  ```

  ```go Go
  client := anthropic.NewClient()

  response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
  	Model:     anthropic.ModelClaudeOpus5_5,
  	MaxTokens: 1024,
  	Tools: []anthropic.ToolUnionParam{
  		{OfBashTool20250124: &anthropic.ToolBash20250124Param{}},
  	},
  	Messages: []anthropic.MessageParam{
  		anthropic.NewUserMessage(anthropic.NewTextBlock("List all Python files in the current directory.")),
  	},
  })
  if err != nil {
  	log.Fatal(err)
  }
  fmt.Println(response)
  ```

  ```java Java
  import com.anthropic.models.messages.ToolBash20250124;

  void main() {
      AnthropicClient client = AnthropicOkHttpClient.fromEnv();

      Message response = client.messages().create(
          MessageCreateParams.builder()
              .model(Model.CLAUDE_OPUS_5_5)
              .maxTokens(1024)
              .addTool(ToolBash20250124.builder().build())
              .addUserMessage("List all Python files in the current directory.")
              .build()
      );

      IO.println(response);
  }
  ```

  ```php PHP
  use Anthropic\Messages\ToolBash20250124;

  $client = new Client();

  $response = $client->messages->create(
      model: 'claude-opus-5-5',
      maxTokens: 1024,
      tools: [new ToolBash20250124()],
      messages: [
          ['role' => 'user', 'content' => 'List all Python files in the current directory.'],
      ],
  );

  echo $response;
  ```

  ```ruby Ruby
  client = Anthropic::Client.new

  response = client.messages.create(
    model: "claude-opus-5-5",
    max_tokens: 1024,
    tools: [{type: "bash_20250124", name: "bash"}],
    messages: [
      {role: "user", content: "List all Python files in the current directory."}
    ]
  )

  puts response
  ```
</CodeGroup>

Claude répond avec `stop_reason: "tool_use"` et un bloc `tool_use` qui contient la commande que votre application doit exécuter :

```json Output
{
  "id": "msg_01XAbCDeFgHiJkLmNoPQrStU",
  "model": "claude-opus-5-5",
  "stop_reason": "tool_use",
  "role": "assistant",
  "content": [
    {
      "type": "text",
      "text": "I'll list all Python files in the current directory for you."
    },
    {
      "type": "tool_use",
      "id": "toolu_01A09q90qw90lq917835lq9",
      "name": "bash",
      "input": {
        "command": "ls *.py"
      }
    }
  ]
}
```

Exécutez `input.command` dans votre session bash et renvoyez la sortie sous forme de `tool_result`. Consultez [Implémenter l'outil bash](https://platform.claude.com/docs/fr/agents-and-tools/tool-use/bash-tool#implement-the-bash-tool) pour l'aller-retour complet.

## Fonctionnement

Chaque appel d'outil correspond à un aller-retour entre Claude et votre application :

1. Claude renvoie un bloc `tool_use` contenant la `command` à exécuter.
2. Votre application exécute la commande dans sa session bash.
3. Votre application renvoie la sortie de la commande, stdout et stderr ensemble, à Claude dans un bloc `tool_result`.
4. Claude demande une autre commande dans la même session ou répond avec du texte.

Claude peut également renvoyer plusieurs blocs `tool_use` dans une seule réponse. Exécutez-les dans l'ordre dans la même session et renvoyez tous les résultats dans un seul message `user`. Consultez [Utilisation d'outils en parallèle](https://platform.claude.com/docs/fr/agents-and-tools/tool-use/parallel-tool-use).

L'API est sans état. Rien concernant votre session shell ne transite entre les requêtes ; c'est donc votre application qui décide quand la session démarre, combien de temps elle vit et quand la redémarrer. Pour le cycle complet de requête et de réponse, consultez [Gérer les appels d'outils](https://platform.claude.com/docs/fr/agents-and-tools/tool-use/handle-tool-calls).

## Paramètres

Une définition de l'outil bash comporte deux champs obligatoires, `type` et `name`, et `name` doit être `bash`. L'outil est sans schéma : vous ne fournissez pas d'`input_schema`, car le schéma est intégré au modèle de Claude et ne peut pas être modifié. Le tableau suivant répertorie les champs d'entrée que Claude définit lorsqu'il appelle l'outil.

| Paramètre | Obligatoire | Description                                        |
| --------- | ----------- | -------------------------------------------------- |
| `command` | Oui\*       | La commande bash à exécuter                        |
| `restart` | Non         | Définir sur `true` pour redémarrer la session bash |

\*Obligatoire sauf si `restart` est utilisé

Pour gérer `restart: true`, tuez le processus shell, démarrez-en un nouveau et renvoyez un `tool_result` qui confirme le redémarrage. Une session redémarrée repart de zéro : le répertoire de travail, les variables d'environnement et tous les processus en cours d'exécution ont disparu.

<Accordion title="Exemple d'utilisation">
  Exécuter une commande :

  ```json
  {
    "command": "ls -la *.py"
  }
  ```

  Redémarrer la session :

  ```json
  {
    "restart": true
  }
  ```
</Accordion>

## Versions de l'outil

`bash_20250124` est la version actuelle de l'outil et ne nécessite aucun en-tête bêta. Tous les modèles à partir de Claude Sonnet 3.7 ([retiré](https://platform.claude.com/docs/fr/about-claude/model-deprecations)) l'acceptent, y compris tous les modèles Claude actuels.

La version d'origine `bash_20241022` fonctionne uniquement avec le modèle Claude Sonnet 3.5 d'octobre 2024 ([retiré](https://platform.claude.com/docs/fr/about-claude/model-deprecations)). Les requêtes qui l'utilisent nécessitent l'en-tête `anthropic-beta: computer-use-2024-10-22`, et le SDK ne l'expose que dans son espace de noms bêta. Les nouvelles intégrations doivent utiliser `bash_20250124`.

## Exemple : automatisation en plusieurs étapes

Claude peut enchaîner des commandes sur plusieurs appels d'outils pour accomplir une tâche en plusieurs étapes :

```text
User request:
"Install the requests library and create a simple Python script that
fetches a joke from an API, then run it."

Claude's tool uses:
1. Install package
   {"command": "pip install requests"}

2. Create script
   {"command": "cat > fetch_joke.py << 'EOF'\nimport requests\nresponse = requests.get('https://official-joke-api.appspot.com/random_joke')\njoke = response.json()\nprint(f\"Setup: {joke['setup']}\")\nprint(f\"Punchline: {joke['punchline']}\")\nEOF"}

3. Run script
   {"command": "python fetch_joke.py"}
```

La session conserve l'état entre les commandes, de sorte que les fichiers créés à l'étape 2 sont disponibles à l'étape 3.

## Implémenter l'outil bash

Claude détermine quelle commande exécuter. Votre application est responsable de tout le reste : le processus shell, le délai d'expiration et les contrôles de sécurité. Les étapes suivantes présentent une implémentation minimale.

<Steps>
  <Step title="Créer une session bash persistante">
    Démarrez un processus bash de longue durée et exécutez chaque commande à l'intérieur de celui-ci. Comme un pipe vers un processus actif ne signale jamais de fin de fichier, la session affiche une ligne sentinelle unique après chaque commande pour marquer l'endroit où se termine la sortie de cette commande :

    <CodeGroup exclude="shell">
      ```python Python
      import subprocess
      import uuid


      class BashSession:
          """A bash process that stays alive between commands so state persists."""

          def __init__(self):
              self.process = subprocess.Popen(
                  ["/bin/bash"],
                  stdin=subprocess.PIPE,
                  stdout=subprocess.PIPE,
                  stderr=subprocess.STDOUT,  # interleave errors with output, in order
                  start_new_session=True,  # own process group: a timeout can kill every child
                  text=True,
              )

          def execute_command(self, command):
              """Run a command in the session and return its output."""
              sentinel = f"__CLAUDE_BASH_DONE_{uuid.uuid4().hex}__"  # unique per call
              self.process.stdin.write(f"{command}\necho {sentinel}\n")
              self.process.stdin.flush()

              output = []
              for line in self.process.stdout:
                  if sentinel in line:  # this command's output is complete
                      break
                  output.append(line)
              return "".join(output)

          def restart(self):
              self.process.kill()
              self.process.wait()
              self.__init__()


      bash_session = BashSession()
      print(bash_session.execute_command("cd /tmp && pwd"))
      print(bash_session.execute_command("pwd"))  # still /tmp: the session kept its state
      ```

      ```typescript TypeScript
      import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
      import { createInterface, type Interface } from "node:readline";
      import { randomUUID } from "node:crypto";

      // Un processus bash qui reste actif entre les commandes afin que l'état persiste.
      class BashSession {
        process!: ChildProcessWithoutNullStreams;
        private lines!: Interface;

        constructor() {
          this.start();
        }

        private start(): void {
          this.process = spawn("/bin/bash", {
            detached: true // own process group: a timeout can kill every child
          });
          this.process.stdin.write("exec 2>&1\n"); // interleave errors with output, in order
          this.lines = createInterface({ input: this.process.stdout });
        }

        // Exécute une commande dans la session et renvoie sa sortie.
        executeCommand(command: string): Promise<string> {
          const sentinel = `__CLAUDE_BASH_DONE_${randomUUID()}__`; // unique per call
          const output: string[] = [];
          const result = new Promise<string>((resolve) => {
            const onLine = (line: string): void => {
              if (line.includes(sentinel)) {
                // la sortie de cette commande est complète
                this.lines.off("line", onLine);
                resolve(output.join(""));
              } else {
                output.push(`${line}\n`);
              }
            };
            this.lines.on("line", onLine);
          });
          this.process.stdin.write(`${command}\necho ${sentinel}\n`);
          return result;
        }

        restart(): void {
          this.process.kill("SIGKILL");
          this.lines.close();
          this.start();
        }
      }

      const session = new BashSession();
      console.log(await session.executeCommand("cd /tmp && pwd"));
      console.log(await session.executeCommand("pwd")); // still /tmp: the session kept its state
      session.process.stdin.end(); // closing stdin ends the shell so the script can exit
      ```

      ```csharp C#
      using System.Diagnostics;
      using System.Text;

      var session = new BashSession();
      Console.Write(session.ExecuteCommand("cd /tmp && pwd"));
      Console.Write(session.ExecuteCommand("pwd")); // still /tmp: the session kept its state

      // Un processus bash qui reste actif entre les commandes afin que l'état persiste.
      class BashSession
      {
          public Process Process { get; private set; }

          public BashSession()
          {
              Process = Start();
          }

          static Process Start()
          {
              var process = Process.Start(new ProcessStartInfo("/bin/bash")
              {
                  RedirectStandardInput = true,
                  RedirectStandardOutput = true
              })!;
              process.StandardInput.Write("exec 2>&1\n"); // interleave errors with output, in order
              process.StandardInput.Flush();
              return process;
          }

          // Exécute une commande dans la session et renvoie sa sortie.
          public string ExecuteCommand(string command)
          {
              var sentinel = $"__CLAUDE_BASH_DONE_{Guid.NewGuid():N}__"; // unique per call
              Process.StandardInput.Write($"{command}\necho {sentinel}\n");
              Process.StandardInput.Flush();

              var output = new StringBuilder();
              while (Process.StandardOutput.ReadLine() is string line)
              {
                  if (line.Contains(sentinel)) // this command's output is complete
                  {
                      break;
                  }
                  output.Append(line).Append('\n');
              }
              return output.ToString();
          }

          public void Restart()
          {
              Process.Kill(entireProcessTree: true);
              Process.WaitForExit();
              Process = Start();
          }
      }
      ```

      ```go Go
      import (
      	"bufio"
      	"crypto/rand"
      	"encoding/hex"
      	"fmt"
      	"io"
      	"log"
      	"os/exec"
      	"strings"
      	"syscall"
      )

      // BashSession est un processus bash qui reste actif entre les commandes afin que l'état persiste.
      type BashSession struct {
      	cmd    *exec.Cmd
      	stdin  io.WriteCloser
      	output *bufio.Reader
      }

      func NewBashSession() (*BashSession, error) {
      	cmd := exec.Command("/bin/bash")
      	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} // own process group: a timeout can kill every child
      	stdin, err := cmd.StdinPipe()
      	if err != nil {
      		return nil, err
      	}
      	stdout, err := cmd.StdoutPipe()
      	if err != nil {
      		return nil, err
      	}
      	cmd.Stderr = cmd.Stdout // interleave errors with output, in order
      	if err := cmd.Start(); err != nil {
      		return nil, err
      	}
      	return &BashSession{cmd: cmd, stdin: stdin, output: bufio.NewReader(stdout)}, nil
      }

      // ExecuteCommand exécute une commande dans la session et renvoie sa sortie.
      func (s *BashSession) ExecuteCommand(command string) string {
      	buf := make([]byte, 16)
      	rand.Read(buf)
      	sentinel := fmt.Sprintf("__CLAUDE_BASH_DONE_%s__", hex.EncodeToString(buf)) // unique per call
      	fmt.Fprintf(s.stdin, "%s\necho %s\n", command, sentinel)

      	var output strings.Builder
      	for {
      		line, err := s.output.ReadString('\n')
      		if err != nil || strings.Contains(line, sentinel) { // this command's output is complete
      			break
      		}
      		output.WriteString(line)
      	}
      	return output.String()
      }

      // Restart arrête le shell et démarre une nouvelle session à sa place.
      func (s *BashSession) Restart() error {
      	s.cmd.Process.Kill()
      	s.cmd.Wait()
      	fresh, err := NewBashSession()
      	if err != nil {
      		return err
      	}
      	*s = *fresh
      	return nil
      }

      func main() {
      	session, err := NewBashSession()
      	if err != nil {
      		log.Fatal(err)
      	}
      	fmt.Print(session.ExecuteCommand("cd /tmp && pwd"))
      	fmt.Print(session.ExecuteCommand("pwd")) // still /tmp: the session kept its state
      }
      ```

      ```java Java
      import java.io.BufferedReader;
      import java.io.BufferedWriter;
      import java.io.IOException;
      import java.io.InputStreamReader;
      import java.io.OutputStreamWriter;
      import java.util.UUID;

      // Un processus bash qui reste actif entre les commandes afin que l'état persiste.
      class BashSession {
          Process process;
          BufferedWriter stdin;
          BufferedReader output;

          BashSession() throws IOException {
              start();
          }

          void start() throws IOException {
              ProcessBuilder builder = new ProcessBuilder("/bin/bash");
              builder.redirectErrorStream(true); // interleave errors with output, in order
              process = builder.start();
              stdin = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
              output = new BufferedReader(new InputStreamReader(process.getInputStream()));
          }

          // Exécute une commande dans la session et renvoie sa sortie.
          String executeCommand(String command) throws IOException {
              String sentinel = "__CLAUDE_BASH_DONE_" + UUID.randomUUID() + "__"; // unique per call
              stdin.write(command + "\necho " + sentinel + "\n");
              stdin.flush();

              StringBuilder result = new StringBuilder();
              String line;
              while ((line = output.readLine()) != null) {
                  if (line.contains(sentinel)) { // this command's output is complete
                      break;
                  }
                  result.append(line).append("\n");
              }
              return result.toString();
          }

          void restart() throws IOException, InterruptedException {
              process.destroyForcibly();
              process.waitFor();
              start();
          }
      }

      void main() throws Exception {
          BashSession session = new BashSession();
          IO.println(session.executeCommand("cd /tmp && pwd"));
          IO.println(session.executeCommand("pwd")); // still /tmp: the session kept its state
      }
      ```

      ```php PHP
      // Un processus bash qui reste actif entre les commandes afin que l'état persiste.
      class BashSession
      {
          public $process;
          public $stdin;
          public $output;

          public function __construct()
          {
              $this->start();
          }

          private function start(): void
          {
              // setsid donne au shell son propre groupe de processus : un timeout peut tuer chaque enfant
              $this->process = proc_open(
                  ['setsid', '/bin/bash'],
                  [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['redirect', 1]], // interleave errors with output
                  $pipes
              );
              $this->stdin = $pipes[0];
              $this->output = $pipes[1];
          }

          // Exécute une commande dans la session et renvoie sa sortie.
          public function executeCommand(string $command): string
          {
              $sentinel = '__CLAUDE_BASH_DONE_' . bin2hex(random_bytes(16)) . '__'; // unique per call
              fwrite($this->stdin, "{$command}\necho {$sentinel}\n");
              fflush($this->stdin);

              $output = '';
              while (($line = fgets($this->output)) !== false) {
                  if (str_contains($line, $sentinel)) { // this command's output is complete
                      break;
                  }
                  $output .= $line;
              }
              return $output;
          }

          public function restart(): void
          {
              proc_terminate($this->process, 9);
              proc_close($this->process);
              $this->start();
          }
      }

      $session = new BashSession();
      echo $session->executeCommand("cd /tmp && pwd");
      echo $session->executeCommand("pwd"); // still /tmp: the session kept its state
      ```

      ```ruby Ruby
      require "open3"
      require "securerandom"

      # Un processus bash qui reste actif entre les commandes afin que l'état persiste.
      class BashSession
        attr_reader :output, :wait_thread

        def initialize
          start
        end

        # Exécute une commande dans la session et renvoie sa sortie.
        def execute_command(command)
          sentinel = "__CLAUDE_BASH_DONE_#{SecureRandom.hex(16)}__" # unique per call
          @stdin.write("#{command}\necho #{sentinel}\n")
          @stdin.flush

          output = +""
          @output.each_line do |line|
            break if line.include?(sentinel) # this command's output is complete

            output << line
          end
          output
        end

        def restart
          Process.kill("KILL", @wait_thread.pid)
          @wait_thread.join
          start
        end

        private

        def start
          # popen2e entrelace les erreurs avec la sortie, dans l'ordre ; pgroup donne au shell son
          # propre groupe de processus afin qu'un timeout puisse tuer chaque processus enfant
          @stdin, @output, @wait_thread = Open3.popen2e("/bin/bash", pgroup: true)
        end
      end

      session = BashSession.new
      puts session.execute_command("cd /tmp && pwd")
      puts session.execute_command("pwd") # still /tmp: the session kept its state
      ```
    </CodeGroup>

    La session entrelace stderr avec stdout, de sorte que les messages d'erreur apparaissent là où ils se sont produits. L'exemple omet ce dont une implémentation complète a également besoin : un délai d'expiration qui tue le shell et tous les processus qu'il a lancés lorsqu'une commande se bloque, puis redémarre la session. La bonne pratique [Utiliser des délais d'expiration pour les commandes](https://platform.claude.com/docs/fr/agents-and-tools/tool-use/bash-tool#follow-implementation-best-practices) montre une façon de l'ajouter.
  </Step>

  <Step title="Traiter les appels d'outils de Claude">
    Extrayez et exécutez les commandes à partir des réponses de Claude :

    <CodeGroup exclude="shell">
      ```python Python
      tool_results = []
      for content in response.content:
          if content.type == "tool_use" and content.name == "bash":
              if content.input.get("restart"):
                  bash_session.restart()
                  result = "Bash session restarted"
              else:
                  command = content.input.get("command")
                  result = bash_session.execute_command(command)

              # Un tool_result par bloc tool_use, tous renvoyés dans le message utilisateur suivant
              tool_results.append(
                  {"type": "tool_result", "tool_use_id": content.id, "content": result}
              )
      ```

      ```typescript TypeScript
      const toolResults: { type: string; tool_use_id: string; content: string }[] = [];
      for (const block of response.content) {
        if (block.type === "tool_use" && block.name === "bash") {
          let result: string;
          if (block.input.restart) {
            bashSession.restart();
            result = "Bash session restarted";
          } else {
            result = await bashSession.executeCommand(block.input.command ?? "");
          }

          // Un tool_result par bloc tool_use, tous renvoyés dans le message utilisateur suivant
          toolResults.push({ type: "tool_result", tool_use_id: block.id, content: result });
        }
      }
      ```

      ```csharp C#
      var toolResults = new List<ToolResultBlockParam>();
      foreach (var block in response.Content)
      {
          if (block.TryPickToolUse(out var toolUse) && toolUse.Name == "bash")
          {
              string result;
              if (toolUse.Input.TryGetValue("restart", out var restart) && restart.GetBoolean())
              {
                  bashSession.Restart();
                  result = "Bash session restarted";
              }
              else
              {
                  var command = toolUse.Input["command"].GetString() ?? "";
                  result = bashSession.ExecuteCommand(command);
              }

              // Un tool_result par bloc tool_use, tous renvoyés dans le message utilisateur suivant
              toolResults.Add(new ToolResultBlockParam { ToolUseID = toolUse.ID, Content = result });
          }
      }
      ```

      ```go Go
      var toolResults []anthropic.ContentBlockParamUnion
      for _, block := range response.Content {
      	if block.Type == "tool_use" && block.Name == "bash" {
      		var input struct {
      			Command string `json:"command"`
      			Restart bool   `json:"restart"`
      		}
      		if err := json.Unmarshal(block.Input, &input); err != nil {
      			log.Fatal(err)
      		}

      		var result string
      		if input.Restart {
      			bashSession.Restart()
      			result = "Bash session restarted"
      		} else {
      			result = bashSession.ExecuteCommand(input.Command)
      		}

      		// Un tool_result par bloc tool_use, tous renvoyés dans le message utilisateur suivant
      		toolResults = append(toolResults, anthropic.NewToolResultBlock(block.ID, result, false))
      	}
      }
      ```

      ```java Java
      List<Map<String, Object>> toolResults = new ArrayList<>();
      for (ContentBlock block : response.content()) {
          if (block.type().equals("tool_use") && block.name().equals("bash")) {
              String result;
              if (Boolean.TRUE.equals(block.input().get("restart"))) {
                  bashSession.restart();
                  result = "Bash session restarted";
              } else {
                  String command = (String) block.input().get("command");
                  result = bashSession.executeCommand(command);
              }

              // Un tool_result par bloc tool_use, tous renvoyés dans le message utilisateur suivant
              toolResults.add(Map.of("type", "tool_result", "tool_use_id", block.id(), "content", result));
          }
      }
      ```

      ```php PHP
      $toolResults = [];
      foreach ($response->content as $block) {
          if ($block->type === 'tool_use' && $block->name === 'bash') {
              if (!empty($block->input['restart'])) {
                  $bashSession->restart();
                  $result = 'Bash session restarted';
              } else {
                  $result = $bashSession->executeCommand($block->input['command']);
              }

              // Un tool_result par bloc tool_use, tous renvoyés dans le message utilisateur suivant
              $toolResults[] = ['type' => 'tool_result', 'tool_use_id' => $block->id, 'content' => $result];
          }
      }
      ```

      ```ruby Ruby
      tool_results = []
      response.content.each do |block|
        next unless block.type == :tool_use && block.name == "bash"

        result =
          if block.input[:restart]
            bash_session.restart
            "Bash session restarted"
          else
            bash_session.execute_command(block.input[:command])
          end

        # Un tool_result par bloc tool_use, tous renvoyés dans le message utilisateur suivant
        tool_results << {type: "tool_result", tool_use_id: block.id, content: result}
      end
      ```
    </CodeGroup>
  </Step>

  <Step title="Renvoyer le résultat à Claude">
    Renvoyez le `tool_result` dans un message `user` qui poursuit la même conversation. Claude demande une autre commande dans la même session ou termine sa réponse :

    <CodeGroup>
      ```bash cURL
      curl https://api.anthropic.com/v1/messages \
        -H "content-type: application/json" \
        -H "x-api-key: $ANTHROPIC_API_KEY" \
        -H "anthropic-version: 2023-06-01" \
        -d '{
          "model": "claude-opus-5-5",
          "max_tokens": 1024,
          "tools": [
            {
              "type": "bash_20250124",
              "name": "bash"
            }
          ],
          "messages": [
            {
              "role": "user",
              "content": "List all Python files in the current directory."
            },
            {
              "role": "assistant",
              "content": [
                {
                  "type": "tool_use",
                  "id": "toolu_01A09q90qw90lq917835lq9",
                  "name": "bash",
                  "input": {
                    "command": "ls *.py"
                  }
                }
              ]
            },
            {
              "role": "user",
              "content": [
                {
                  "type": "tool_result",
                  "tool_use_id": "toolu_01A09q90qw90lq917835lq9",
                  "content": "analysis.py\nprocess_data.py\n"
                }
              ]
            }
          ]
        }'
      ```

      ```bash CLI
      ant messages create <<'YAML'
      model: claude-opus-5-5
      max_tokens: 1024
      tools:
        - type: bash_20250124
          name: bash
      messages:
        - role: user
          content: List all Python files in the current directory.
        - role: assistant
          content:
            - type: tool_use
              id: toolu_01A09q90qw90lq917835lq9
              name: bash
              input:
                command: ls *.py
        - role: user
          content:
            - type: tool_result
              tool_use_id: toolu_01A09q90qw90lq917835lq9
              content: |
                analysis.py
                process_data.py
      YAML
      ```

      ```python Python
      client = anthropic.Anthropic()

      response = client.messages.create(
          model="claude-opus-5-5",
          max_tokens=1024,
          tools=[{"type": "bash_20250124", "name": "bash"}],
          messages=[
              {"role": "user", "content": "List all Python files in the current directory."},
              {
                  "role": "assistant",
                  "content": [
                      {
                          "type": "tool_use",
                          "id": "toolu_01A09q90qw90lq917835lq9",
                          "name": "bash",
                          "input": {"command": "ls *.py"},
                      }
                  ],
              },
              {
                  "role": "user",
                  "content": [
                      {
                          "type": "tool_result",
                          "tool_use_id": "toolu_01A09q90qw90lq917835lq9",
                          "content": "analysis.py\nprocess_data.py\n",
                      }
                  ],
              },
          ],
      )

      print(response.content)
      ```

      ```typescript TypeScript
      const client = new Anthropic();

      const response = await client.messages.create({
        model: "claude-opus-5-5",
        max_tokens: 1024,
        tools: [{ type: "bash_20250124", name: "bash" }],
        messages: [
          {
            role: "user",
            content: "List all Python files in the current directory."
          },
          {
            role: "assistant",
            content: [
              {
                type: "tool_use",
                id: "toolu_01A09q90qw90lq917835lq9",
                name: "bash",
                input: { command: "ls *.py" }
              }
            ]
          },
          {
            role: "user",
            content: [
              {
                type: "tool_result",
                tool_use_id: "toolu_01A09q90qw90lq917835lq9",
                content: "analysis.py\nprocess_data.py\n"
              }
            ]
          }
        ]
      });

      console.log(response.content);
      ```

      ```csharp C#
      var client = new AnthropicClient();

      var response = await client.Messages.Create(
          new()
          {
              Model = Model.ClaudeOpus5_5,
              MaxTokens = 1024,
              Tools = [new ToolBash20250124()],
              Messages =
              [
                  new()
                  {
                      Role = Role.User,
                      Content = "List all Python files in the current directory.",
                  },
                  new()
                  {
                      Role = Role.Assistant,
                      Content = new MessageParamContent(new List<ContentBlockParam>
                      {
                          new ContentBlockParam(new ToolUseBlockParam()
                          {
                              ID = "toolu_01A09q90qw90lq917835lq9",
                              Name = "bash",
                              Input = new Dictionary<string, JsonElement>
                              {
                                  ["command"] = JsonSerializer.SerializeToElement("ls *.py"),
                              },
                          }),
                      }),
                  },
                  new()
                  {
                      Role = Role.User,
                      Content = new MessageParamContent(new List<ContentBlockParam>
                      {
                          new ContentBlockParam(new ToolResultBlockParam()
                          {
                              ToolUseID = "toolu_01A09q90qw90lq917835lq9",
                              Content = "analysis.py\nprocess_data.py\n",
                          }),
                      }),
                  },
              ],
          }
      );

      Console.WriteLine(response);
      ```

      ```go Go
      client := anthropic.NewClient()

      response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
      	Model:     anthropic.ModelClaudeOpus5_5,
      	MaxTokens: 1024,
      	Tools: []anthropic.ToolUnionParam{
      		{OfBashTool20250124: &anthropic.ToolBash20250124Param{}},
      	},
      	Messages: []anthropic.MessageParam{
      		anthropic.NewUserMessage(anthropic.NewTextBlock("List all Python files in the current directory.")),
      		anthropic.NewAssistantMessage(
      			anthropic.NewToolUseBlock(
      				"toolu_01A09q90qw90lq917835lq9",
      				map[string]any{"command": "ls *.py"},
      				"bash",
      			),
      		),
      		anthropic.NewUserMessage(
      			anthropic.NewToolResultBlock(
      				"toolu_01A09q90qw90lq917835lq9",
      				"analysis.py\nprocess_data.py\n",
      				false,
      			),
      		),
      	},
      })
      if err != nil {
      	log.Fatal(err)
      }
      fmt.Println(response.Content)
      ```

      ```java Java
      import com.anthropic.core.JsonValue;
      import com.anthropic.models.messages.ContentBlockParam;
      // ...
      import com.anthropic.models.messages.ToolBash20250124;
      import com.anthropic.models.messages.ToolResultBlockParam;
      import com.anthropic.models.messages.ToolUseBlockParam;
      // ...
      void main() {
          AnthropicClient client = AnthropicOkHttpClient.fromEnv();

          MessageCreateParams params = MessageCreateParams.builder()
              .model(Model.CLAUDE_OPUS_5_5)
              .maxTokens(1024)
              .addTool(ToolBash20250124.builder().build())
              .addUserMessage("List all Python files in the current directory.")
              .addAssistantMessageOfBlockParams(
                  List.of(
                      ContentBlockParam.ofToolUse(
                          ToolUseBlockParam.builder()
                              .id("toolu_01A09q90qw90lq917835lq9")
                              .name("bash")
                              .input(
                                  ToolUseBlockParam.Input.builder()
                                      .putAdditionalProperty("command", JsonValue.from("ls *.py"))
                                      .build()
                              )
                              .build()
                      )
                  )
              )
              .addUserMessageOfBlockParams(
                  List.of(
                      ContentBlockParam.ofToolResult(
                          ToolResultBlockParam.builder()
                              .toolUseId("toolu_01A09q90qw90lq917835lq9")
                              .content("analysis.py\nprocess_data.py\n")
                              .build()
                      )
                  )
              )
              .build();

          Message response = client.messages().create(params);
          IO.println(response.content());
      }
      ```

      ```php PHP
      use Anthropic\Messages\ToolBash20250124;

      $client = new Client();

      $response = $client->messages->create(
          model: 'claude-opus-5-5',
          maxTokens: 1024,
          tools: [new ToolBash20250124()],
          messages: [
              ['role' => 'user', 'content' => 'List all Python files in the current directory.'],
              [
                  'role' => 'assistant',
                  'content' => [
                      [
                          'type' => 'tool_use',
                          'id' => 'toolu_01A09q90qw90lq917835lq9',
                          'name' => 'bash',
                          'input' => ['command' => 'ls *.py'],
                      ],
                  ],
              ],
              [
                  'role' => 'user',
                  'content' => [
                      [
                          'type' => 'tool_result',
                          'tool_use_id' => 'toolu_01A09q90qw90lq917835lq9',
                          'content' => "analysis.py\nprocess_data.py\n",
                      ],
                  ],
              ],
          ],
      );

      print_r($response->content);
      ```

      ```ruby Ruby
      client = Anthropic::Client.new

      response = client.messages.create(
        model: "claude-opus-5-5",
        max_tokens: 1024,
        tools: [{type: "bash_20250124", name: "bash"}],
        messages: [
          {role: "user", content: "List all Python files in the current directory."},
          {
            role: "assistant",
            content: [
              {
                type: "tool_use",
                id: "toolu_01A09q90qw90lq917835lq9",
                name: "bash",
                input: {command: "ls *.py"}
              }
            ]
          },
          {
            role: "user",
            content: [
              {
                type: "tool_result",
                tool_use_id: "toolu_01A09q90qw90lq917835lq9",
                content: "analysis.py\nprocess_data.py\n"
              }
            ]
          }
        ]
      )

      puts response.content
      ```
    </CodeGroup>

    Répétez le cycle d'exécution et de renvoi tant que `stop_reason` vaut `tool_use`. Pour la boucle complète, consultez [Gérer les résultats des outils clients](https://platform.claude.com/docs/fr/agents-and-tools/tool-use/handle-tool-calls#handling-results-from-client-tools).
  </Step>

  <Step title="Mettre en place des mesures de sécurité">
    Ajoutez des validations et des restrictions. Utilisez une « allowlist » (liste d'autorisation) plutôt qu'une « blocklist » (liste de blocage) : une liste de blocage laisse passer toute commande qu'elle n'a pas anticipée. L'exemple rejette également les opérateurs shell qui apparaissent sous forme de mots distincts :

    <CodeGroup exclude="shell">
      ```python Python
      import shlex

      ALLOWED_COMMANDS = {"ls", "cat", "echo", "pwd", "grep", "find", "wc", "head", "tail"}
      SHELL_OPERATORS = {"&&", "||", "|", ";", "&", ">", "<", ">>"}


      def validate_command(command):
          # Autoriser uniquement les commandes d'une liste d'autorisation explicite
          try:
              tokens = shlex.split(command)
          except ValueError:
              return False, "Could not parse command"

          if not tokens:
              return False, "Empty command"

          executable = tokens[0]
          if executable not in ALLOWED_COMMANDS:
              return False, f"Command '{executable}' is not in the allowlist"

          # Rejeter les opérateurs shell écrits comme des mots séparés
          for token in tokens[1:]:
              if token in SHELL_OPERATORS or token.startswith(("$", "`")):
                  return False, f"Shell operator '{token}' is not allowed"

          return True, None
      ```

      ```typescript TypeScript
      const ALLOWED_COMMANDS = new Set([
        "ls",
        "cat",
        "echo",
        "pwd",
        "grep",
        "find",
        "wc",
        "head",
        "tail"
      ]);
      const SHELL_OPERATORS = new Set(["&&", "||", "|", ";", "&", ">", "<", ">>"]);

      function validateCommand(command: string): { ok: boolean; reason?: string } {
        // Découpage sur les espaces : suffisant pour une vérification de type garde-fou
        const tokens = command.split(/\s+/).filter((token) => token.length > 0);
        if (tokens.length === 0) {
          return { ok: false, reason: "Empty command" };
        }

        // N'autoriser que les commandes d'une liste d'autorisation explicite
        const executable = tokens[0];
        if (!ALLOWED_COMMANDS.has(executable)) {
          return { ok: false, reason: `Command '${executable}' is not in the allowlist` };
        }

        // Rejeter les opérateurs shell écrits comme des mots séparés
        for (const token of tokens.slice(1)) {
          const bare = token.replace(/^["']+/, ""); // a quoted token can still smuggle an expansion
          if (SHELL_OPERATORS.has(token) || bare.startsWith("$") || bare.startsWith("`")) {
            return { ok: false, reason: `Shell operator '${token}' is not allowed` };
          }
        }

        return { ok: true };
      }
      ```

      ```csharp C#
      var allowedCommands = new HashSet<string>
      {
          "ls", "cat", "echo", "pwd", "grep", "find", "wc", "head", "tail"
      };
      var shellOperators = new HashSet<string> { "&&", "||", "|", ";", "&", ">", "<", ">>" };

      (bool Ok, string? Reason) ValidateCommand(string command)
      {
          // Découpage sur les espaces : suffisant pour une vérification de type garde-fou
          var tokens = command.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
          if (tokens.Length == 0)
          {
              return (false, "Empty command");
          }

          // N'autoriser que les commandes d'une liste d'autorisation explicite
          var executable = tokens[0];
          if (!allowedCommands.Contains(executable))
          {
              return (false, $"Command '{executable}' is not in the allowlist");
          }

          // Rejeter les opérateurs shell écrits comme des mots séparés
          foreach (var token in tokens.Skip(1))
          {
              var bare = token.TrimStart('"', '\''); // a quoted token can still smuggle an expansion
              if (shellOperators.Contains(token) || bare.StartsWith('$') || bare.StartsWith('`'))
              {
                  return (false, $"Shell operator '{token}' is not allowed");
              }
          }

          return (true, null);
      }
      ```

      ```go Go
      var allowedCommands = map[string]bool{
      	"ls": true, "cat": true, "echo": true, "pwd": true, "grep": true,
      	"find": true, "wc": true, "head": true, "tail": true,
      }

      var shellOperators = map[string]bool{
      	"&&": true, "||": true, "|": true, ";": true, "&": true,
      	">": true, "<": true, ">>": true,
      }

      func validateCommand(command string) (bool, string) {
      	// Découpage sur les espaces : suffisant pour une vérification de type garde-fou
      	tokens := strings.Fields(command)
      	if len(tokens) == 0 {
      		return false, "Empty command"
      	}

      	// N'autoriser que les commandes d'une liste d'autorisation explicite
      	executable := tokens[0]
      	if !allowedCommands[executable] {
      		return false, fmt.Sprintf("Command %q is not in the allowlist", executable)
      	}

      	// Rejeter les opérateurs shell écrits comme des mots séparés
      	for _, token := range tokens[1:] {
      		bare := strings.TrimLeft(token, `"'`) // a quoted token can still smuggle an expansion
      		if shellOperators[token] || strings.HasPrefix(bare, "$") || strings.HasPrefix(bare, "`") {
      			return false, fmt.Sprintf("Shell operator %q is not allowed", token)
      		}
      	}

      	return true, ""
      }
      ```

      ```java Java
      import java.util.List;
      import java.util.Set;

      static final Set<String> ALLOWED_COMMANDS =
          Set.of("ls", "cat", "echo", "pwd", "grep", "find", "wc", "head", "tail");
      static final Set<String> SHELL_OPERATORS = Set.of("&&", "||", "|", ";", "&", ">", "<", ">>");

      record Validation(boolean ok, String reason) {}

      Validation validateCommand(String command) {
          // Découpage sur les espaces : suffisant pour une vérification de type garde-fou
          List<String> tokens = List.of(command.trim().split("\\s+"));
          if (tokens.size() == 1 && tokens.get(0).isEmpty()) {
              return new Validation(false, "Empty command");
          }

          // N'autoriser que les commandes d'une liste d'autorisation explicite
          String executable = tokens.get(0);
          if (!ALLOWED_COMMANDS.contains(executable)) {
              return new Validation(false, "Command '" + executable + "' is not in the allowlist");
          }

          // Rejeter les opérateurs shell écrits comme des mots séparés
          for (String token : tokens.subList(1, tokens.size())) {
              String bare = token.replaceFirst("^[\"']+", ""); // a quoted token can still smuggle an expansion
              if (SHELL_OPERATORS.contains(token) || bare.startsWith("$") || bare.startsWith("`")) {
                  return new Validation(false, "Shell operator '" + token + "' is not allowed");
              }
          }

          return new Validation(true, null);
      }
      ```

      ```php PHP
      const ALLOWED_COMMANDS = ['ls', 'cat', 'echo', 'pwd', 'grep', 'find', 'wc', 'head', 'tail'];
      const SHELL_OPERATORS = ['&&', '||', '|', ';', '&', '>', '<', '>>'];

      function validateCommand(string $command): array
      {
          // Découpage sur les espaces : suffisant pour une vérification de type garde-fou
          $tokens = preg_split('/\\s+/', trim($command), -1, PREG_SPLIT_NO_EMPTY);
          if ($tokens === false || $tokens === []) {
              return [false, 'Empty command'];
          }

          // N'autoriser que les commandes d'une liste d'autorisation explicite
          $executable = $tokens[0];
          if (!in_array($executable, ALLOWED_COMMANDS, true)) {
              return [false, "Command '{$executable}' is not in the allowlist"];
          }

          // Rejeter les opérateurs shell écrits comme des mots séparés
          foreach (array_slice($tokens, 1) as $token) {
              $bare = ltrim($token, '"\''); // a quoted token can still smuggle an expansion
              if (in_array($token, SHELL_OPERATORS, true) || str_starts_with($bare, '$') || str_starts_with($bare, '`')) {
                  return [false, "Shell operator '{$token}' is not allowed"];
              }
          }

          return [true, null];
      }
      ```

      ```ruby Ruby
      require "shellwords"

      ALLOWED_COMMANDS = %w[ls cat echo pwd grep find wc head tail].freeze
      SHELL_OPERATORS = ["&&", "||", "|", ";", "&", ">", "<", ">>"].freeze

      def validate_command(command)
        # Autoriser uniquement les commandes d'une liste d'autorisation explicite
        begin
          tokens = Shellwords.split(command)
        rescue ArgumentError
          return [false, "Could not parse command"]
        end

        return [false, "Empty command"] if tokens.empty?

        executable = tokens[0]
        unless ALLOWED_COMMANDS.include?(executable)
          return [false, "Command '#{executable}' is not in the allowlist"]
        end

        # Rejeter les opérateurs shell écrits comme des mots séparés
        tokens[1..].each do |token|
          if SHELL_OPERATORS.include?(token) || token.start_with?("$", "`")
            return [false, "Shell operator '#{token}' is not allowed"]
          end
        end

        [true, nil]
      end
      ```
    </CodeGroup>

    Cette vérification est un garde-fou contre les erreurs évidentes, pas une frontière de contrôle. Elle rejette l'enchaînement avec espaces (`&&`), les pipes et les redirections que les autres exemples de cette page utilisent. Elle ne détecte pas un opérateur collé à un mot, comme `cat data.txt|grep x`, car le tokenizer conserve `data.txt|grep` dans un seul jeton. Décidez quelles commandes et quels opérateurs votre application autorise. Le véritable contrôle est l'isolation : exécutez toute la session dans un conteneur ou une machine virtuelle (consultez [Sécurité](https://platform.claude.com/docs/fr/agents-and-tools/tool-use/bash-tool#security)).
  </Step>
</Steps>

### Gérer les erreurs

Lorsqu'une commande échoue ou que la session se rompt, indiquez à Claude ce qui s'est passé. Renvoyez le message comme contenu du `tool_result` et définissez `is_error` sur `true`, ce qui marque l'appel d'outil comme ayant échoué. Consultez [Gestion des erreurs avec is\_error](https://platform.claude.com/docs/fr/agents-and-tools/tool-use/handle-tool-calls#handling-errors-with-is-error).

<AccordionGroup>
  <Accordion title="Délai d'exécution de la commande dépassé">
    Si une commande prend trop de temps à s'exécuter :

    ```json
    {
      "role": "user",
      "content": [
        {
          "type": "tool_result",
          "tool_use_id": "toolu_01A09q90qw90lq917835lq9",
          "content": "Error: command did not finish within 30 seconds",
          "is_error": true
        }
      ]
    }
    ```
  </Accordion>

  <Accordion title="Commande introuvable">
    Si une commande n'existe pas :

    ```json
    {
      "role": "user",
      "content": [
        {
          "type": "tool_result",
          "tool_use_id": "toolu_01A09q90qw90lq917835lq9",
          "content": "bash: nonexistentcommand: command not found",
          "is_error": true
        }
      ]
    }
    ```
  </Accordion>

  <Accordion title="Permission refusée">
    En cas de problèmes de permissions :

    ```json
    {
      "role": "user",
      "content": [
        {
          "type": "tool_result",
          "tool_use_id": "toolu_01A09q90qw90lq917835lq9",
          "content": "bash: /root/sensitive-file: Permission denied",
          "is_error": true
        }
      ]
    }
    ```
  </Accordion>
</AccordionGroup>

### Suivre les bonnes pratiques d'implémentation

<AccordionGroup>
  <Accordion title="Utiliser des délais d'expiration pour les commandes">
    Une commande qui ne se termine jamais, comme une commande qui attend une saisie, bloque la session indéfiniment car sa ligne sentinelle n'arrive jamais. Donnez une échéance à chaque commande. Lorsque l'échéance est dépassée, arrêtez le shell et tout ce que la commande a lancé, puis redémarrez la session :

    <CodeGroup exclude="shell">
      ```python Python
      import concurrent.futures
      import os
      import signal


      def execute_with_timeout(session, command, timeout=30):
          """Run a command in the session, replacing the session if the command hangs."""
          with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
              future = pool.submit(session.execute_command, command)
              try:
                  return future.result(timeout=timeout)
              except concurrent.futures.TimeoutError:
                  # Le groupe comprend le shell et chaque processus lancé par la commande
                  os.killpg(session.process.pid, signal.SIGKILL)
                  session.restart()
                  return f"Error: command did not finish within {timeout} seconds"
      ```

      ```typescript TypeScript
      // Exécute une commande dans la session, en remplaçant la session si la commande se bloque.
      async function executeWithTimeout(
        session: BashSession,
        command: string,
        timeoutMs = 30000
      ): Promise<string> {
        let timer: NodeJS.Timeout | undefined;
        const timedOut = new Promise<never>((_, reject) => {
          timer = setTimeout(() => reject(new Error("timeout")), timeoutMs);
        });
        try {
          return await Promise.race([session.executeCommand(command), timedOut]);
        } catch {
          // Le groupe comprend le shell et tous les processus lancés par la commande
          if (session.process.pid !== undefined) {
            process.kill(-session.process.pid, "SIGKILL");
          }
          session.restart();
          return `Error: command did not finish within ${timeoutMs / 1000} seconds`;
        } finally {
          clearTimeout(timer);
        }
      }
      ```

      ```csharp C#
      using System.Diagnostics;

      // Exécute une commande dans la session, en remplaçant la session si la commande se bloque.
      static string ExecuteWithTimeout(BashSession session, string command, int timeoutSeconds = 30)
      {
          var work = Task.Run(() => session.ExecuteCommand(command));
          if (work.Wait(TimeSpan.FromSeconds(timeoutSeconds)))
          {
              return work.Result;
          }

          // Arrête le shell et tous les processus qu'il a lancés, puis démarre une nouvelle session
          session.Process.Kill(entireProcessTree: true);
          session.Restart();
          return $"Error: command did not finish within {timeoutSeconds} seconds";
      }
      ```

      ```go Go
      // executeWithTimeout exécute une commande et remplace la session si la commande se bloque.
      func executeWithTimeout(session *BashSession, command string, timeoutSeconds int) string {
      	done := make(chan string, 1)
      	go func() { done <- session.ExecuteCommand(command) }()

      	select {
      	case result := <-done:
      		return result
      	case <-time.After(time.Duration(timeoutSeconds) * time.Second):
      		// Le groupe comprend le shell et tous les processus lancés par la commande.
      		syscall.Kill(-session.cmd.Process.Pid, syscall.SIGKILL)
      		session.Restart()
      		return fmt.Sprintf("Error: command did not finish within %d seconds", timeoutSeconds)
      	}
      }
      ```

      ```java Java
      // Exécute une commande dans la session, en remplaçant la session si la commande se bloque.
      String executeWithTimeout(BashSession session, String command, int timeoutSeconds) throws Exception {
          ExecutorService pool = Executors.newSingleThreadExecutor();
          try {
              Future<String> future = pool.submit(() -> session.executeCommand(command));
              return future.get(timeoutSeconds, TimeUnit.SECONDS);
          } catch (TimeoutException e) {
              // Arrête le shell et tous les processus qu'il a lancés, puis démarre une nouvelle session
              session.process.descendants().forEach(ProcessHandle::destroyForcibly);
              session.process.destroyForcibly();
              session.restart();
              return "Error: command did not finish within " + timeoutSeconds + " seconds";
          } finally {
              pool.shutdownNow();
          }
      }
      ```

      ```php PHP
      // Exécute une commande mais abandonne si elle ne se termine pas avant l'échéance. PHP bloque sur
      // les lectures de pipe, donc l'échéance est gérée dans la boucle de lecture : stream_select() attend
      // une sortie lisible avant chaque fgets() afin que la boucle puisse vérifier l'échéance.
      function executeWithTimeout(BashSession $session, string $command, int $timeout = 30): string
      {
          $sentinel = '__CLAUDE_BASH_DONE_' . bin2hex(random_bytes(16)) . '__'; // unique per call
          fwrite($session->stdin, "{$command}\necho {$sentinel}\n");
          fflush($session->stdin);

          $deadline = microtime(true) + $timeout;
          $output = '';
          while (microtime(true) < $deadline) {
              $read = [$session->output];
              $write = null;
              $except = null;
              if (stream_select($read, $write, $except, 1) === 0) {
                  continue; // no output yet; check the deadline again
              }
              $line = fgets($session->output);
              if ($line === false || str_contains($line, $sentinel)) {
                  return $output; // this command's output is complete
              }
              $output .= $line;
          }

          // Le groupe comprend le shell et tous les processus lancés par la commande
          posix_kill(-proc_get_status($session->process)['pid'], 9); // 9 = SIGKILL
          $session->restart();
          return "Error: command did not finish within {$timeout} seconds";
      }
      ```

      ```ruby Ruby
      require "timeout"

      # Exécute une commande dans la session, en remplaçant la session si la commande se bloque.
      def execute_with_timeout(session, command, timeout: 30)
        Timeout.timeout(timeout) { session.execute_command(command) }
      rescue Timeout::Error
        # Le groupe comprend le shell et tous les processus lancés par la commande
        Process.kill("KILL", -session.wait_thread.pid)
        session.restart
        "Error: command did not finish within #{timeout} seconds"
      end
      ```
    </CodeGroup>

    L'arrêt forcé stoppe la commande bloquée et tout ce qu'elle a lancé. Renvoyez le message sous forme de `tool_result` d'erreur (consultez [Gérer les erreurs](https://platform.claude.com/docs/fr/agents-and-tools/tool-use/bash-tool#handle-errors)), ce qui marque l'appel d'outil comme ayant échoué.
  </Accordion>

  <Accordion title="Maintenir l'état de la session">
    Gardez la session bash persistante pour conserver les variables d'environnement et le répertoire de travail :

    <CodeGroup exclude="shell">
      ```python Python
      # Les commandes exécutées dans la même session conservent leur état
      commands = [
          "cd /tmp",
          "echo 'Hello' > test.txt",
          "cat test.txt",  # The session is still in /tmp
      ]
      ```

      ```typescript TypeScript
      // Les commandes exécutées dans la même session conservent leur état
      const commands = [
        "cd /tmp",
        "echo 'Hello' > test.txt",
        "cat test.txt" // The session is still in /tmp
      ];
      ```

      ```csharp C#
      // Les commandes exécutées dans la même session conservent leur état
      string[] commands =
      [
          "cd /tmp",
          "echo 'Hello' > test.txt",
          "cat test.txt", // The session is still in /tmp
      ];
      ```

      ```go Go
      // Les commandes exécutées dans la même session conservent leur état
      commands := []string{
      	"cd /tmp",
      	"echo 'Hello' > test.txt",
      	"cat test.txt", // The session is still in /tmp
      }
      ```

      ```java Java
      // Les commandes exécutées dans la même session conservent leur état
      List<String> commands = List.of(
          "cd /tmp",
          "echo 'Hello' > test.txt",
          "cat test.txt" // The session is still in /tmp
      );
      ```

      ```php PHP
      // Les commandes exécutées dans la même session conservent leur état
      $commands = [
          'cd /tmp',
          "echo 'Hello' > test.txt",
          'cat test.txt', // The session is still in /tmp
      ];
      ```

      ```ruby Ruby
      # Les commandes exécutées dans la même session conservent leur état
      commands = [
        "cd /tmp",
        "echo 'Hello' > test.txt",
        "cat test.txt" # The session is still in /tmp
      ]
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Gérer les sorties volumineuses">
    Tronquez les sorties volumineuses pour éviter les problèmes de limite de tokens :

    <CodeGroup exclude="shell">
      ```python Python
      def truncate_output(output, max_lines=100):
          lines = output.split("\n")
          if len(lines) > max_lines:
              truncated = "\n".join(lines[:max_lines])
              return f"{truncated}\n\n... Output truncated ({len(lines)} total lines) ..."
          return output
      ```

      ```typescript TypeScript
      function truncateOutput(output: string, maxLines = 100): string {
        const lines = output.split("\n");
        if (lines.length > maxLines) {
          const truncated = lines.slice(0, maxLines).join("\n");
          return `${truncated}\n\n... Output truncated (${lines.length} total lines) ...`;
        }
        return output;
      }
      ```

      ```csharp C#
      string TruncateOutput(string output, int maxLines = 100)
      {
          var lines = output.Split('\n');
          if (lines.Length > maxLines)
          {
              var truncated = string.Join("\n", lines.Take(maxLines));
              return $"{truncated}\n\n... Output truncated ({lines.Length} total lines) ...";
          }
          return output;
      }
      ```

      ```go Go
      func truncateOutput(output string, maxLines int) string {
      	lines := strings.Split(output, "\n")
      	if len(lines) > maxLines {
      		truncated := strings.Join(lines[:maxLines], "\n")
      		return fmt.Sprintf("%s\n\n... Output truncated (%d total lines) ...", truncated, len(lines))
      	}
      	return output
      }
      ```

      ```java Java
      String truncateOutput(String output, int maxLines) {
          String[] lines = output.split("\n", -1);
          if (lines.length > maxLines) {
              String truncated = String.join("\n", Arrays.copyOf(lines, maxLines));
              return truncated + "\n\n... Output truncated (" + lines.length + " total lines) ...";
          }
          return output;
      }
      ```

      ```php PHP
      function truncateOutput(string $output, int $maxLines = 100): string
      {
          $lines = explode("\n", $output);
          if (count($lines) > $maxLines) {
              $truncated = implode("\n", array_slice($lines, 0, $maxLines));
              return "{$truncated}\n\n... Output truncated (" . count($lines) . ' total lines) ...';
          }
          return $output;
      }
      ```

      ```ruby Ruby
      def truncate_output(output, max_lines: 100)
        lines = output.split("\n", -1)
        return output unless lines.length > max_lines

        truncated = lines.first(max_lines).join("\n")
        "#{truncated}\n\n... Output truncated (#{lines.length} total lines) ..."
      end
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Journaliser toutes les commandes">
    Conservez une piste d'audit. Faites passer chaque commande par un wrapper unique qui enregistre la commande avant son exécution et la sortie une fois qu'elle est terminée. Une commande qui se bloque ou rompt la session laisse tout de même une trace :

    <CodeGroup exclude="shell">
      ```python Python
      import logging

      logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s")


      def execute_and_log(session, command):
          """Run a command in the session and keep an audit record of it."""
          logging.info("command=%r", command)
          output = session.execute_command(command)
          logging.info("output=%r", output[:200])  # first 200 characters
          return output
      ```

      ```typescript TypeScript
      // Exécuter une commande dans la session et en conserver une trace d'audit.
      async function executeAndLog(session: BashSession, command: string): Promise<string> {
        console.error(`command=${JSON.stringify(command)}`);
        const output = await session.executeCommand(command);
        console.error(`output=${JSON.stringify(output.slice(0, 200))}`); // first 200 characters
        return output;
      }
      ```

      ```csharp C#
      // Exécute une commande dans la session et en conserve une trace d'audit.
      static string ExecuteAndLog(BashSession session, string command)
      {
          Console.Error.WriteLine($"command={command}");
          var output = session.ExecuteCommand(command);
          Console.Error.WriteLine($"output={output[..Math.Min(output.Length, 200)]}"); // first 200 characters
          return output;
      }
      ```

      ```go Go
      // executeAndLog exécute une commande dans la session et en conserve une trace d'audit.
      func executeAndLog(session *BashSession, command string) string {
      	log.Printf("command=%q", command)
      	output := session.ExecuteCommand(command)
      	log.Printf("output=%q", output[:min(len(output), 200)]) // first 200 characters
      	return output
      }
      ```

      ```java Java
      static final Logger AUDIT = Logger.getLogger("bash-audit");

      // Exécuter une commande dans la session et en conserver une trace d'audit.
      String executeAndLog(BashSession session, String command) throws IOException {
          AUDIT.info("command=" + command);
          String output = session.executeCommand(command);
          AUDIT.info("output=" + output.substring(0, Math.min(output.length(), 200))); // first 200 characters
          return output;
      }
      ```

      ```php PHP
      // Exécute une commande dans la session et en conserve une trace d'audit.
      function executeAndLog(BashSession $session, string $command): string
      {
          error_log("command={$command}");
          $output = $session->executeCommand($command);
          error_log('output=' . substr($output, 0, 200)); // first 200 characters
          return $output;
      }
      ```

      ```ruby Ruby
      require "logger"

      AUDIT = Logger.new($stderr)

      # Exécuter une commande dans la session et en conserver une trace d'audit.
      def execute_and_log(session, command)
        AUDIT.info("command=#{command.inspect}")
        output = session.execute_command(command)
        AUDIT.info("output=#{output[0, 200].inspect}") # first 200 characters
        output
      end
      ```
    </CodeGroup>

    Les enregistrements sont envoyés vers `stderr` par défaut ; dirigez-les vers un fichier ou votre pipeline de journalisation pour les conserver. Incluez tout ce qui relie l'enregistrement à la requête dans votre application, comme l'utilisateur final et le `tool_use_id`.
  </Accordion>
</AccordionGroup>

## Sécurité

<Warning>
  Votre application exécute n'importe quelle commande demandée par Claude. Exécutez la session dans un environnement isolé, tel qu'un conteneur ou une machine virtuelle, en tant qu'utilisateur disposant des privilèges minimaux nécessaires pour effectuer le travail. Traitez chaque commande comme une entrée non fiable.
</Warning>

Au-delà de l'isolation, ajoutez ces contrôles :

* Validez les commandes avant de les exécuter, avec une liste d'autorisation plutôt qu'une liste de blocage. Consultez [Implémenter l'outil bash](https://platform.claude.com/docs/fr/agents-and-tools/tool-use/bash-tool#implement-the-bash-tool).
* Définissez des limites de ressources sur le processus shell (CPU, mémoire et disque), par exemple avec `ulimit`.
* Journalisez chaque commande et sa sortie afin de pouvoir auditer ce qui a été exécuté.
* Masquez les identifiants et autres secrets dans la sortie avant de la renvoyer à Claude.

## Tarification

La définition de l'outil bash ajoute les tokens d'entrée suivants à votre requête. Cela s'ajoute à l'[invite système d'utilisation d'outils](https://platform.claude.com/docs/fr/agents-and-tools/tool-use/overview#pricing) propre à chaque modèle, qui s'applique dès qu'un outil est présent.

| Modèle                                                           | Tokens d'entrée supplémentaires |
| ---------------------------------------------------------------- | ------------------------------- |
| Modèles Claude 4.7 et ultérieurs ainsi que Claude Mythos Preview | 325 tokens                      |
| Modèles Claude 4.6 et antérieurs                                 | 244 tokens                      |

Des tokens supplémentaires sont consommés par :

* Les sorties de commandes (stdout/stderr)
* Les messages d'erreur
* Les contenus de fichiers volumineux

Consultez la [tarification de l'utilisation d'outils](https://platform.claude.com/docs/fr/agents-and-tools/tool-use/overview#pricing) pour tous les détails de tarification.

## Modèles courants

### Flux de travail de développement

* Exécuter des tests : `pytest && coverage report`
* Compiler des projets : `npm install && npm run build`
* Opérations Git : `git status && git add . && git commit -m "message"`

Pour des conseils sur l'utilisation de git comme mécanisme de point de contrôle et de récupération dans les flux de travail d'agents de longue durée, consultez les [bonnes pratiques de gestion d'état](https://platform.claude.com/docs/fr/build-with-claude/prompt-engineering/claude-prompting-best-practices#state-management-best-practices).

### Opérations sur les fichiers

* Traiter des données : `wc -l *.csv && ls -lh *.csv`
* Rechercher des fichiers : `find . -name "*.py" | xargs grep "pattern"`
* Créer des sauvegardes : `tar -czf backup.tar.gz ./data`

### Tâches système

* Vérifier les ressources : `df -h && free -m`
* Gestion des processus : `ps aux | grep python`
* Configuration de l'environnement : `export PATH=$PATH:/new/path && echo $PATH`

## Limitations

* **Pas de commandes interactives :** la session ne peut pas exécuter `vim`, `less`, des invites de mot de passe ni aucune commande qui attend une saisie sur stdin.
* **Pas d'applications graphiques :** la session est uniquement en ligne de commande.
* **Portée de la session :** l'état de la session bash est côté client. Votre application est responsable du maintien de la session shell entre les tours.
* **Limites de sortie :** l'API ne tronque pas les résultats d'outils (une requête trop volumineuse est rejetée). Tronquez les sorties volumineuses dans votre application avant de les renvoyer à Claude.
* **Pas de streaming :** la sortie ne parvient à Claude que lorsque votre application renvoie le `tool_result` dans la requête suivante.

## Combinaison avec d'autres outils

L'outil bash se combine bien avec l'[outil d'édition de texte](https://platform.claude.com/docs/fr/agents-and-tools/tool-use/text-editor-tool) : Claude modifie un fichier avec un outil et demande la commande qui l'exécute avec l'autre.

<Note>
  Si vous utilisez également l'[outil d'exécution de code](https://platform.claude.com/docs/fr/agents-and-tools/tool-use/code-execution-tool), Claude a accès à deux environnements d'exécution distincts : votre session bash locale et le conteneur sandbox d'Anthropic. L'état n'est pas partagé entre eux. Consultez [Utiliser l'exécution de code avec d'autres outils d'exécution](https://platform.claude.com/docs/fr/agents-and-tools/tool-use/code-execution-tool#using-code-execution-with-other-execution-tools) pour des conseils sur la manière d'inviter Claude à distinguer les environnements.
</Note>

## Étapes suivantes

<CardGroup cols={2}>
  <Card title="Outil d'édition de texte" icon="file" href="https://platform.claude.com/docs/fr/agents-and-tools/tool-use/text-editor-tool">
    Affichez et modifiez des fichiers texte pour déboguer, corriger et améliorer du code.
  </Card>

  <Card title="Utilisation d'outils avec Claude" icon="tool" href="https://platform.claude.com/docs/fr/agents-and-tools/tool-use/overview">
    Connectez Claude à des outils et API externes. Découvrez où les outils s'exécutent, quand Claude les appelle et quel outil convient à votre tâche.
  </Card>
</CardGroup>
