---
title: Bash-Tool
url: https://platform.claude.com/docs/de/agents-and-tools/tool-use/bash-tool
description: Lass Claude Shell-Befehle anfordern, die deine Anwendung in einer persistenten Bash-Sitzung ausführt und als Tool-Ergebnisse zurückgibt.
---

<Note>
  Um zu erfahren, wie „zero data retention“ (Null-Datenspeicherung), oder ZDR, auf diese Funktion angewendet wird, siehe [API und Datenspeicherung](https://platform.claude.com/docs/de/manage-claude/api-and-data-retention).
</Note>

Das Bash-Tool ist ein [Client-Tool](https://platform.claude.com/docs/de/agents-and-tools/tool-use/how-tool-use-works): Claude führt Befehle nicht selbst aus. Wenn du das Tool in eine Anfrage aufnimmst, antwortet Claude mit einem `tool_use`-Block, der den auszuführenden Befehl benennt. Deine Anwendung führt diesen Befehl in einer Bash-Sitzung aus, die ihr gehört, und gibt die Ausgabe in einem `tool_result`-Block zurück.

Deine Anwendung hält einen Bash-Prozess über Tool-Aufrufe hinweg am Leben, sodass der Zustand zwischen Befehlen erhalten bleibt. Das Arbeitsverzeichnis, Umgebungsvariablen und alle Dateien, die ein Befehl erstellt, sind für den nächsten Befehl noch vorhanden.

Die aktuelle Version des Tools ist `bash_20250124`. Informationen zur Modellunterstützung, zu Beta-Headern und zur früheren Version findest du unter [Tool-Versionen](https://platform.claude.com/docs/de/agents-and-tools/tool-use/bash-tool#tool-versions). Alle von Anthropic bereitgestellten Tools findest du in der [Tool-Referenz](https://platform.claude.com/docs/de/agents-and-tools/tool-use/tool-reference).

## Anwendungsfälle

* **Entwicklungs-Workflows:** Build-Befehle, Tests und Entwicklungstools ausführen
* **Systemautomatisierung:** Skripte ausführen, Dateien verwalten, Aufgaben automatisieren
* **Datenverarbeitung:** Dateien verarbeiten, Analyseskripte ausführen, Datensätze verwalten
* **Umgebungseinrichtung:** Pakete installieren, Umgebungen konfigurieren

## Schnellstart

<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 antwortet mit `stop_reason: "tool_use"` und einem `tool_use`-Block, der den Befehl enthält, den deine Anwendung ausführen soll:

```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"
      }
    }
  ]
}
```

Führe `input.command` in deiner Bash-Sitzung aus und sende die Ausgabe als `tool_result` zurück. Den vollständigen Roundtrip findest du unter [Das Bash-Tool implementieren](https://platform.claude.com/docs/de/agents-and-tools/tool-use/bash-tool#implement-the-bash-tool).

## So funktioniert es

Jeder Tool-Aufruf ist ein Roundtrip zwischen Claude und deiner Anwendung:

1. Claude gibt einen `tool_use`-Block zurück, der den auszuführenden `command` enthält.
2. Deine Anwendung führt den Befehl in ihrer Bash-Sitzung aus.
3. Deine Anwendung gibt die Ausgabe des Befehls, stdout und stderr zusammen, in einem `tool_result`-Block an Claude zurück.
4. Claude fordert entweder einen weiteren Befehl in derselben Sitzung an oder antwortet mit Text.

Claude kann auch mehrere `tool_use`-Blöcke in einer Antwort zurückgeben. Führe sie der Reihe nach in derselben Sitzung aus und gib alle Ergebnisse in einer einzigen `user`-Nachricht zurück. Siehe [Parallele Tool-Nutzung](https://platform.claude.com/docs/de/agents-and-tools/tool-use/parallel-tool-use).

Die API ist zustandslos. Nichts über deine Shell-Sitzung wird zwischen Anfragen übertragen, daher entscheidet deine Anwendung, wann die Sitzung beginnt, wie lange sie lebt und wann sie neu gestartet wird. Den vollständigen Anfrage- und Antwortzyklus findest du unter [Tool-Aufrufe verarbeiten](https://platform.claude.com/docs/de/agents-and-tools/tool-use/handle-tool-calls).

## Parameter

Eine Bash-Tool-Definition hat zwei Pflichtfelder, `type` und `name`, und `name` muss `bash` sein. Das Tool ist schemalos: Du gibst kein `input_schema` an, da das Schema in Claudes Modell eingebaut ist und nicht geändert werden kann. Die folgende Tabelle listet die Eingabefelder auf, die Claude setzt, wenn es das Tool aufruft.

| Parameter | Erforderlich | Beschreibung                                          |
| --------- | ------------ | ----------------------------------------------------- |
| `command` | Ja\*         | Der auszuführende Bash-Befehl                         |
| `restart` | Nein         | Auf `true` setzen, um die Bash-Sitzung neu zu starten |

\*Erforderlich, sofern nicht `restart` verwendet wird

Um `restart: true` zu verarbeiten, beende den Shell-Prozess, starte einen neuen und gib ein `tool_result` zurück, das den Neustart bestätigt. Eine neu gestartete Sitzung beginnt sauber: Das Arbeitsverzeichnis, Umgebungsvariablen und alle laufenden Prozesse sind weg.

<Accordion title="Beispielverwendung">
  Einen Befehl ausführen:

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

  Die Sitzung neu starten:

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

## Tool-Versionen

`bash_20250124` ist die aktuelle Version des Tools und erfordert keinen Beta-Header. Jedes Modell ab Claude Sonnet 3.7 ([eingestellt](https://platform.claude.com/docs/de/about-claude/model-deprecations)) akzeptiert sie, einschließlich aller aktuellen Claude-Modelle.

Die ursprüngliche Version `bash_20241022` funktioniert nur mit dem Claude Sonnet 3.5-Modell vom Oktober 2024 ([eingestellt](https://platform.claude.com/docs/de/about-claude/model-deprecations)). Anfragen, die sie verwenden, benötigen den Header `anthropic-beta: computer-use-2024-10-22`, und das SDK stellt sie nur in seinem Beta-Namespace bereit. Neue Integrationen sollten `bash_20250124` verwenden.

## Beispiel: Mehrstufige Automatisierung

Claude kann Befehle über Tool-Aufrufe hinweg verketten, um eine mehrstufige Aufgabe zu erledigen:

```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"}
```

Die Sitzung behält den Zustand zwischen Befehlen bei, sodass in Schritt 2 erstellte Dateien in Schritt 3 verfügbar sind.

## Das Bash-Tool implementieren

Claude bestimmt, welcher Befehl ausgeführt wird. Deine Anwendung ist für alles andere verantwortlich: den Shell-Prozess, das Timeout und die Sicherheitsprüfungen. Die folgenden Schritte zeigen eine minimale Implementierung.

<Steps>
  <Step title="Eine persistente Bash-Sitzung erstellen">
    Starte einen langlebigen Bash-Prozess und führe jeden Befehl darin aus. Da eine Pipe zu einem laufenden Prozess niemals ein Dateiende meldet, gibt die Sitzung nach jedem Befehl eine eindeutige Sentinel-Zeile aus, um zu markieren, wo die Ausgabe dieses Befehls endet:

    <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";

      // Ein Bash-Prozess, der zwischen Befehlen aktiv bleibt, damit der Zustand erhalten bleibt.
      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 });
        }

        // Führe einen Befehl in der Session aus und gib seine Ausgabe zurück.
        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)) {
                // die Ausgabe dieses Befehls ist vollständig
                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

      // Ein Bash-Prozess, der zwischen Befehlen aktiv bleibt, damit der Zustand erhalten bleibt.
      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;
          }

          // Führe einen Befehl in der Session aus und gib dessen Ausgabe zurück.
          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 ist ein Bash-Prozess, der zwischen Befehlen aktiv bleibt, sodass der Zustand erhalten bleibt.
      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 führt einen Befehl in der Session aus und gibt dessen Ausgabe zurück.
      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 beendet die Shell und startet an ihrer Stelle eine neue Session.
      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;

      // Ein Bash-Prozess, der zwischen Befehlen aktiv bleibt, damit der Zustand erhalten bleibt.
      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()));
          }

          // Führe einen Befehl in der Sitzung aus und gib seine Ausgabe zurück.
          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
      // Ein Bash-Prozess, der zwischen Befehlen aktiv bleibt, damit der Zustand erhalten bleibt.
      class BashSession
      {
          public $process;
          public $stdin;
          public $output;

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

          private function start(): void
          {
              // setsid gibt der Shell eine eigene Prozessgruppe: Ein Timeout kann jeden Kindprozess beenden
              $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];
          }

          // Führe einen Befehl in der Session aus und gib seine Ausgabe zurück.
          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"

      # Ein Bash-Prozess, der zwischen Befehlen aktiv bleibt, damit der Zustand erhalten bleibt.
      class BashSession
        attr_reader :output, :wait_thread

        def initialize
          start
        end

        # Führe einen Befehl in der Session aus und gib seine Ausgabe zurück.
        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 verschachtelt Fehler mit der Ausgabe in Reihenfolge; pgroup gibt der Shell ihre
          # eigene Prozessgruppe, damit ein Timeout jeden Kindprozess beenden kann
          @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>

    Die Sitzung verschränkt stderr mit stdout, sodass Fehlermeldungen dort landen, wo sie aufgetreten sind. Das Beispiel lässt aus, was eine vollständige Implementierung ebenfalls benötigt: ein Timeout, das die Shell und jeden von ihr gestarteten Prozess beendet, wenn ein Befehl hängt, und dann die Sitzung neu startet. Die Best Practice [Befehls-Timeouts verwenden](https://platform.claude.com/docs/de/agents-and-tools/tool-use/bash-tool#follow-implementation-best-practices) zeigt eine Möglichkeit, dies hinzuzufügen.
  </Step>

  <Step title="Claudes Tool-Aufrufe verarbeiten">
    Extrahiere Befehle aus Claudes Antworten und führe sie aus:

    <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)

              # Ein tool_result pro tool_use-Block, alle in der nächsten User-Nachricht zurückgegeben
              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 ?? "");
          }

          // Ein tool_result pro tool_use-Block, alle in der nächsten User-Nachricht zurückgegeben
          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);
              }

              // Ein tool_result pro tool_use-Block, alle in der nächsten User-Nachricht zurückgegeben
              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)
      		}

      		// Ein tool_result pro tool_use-Block, alle in der nächsten User-Nachricht zurückgegeben
      		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);
              }

              // Ein tool_result pro tool_use-Block, alle in der nächsten User-Nachricht zurückgegeben
              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']);
              }

              // Ein tool_result pro tool_use-Block, alle in der nächsten User-Nachricht zurückgegeben
              $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

        # Ein tool_result pro tool_use-Block, alle in der nächsten User-Nachricht zurückgegeben
        tool_results << {type: "tool_result", tool_use_id: block.id, content: result}
      end
      ```
    </CodeGroup>
  </Step>

  <Step title="Das Ergebnis an Claude zurückgeben">
    Sende das `tool_result` in einer `user`-Nachricht zurück, die dieselbe Konversation fortsetzt. Claude fordert entweder einen weiteren Befehl in derselben Sitzung an oder schließt seine Antwort ab:

    <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>

    Wiederhole den Zyklus aus Ausführen und Zurückgeben, solange `stop_reason` den Wert `tool_use` hat. Die vollständige Schleife findest du unter [Ergebnisse von Client-Tools verarbeiten](https://platform.claude.com/docs/de/agents-and-tools/tool-use/handle-tool-calls#handling-results-from-client-tools).
  </Step>

  <Step title="Sicherheitsmaßnahmen implementieren">
    Füge Validierung und Einschränkungen hinzu. Verwende eine „allowlist" (Positivliste) statt einer „blocklist" (Sperrliste): Eine Blocklist übersieht jeden Befehl, den sie nicht vorhergesehen hat. Das Beispiel lehnt außerdem Shell-Operatoren ab, die als separate Wörter auftreten:

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

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


      def validate_command(command):
          # Erlaube nur Befehle aus einer expliziten Allowlist
          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"

          # Lehne Shell-Operatoren ab, die als separate Wörter geschrieben sind
          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 } {
        // An Leerzeichen aufteilen: genügt für eine Stolperdraht-Prüfung
        const tokens = command.split(/\s+/).filter((token) => token.length > 0);
        if (tokens.length === 0) {
          return { ok: false, reason: "Empty command" };
        }

        // Nur Befehle aus einer expliziten Allowlist zulassen
        const executable = tokens[0];
        if (!ALLOWED_COMMANDS.has(executable)) {
          return { ok: false, reason: `Command '${executable}' is not in the allowlist` };
        }

        // Als separate Wörter geschriebene Shell-Operatoren ablehnen
        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)
      {
          // An Leerzeichen aufteilen: reicht für eine Stolperdraht-Prüfung
          var tokens = command.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
          if (tokens.Length == 0)
          {
              return (false, "Empty command");
          }

          // Nur Befehle aus einer expliziten Allowlist zulassen
          var executable = tokens[0];
          if (!allowedCommands.Contains(executable))
          {
              return (false, $"Command '{executable}' is not in the allowlist");
          }

          // Als separate Wörter geschriebene Shell-Operatoren ablehnen
          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) {
      	// An Leerzeichen aufteilen: ausreichend für eine Stolperdraht-Prüfung
      	tokens := strings.Fields(command)
      	if len(tokens) == 0 {
      		return false, "Empty command"
      	}

      	// Nur Befehle aus einer expliziten Allowlist zulassen
      	executable := tokens[0]
      	if !allowedCommands[executable] {
      		return false, fmt.Sprintf("Command %q is not in the allowlist", executable)
      	}

      	// Als separate Wörter geschriebene Shell-Operatoren ablehnen
      	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) {
          // An Leerzeichen aufteilen: ausreichend für eine Stolperdraht-Prüfung
          List<String> tokens = List.of(command.trim().split("\\s+"));
          if (tokens.size() == 1 && tokens.get(0).isEmpty()) {
              return new Validation(false, "Empty command");
          }

          // Nur Befehle aus einer expliziten Allowlist zulassen
          String executable = tokens.get(0);
          if (!ALLOWED_COMMANDS.contains(executable)) {
              return new Validation(false, "Command '" + executable + "' is not in the allowlist");
          }

          // Als separate Wörter geschriebene Shell-Operatoren ablehnen
          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
      {
          // An Leerzeichen aufteilen: reicht für eine Stolperdraht-Prüfung
          $tokens = preg_split('/\\s+/', trim($command), -1, PREG_SPLIT_NO_EMPTY);
          if ($tokens === false || $tokens === []) {
              return [false, 'Empty command'];
          }

          // Nur Befehle aus einer expliziten Allowlist zulassen
          $executable = $tokens[0];
          if (!in_array($executable, ALLOWED_COMMANDS, true)) {
              return [false, "Command '{$executable}' is not in the allowlist"];
          }

          // Als separate Wörter geschriebene Shell-Operatoren ablehnen
          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)
        # Nur Befehle aus einer expliziten Allowlist zulassen
        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

        # Als separate Wörter geschriebene Shell-Operatoren ablehnen
        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>

    Diese Prüfung ist ein Stolperdraht für offensichtliche Fehler, keine Durchsetzungsgrenze. Sie lehnt die mit Leerzeichen getrennte Verkettung (`&&`), Pipes und Umleitungen ab, die die anderen Beispiele auf dieser Seite verwenden. Sie erkennt keinen Operator, der an ein Wort geklebt ist, wie etwa `cat data.txt|grep x`, da der Tokenizer `data.txt|grep` innerhalb eines Tokens belässt. Entscheide, welche Befehle und Operatoren deine Anwendung zulässt. Die eigentliche Kontrolle ist Isolation: Führe die gesamte Sitzung in einem Container oder einer virtuellen Maschine aus (siehe [Sicherheit](https://platform.claude.com/docs/de/agents-and-tools/tool-use/bash-tool#security)).
  </Step>
</Steps>

### Fehler behandeln

Wenn ein Befehl fehlschlägt oder die Sitzung abbricht, teile Claude mit, was passiert ist. Gib die Meldung als `tool_result`-Inhalt zurück und setze `is_error` auf `true`, was den Tool-Aufruf als fehlgeschlagen markiert. Siehe [Fehlerbehandlung mit is\_error](https://platform.claude.com/docs/de/agents-and-tools/tool-use/handle-tool-calls#handling-errors-with-is-error).

<AccordionGroup>
  <Accordion title="Timeout bei der Befehlsausführung">
    Wenn die Ausführung eines Befehls zu lange dauert:

    ```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="Befehl nicht gefunden">
    Wenn ein Befehl nicht existiert:

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

  <Accordion title="Zugriff verweigert">
    Wenn es Berechtigungsprobleme gibt:

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

### Best Practices für die Implementierung befolgen

<AccordionGroup>
  <Accordion title="Befehls-Timeouts verwenden">
    Ein Befehl, der nie endet, etwa einer, der auf Eingabe wartet, blockiert die Sitzung für immer, weil seine Sentinel-Zeile nie ankommt. Gib jedem Befehl eine Frist. Wenn die Frist abläuft, stoppe die Shell und alles, was der Befehl gestartet hat, und starte dann die Sitzung neu:

    <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:
                  # Die Gruppe umfasst die Shell und jeden Prozess, den der Befehl gestartet hat
                  os.killpg(session.process.pid, signal.SIGKILL)
                  session.restart()
                  return f"Error: command did not finish within {timeout} seconds"
      ```

      ```typescript TypeScript
      // Führe einen Befehl in der Session aus und ersetze die Session, falls der Befehl hängt.
      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 {
          // Die Gruppe ist die Shell und jeder Prozess, den der Befehl gestartet hat
          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;

      // Führe einen Befehl in der Session aus und ersetze die Session, falls der Befehl hängt.
      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;
          }

          // Stoppe die Shell und jeden von ihr gestarteten Prozess, starte dann eine neue Session
          session.Process.Kill(entireProcessTree: true);
          session.Restart();
          return $"Error: command did not finish within {timeoutSeconds} seconds";
      }
      ```

      ```go Go
      // executeWithTimeout führt einen Befehl aus und ersetzt die Session, falls der Befehl hängt.
      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):
      		// Die Gruppe umfasst die Shell und jeden Prozess, den der Befehl gestartet hat
      		syscall.Kill(-session.cmd.Process.Pid, syscall.SIGKILL)
      		session.Restart()
      		return fmt.Sprintf("Error: command did not finish within %d seconds", timeoutSeconds)
      	}
      }
      ```

      ```java Java
      // Führe einen Befehl in der Session aus und ersetze die Session, falls der Befehl hängt.
      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) {
              // Stoppe die Shell und jeden von ihr gestarteten Prozess, starte dann eine neue 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
      // Führe einen Befehl aus, aber brich ab, wenn er nicht innerhalb der Frist fertig wird. PHP blockiert bei
      // Pipe-Lesevorgängen, daher liegt die Frist in der Leseschleife: stream_select() wartet auf
      // lesbare Ausgabe vor jedem fgets(), damit die Schleife die Frist prüfen kann.
      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;
          }

          // Die Gruppe ist die Shell und jeder Prozess, den der Befehl gestartet hat
          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"

      # Führe einen Befehl in der Session aus und ersetze die Session, falls der Befehl hängt.
      def execute_with_timeout(session, command, timeout: 30)
        Timeout.timeout(timeout) { session.execute_command(command) }
      rescue Timeout::Error
        # Die Gruppe ist die Shell und jeder Prozess, den der Befehl gestartet hat
        Process.kill("KILL", -session.wait_thread.pid)
        session.restart
        "Error: command did not finish within #{timeout} seconds"
      end
      ```
    </CodeGroup>

    Der Kill stoppt den hängenden Befehl und alles, was er gestartet hat. Gib die Meldung als Fehler-`tool_result` zurück (siehe [Fehler behandeln](https://platform.claude.com/docs/de/agents-and-tools/tool-use/bash-tool#handle-errors)), was den Tool-Aufruf als fehlgeschlagen markiert.
  </Accordion>

  <Accordion title="Sitzungszustand beibehalten">
    Halte die Bash-Sitzung persistent, um Umgebungsvariablen und das Arbeitsverzeichnis beizubehalten:

    <CodeGroup exclude="shell">
      ```python Python
      # Befehle, die in derselben Sitzung ausgeführt werden, behalten ihren Zustand bei
      commands = [
          "cd /tmp",
          "echo 'Hello' > test.txt",
          "cat test.txt",  # The session is still in /tmp
      ]
      ```

      ```typescript TypeScript
      // Befehle, die in derselben Sitzung ausgeführt werden, behalten ihren Zustand bei
      const commands = [
        "cd /tmp",
        "echo 'Hello' > test.txt",
        "cat test.txt" // The session is still in /tmp
      ];
      ```

      ```csharp C#
      // Befehle, die in derselben Session ausgeführt werden, behalten ihren Zustand bei
      string[] commands =
      [
          "cd /tmp",
          "echo 'Hello' > test.txt",
          "cat test.txt", // The session is still in /tmp
      ];
      ```

      ```go Go
      // Befehle, die in derselben Sitzung ausgeführt werden, behalten ihren Zustand bei
      commands := []string{
      	"cd /tmp",
      	"echo 'Hello' > test.txt",
      	"cat test.txt", // The session is still in /tmp
      }
      ```

      ```java Java
      // Befehle, die in derselben Sitzung ausgeführt werden, behalten ihren Zustand bei
      List<String> commands = List.of(
          "cd /tmp",
          "echo 'Hello' > test.txt",
          "cat test.txt" // The session is still in /tmp
      );
      ```

      ```php PHP
      // Befehle, die in derselben Sitzung ausgeführt werden, behalten ihren Zustand bei
      $commands = [
          'cd /tmp',
          "echo 'Hello' > test.txt",
          'cat test.txt', // The session is still in /tmp
      ];
      ```

      ```ruby Ruby
      # Befehle, die in derselben Sitzung ausgeführt werden, behalten ihren Zustand bei
      commands = [
        "cd /tmp",
        "echo 'Hello' > test.txt",
        "cat test.txt" # The session is still in /tmp
      ]
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Große Ausgaben verarbeiten">
    Kürze große Ausgaben, um Probleme mit Token-Limits zu vermeiden:

    <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="Alle Befehle protokollieren">
    Führe ein Audit-Protokoll. Leite jeden Befehl durch einen Wrapper, der den Befehl vor der Ausführung und die Ausgabe nach Abschluss aufzeichnet. Ein Befehl, der hängt oder die Sitzung abbricht, hinterlässt trotzdem einen Eintrag:

    <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
      // Führe einen Befehl in der Session aus und bewahre einen Audit-Eintrag dazu auf.
      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#
      // Führe einen Befehl in der Session aus und bewahre einen Audit-Eintrag dazu auf.
      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 führt einen Befehl in der Session aus und protokolliert ihn in einem Audit-Eintrag.
      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");

      // Führe einen Befehl in der Session aus und bewahre einen Audit-Eintrag dazu auf.
      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
      // Führe einen Befehl in der Session aus und bewahre einen Audit-Eintrag dazu auf.
      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)

      # Führe einen Befehl in der Session aus und bewahre einen Audit-Eintrag dazu auf.
      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>

    Die Einträge gehen standardmäßig an `stderr`; leite sie in eine Datei oder deine Logging-Pipeline, um sie aufzubewahren. Nimm alles auf, was den Eintrag in deiner Anwendung mit der Anfrage verknüpft, etwa den Endnutzer und die `tool_use_id`.
  </Accordion>
</AccordionGroup>

## Sicherheit

<Warning>
  Deine Anwendung führt jeden Befehl aus, den Claude anfordert. Führe die Sitzung in einer isolierten Umgebung aus, etwa einem Container oder einer virtuellen Maschine, als der am wenigsten privilegierte Benutzer, der die Arbeit erledigen kann. Behandle jeden Befehl als nicht vertrauenswürdige Eingabe.
</Warning>

Füge über die Isolation hinaus diese Kontrollen hinzu:

* Validiere Befehle vor der Ausführung, mit einer Allowlist statt einer Blocklist. Siehe [Das Bash-Tool implementieren](https://platform.claude.com/docs/de/agents-and-tools/tool-use/bash-tool#implement-the-bash-tool).
* Setze Ressourcenlimits für den Shell-Prozess (CPU, Arbeitsspeicher und Festplatte), zum Beispiel mit `ulimit`.
* Protokolliere jeden Befehl und seine Ausgabe, damit du prüfen kannst, was ausgeführt wurde.
* Schwärze Zugangsdaten und andere Geheimnisse in der Ausgabe, bevor du sie an Claude zurückgibst.

## Preise

Die Bash-Tool-Definition fügt deiner Anfrage die folgenden Input-Token hinzu. Dies gilt zusätzlich zum modellspezifischen [System-Prompt für die Tool-Nutzung](https://platform.claude.com/docs/de/agents-and-tools/tool-use/overview#pricing), der immer dann angewendet wird, wenn ein beliebiges Tool vorhanden ist.

| Modell                                                    | Zusätzliche Input-Token |
| --------------------------------------------------------- | ----------------------- |
| Claude 4.7 und neuere Modelle sowie Claude Mythos Preview | 325 Token               |
| Claude 4.6 und ältere Modelle                             | 244 Token               |

Zusätzliche Token werden verbraucht durch:

* Befehlsausgaben (stdout/stderr)
* Fehlermeldungen
* Große Dateiinhalte

Vollständige Preisdetails findest du unter [Preise für Tool-Nutzung](https://platform.claude.com/docs/de/agents-and-tools/tool-use/overview#pricing).

## Häufige Muster

### Entwicklungs-Workflows

* Tests ausführen: `pytest && coverage report`
* Projekte bauen: `npm install && npm run build`
* Git-Operationen: `git status && git add . && git commit -m "message"`

Hinweise zur Verwendung von git als Checkpoint- und Wiederherstellungsmechanismus in lang laufenden Agenten-Workflows findest du unter [Best Practices für die Zustandsverwaltung](https://platform.claude.com/docs/de/build-with-claude/prompt-engineering/claude-prompting-best-practices#state-management-best-practices).

### Dateioperationen

* Daten verarbeiten: `wc -l *.csv && ls -lh *.csv`
* Dateien durchsuchen: `find . -name "*.py" | xargs grep "pattern"`
* Backups erstellen: `tar -czf backup.tar.gz ./data`

### Systemaufgaben

* Ressourcen prüfen: `df -h && free -m`
* Prozessverwaltung: `ps aux | grep python`
* Umgebungseinrichtung: `export PATH=$PATH:/new/path && echo $PATH`

## Einschränkungen

* **Keine interaktiven Befehle:** Die Sitzung kann `vim`, `less`, Passwortabfragen oder andere Befehle, die auf Eingabe über stdin warten, nicht ausführen.
* **Keine GUI-Anwendungen:** Die Sitzung ist rein kommandozeilenbasiert.
* **Sitzungsumfang:** Der Zustand der Bash-Sitzung liegt clientseitig. Deine Anwendung ist dafür verantwortlich, die Shell-Sitzung zwischen den Turns aufrechtzuerhalten.
* **Ausgabelimits:** Die API kürzt Tool-Ergebnisse nicht (eine zu große Anfrage wird abgelehnt). Kürze große Ausgaben in deiner Anwendung, bevor du sie an Claude zurückgibst.
* **Kein Streaming:** Die Ausgabe erreicht Claude erst, wenn deine Anwendung das `tool_result` in der nächsten Anfrage zurückgibt.

## Kombination mit anderen Tools

Das Bash-Tool lässt sich gut mit dem [Texteditor-Tool](https://platform.claude.com/docs/de/agents-and-tools/tool-use/text-editor-tool) kombinieren: Claude bearbeitet eine Datei mit dem einen Tool und fordert mit dem anderen den Befehl an, der sie ausführt.

<Note>
  Wenn du zusätzlich das [Code-Ausführungstool](https://platform.claude.com/docs/de/agents-and-tools/tool-use/code-execution-tool) verwendest, hat Claude Zugriff auf zwei getrennte Ausführungsumgebungen: deine lokale Bash-Sitzung und Anthropics Sandbox-Container. Der Zustand wird zwischen ihnen nicht geteilt. Hinweise dazu, wie du Claude per Prompt zwischen den Umgebungen unterscheiden lässt, findest du unter [Code-Ausführung mit anderen Ausführungstools verwenden](https://platform.claude.com/docs/de/agents-and-tools/tool-use/code-execution-tool#using-code-execution-with-other-execution-tools).
</Note>

## Nächste Schritte

<CardGroup cols={2}>
  <Card title="Texteditor-Tool" icon="file" href="https://platform.claude.com/docs/de/agents-and-tools/tool-use/text-editor-tool">
    Textdateien anzeigen und ändern, um Code zu debuggen, zu korrigieren und zu verbessern.
  </Card>

  <Card title="Tool-Nutzung mit Claude" icon="tool" href="https://platform.claude.com/docs/de/agents-and-tools/tool-use/overview">
    Verbinde Claude mit externen Tools und APIs. Erfahre, wo Tools ausgeführt werden, wann Claude sie aufruft und welches Tool zu deiner Aufgabe passt.
  </Card>
</CardGroup>
