Bash 工具是一种 client tool(客户端工具):Claude 本身不会运行命令。当您在请求中包含该工具时,Claude 会回复一个 tool_use 块,其中指明要运行的命令。您的应用程序在其拥有的 bash 会话中运行该命令,并在 tool_result 块中返回输出。

您的应用程序在多次工具调用之间保持一个 bash 进程存活,因此状态会在命令之间持久保留。工作目录、环境变量以及命令创建的任何文件在下一条命令执行时仍然存在。

该工具的当前版本为 bash_20250124。有关模型支持、beta 头以及早期版本的信息,请参阅工具版本。有关 Anthropic 提供的所有工具,请参阅工具参考。

使用场景

  • 开发工作流: 运行构建命令、测试和开发工具
  • 系统自动化: 运行脚本、管理文件、自动化任务
  • 数据处理: 处理文件、运行分析脚本、管理数据集
  • 环境设置: 安装软件包、配置环境

快速开始

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."
      }
    ]
  }'

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

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

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

C#

var client = new AnthropicClient();

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

Console.WriteLine(response);

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

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

use Anthropic\Messages\Model;
use Anthropic\Messages\ToolBash20250124;

$client = new Client();

$response = $client->messages->create(
    model: 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

client = Anthropic::Client.new

response = client.messages.create(
  model: Anthropic::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

Claude 会以 stop_reason: "tool_use" 和一个 tool_use 块进行响应,其中包含供您的应用程序运行的命令:

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

在您的 bash 会话中运行 input.command,并将输出作为 tool_result 发送回去。有关完整的往返流程,请参阅实现 bash 工具。

工作原理

每次工具调用都是 Claude 与您的应用程序之间的一次往返:

  1. Claude 返回一个包含要运行的 command 的 tool_use 块。
  2. 您的应用程序在其 bash 会话中运行该命令。
  3. 您的应用程序将命令的输出(stdout 和 stderr 合并在一起)通过 tool_result 块返回给 Claude。
  4. Claude 要么在同一会话中请求另一条命令,要么以文本进行响应。

Claude 也可能在一次响应中返回多个 tool_use 块。请在同一会话中按顺序运行它们,并在一条 user 消息中返回所有结果。请参阅并行工具使用。

API 是无状态的。您的 shell 会话的任何信息都不会在请求之间传递,因此由您的应用程序决定会话何时启动、持续多久以及何时重启。有关完整的请求和响应周期,请参阅处理工具调用。

参数

Bash 工具定义有两个必填字段:type 和 name,且 name 必须为 bash。该工具是无 schema 的:您无需提供 input_schema,因为 schema 已内置于 Claude 的模型中且无法修改。下表列出了 Claude 调用该工具时设置的输入字段。

参数 必填 描述
command 是* 要运行的 bash 命令
restart 否 设置为 true 以重启 bash 会话

*除非使用 restart,否则为必填

要处理 restart: true,请终止 shell 进程,启动一个新进程,并返回一个确认重启的 tool_result。重启后的会话从干净状态开始:工作目录、环境变量以及任何正在运行的进程都将消失。

使用示例

运行命令:

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

重启会话:

{
  "restart": true
}

工具版本

bash_20250124 是该工具的当前版本,不需要 beta 头。从 Claude Sonnet 3.7(已退役)起的每个模型都接受它,包括所有当前的 Claude 模型。

原始的 bash_20241022 版本仅适用于 2024 年 10 月的 Claude Sonnet 3.5 模型(已退役)。使用该版本的请求需要 anthropic-beta: computer-use-2024-10-22 标头,并且 SDK 仅在其 beta 命名空间中提供该版本。新的集成应使用 bash_20250124。

示例:多步骤自动化

Claude 可以跨多次工具调用串联命令,以完成多步骤任务:

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

会话在命令之间保持状态,因此在第 2 步中创建的文件在第 3 步中可用。

实现 bash 工具

Claude 决定运行哪条命令。其他一切都由您的应用程序负责:shell 进程、超时以及安全检查。以下步骤展示了一个最小实现。

  1. 创建持久化的 bash 会话

    启动一个长期存活的 bash 进程,并在其中运行每条命令。由于连接到存活进程的管道永远不会报告文件结束(end-of-file),会话会在每条命令之后打印一行唯一的哨兵(sentinel)行,以标记该命令输出的结束位置:

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

    import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
    import { createInterface, type Interface } from "node:readline";
    import { randomUUID } from "node:crypto";
    
    // 一个在命令之间保持存活的 bash 进程,以便状态得以持久化。
    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 });
      }
    
      // 在会话中运行命令并返回其输出。
      executeCommand(command: string): Promise<string> {
        const sentinel = `__CLAUDE_BASH_DONE_${randomUUID()}__`; // unique for each call
        const output: string[] = [];
        const result = new Promise<string>((resolve) => {
          const onLine = (line: string): void => {
            if (line.includes(sentinel)) {
              // 此命令的输出已完成
              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

    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
    
    // 一个在命令之间保持存活的 bash 进程,以便状态得以持久化。
    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;
        }
    
        // 在会话中运行命令并返回其输出。
        public string ExecuteCommand(string command)
        {
            var sentinel = $"__CLAUDE_BASH_DONE_{Guid.NewGuid():N}__"; // unique for each 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

    import (
    	"bufio"
    	"crypto/rand"
    	"encoding/hex"
    	"fmt"
    	"io"
    	"log"
    	"os/exec"
    	"strings"
    	"syscall"
    )
    
    // BashSession 是一个在命令之间保持存活的 bash 进程,因此状态得以持久保留。
    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 在会话中运行命令并返回其输出。
    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 for each 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 终止当前 shell,并在原位置启动一个全新的会话。
    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

    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.OutputStreamWriter;
    import java.util.UUID;
    
    // 一个在命令之间保持存活的 bash 进程,以便状态得以持久化。
    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()));
        }
    
        // 在会话中运行命令并返回其输出。
        String executeCommand(String command) throws IOException {
            String sentinel = "__CLAUDE_BASH_DONE_" + UUID.randomUUID() + "__"; // unique for each 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

    // 一个在命令之间保持存活的 bash 进程,以便状态得以持久化。
    class BashSession
    {
        public $process;
        public $stdin;
        public $output;
    
        public function __construct()
        {
            $this->start();
        }
    
        private function start(): void
        {
            // setsid 为 shell 提供独立的进程组:超时时可终止所有子进程
            $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];
        }
    
        // 在会话中运行命令并返回其输出。
        public function executeCommand(string $command): string
        {
            $sentinel = '__CLAUDE_BASH_DONE_' . bin2hex(random_bytes(16)) . '__'; // unique for each 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

    require "open3"
    require "securerandom"
    
    # 一个在命令之间保持存活的 bash 进程,以便状态得以持久化。
    class BashSession
      attr_reader :output, :wait_thread
    
      def initialize
        start
      end
    
      # 在会话中运行命令并返回其输出。
      def execute_command(command)
        sentinel = "__CLAUDE_BASH_DONE_#{SecureRandom.hex(16)}__" # unique for each 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 将错误与输出按顺序交错;pgroup 为 shell 分配
        # 独立的进程组,以便超时时可以终止所有子进程
        @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

    该会话将 stderr 与 stdout 交错输出,因此错误消息会出现在其发生的位置。该示例省略了完整实现还需要的内容:当命令挂起时,通过超时终止 shell 及其启动的所有进程,然后重启会话。使用命令超时最佳实践展示了一种添加方式。

  2. 处理 Claude 的工具调用

    从 Claude 的响应中提取并运行命令:

    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)
    
            # 每个 tool_use 块对应一个 tool_result,全部在下一条用户消息中返回
            tool_results.append(
                {"type": "tool_result", "tool_use_id": content.id, "content": result}
            )

    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 ?? "");
        }
    
        // 每个 tool_use 块对应一个 tool_result,全部在下一条用户消息中返回
        toolResults.push({ type: "tool_result", tool_use_id: block.id, content: result });
      }
    }

    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);
            }
    
            // 每个 tool_use 块对应一个 tool_result,全部在下一条用户消息中返回
            toolResults.Add(new ToolResultBlockParam { ToolUseID = toolUse.ID, Content = result });
        }
    }

    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)
    		}
    
    		// 每个 tool_use 块对应一个 tool_result,全部在下一条 user 消息中返回
    		toolResults = append(toolResults, anthropic.NewToolResultBlock(block.ID, result, false))
    	}
    }

    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);
            }
    
            // 每个 tool_use 块对应一个 tool_result,全部在下一条用户消息中返回
            toolResults.add(Map.of("type", "tool_result", "tool_use_id", block.id(), "content", result));
        }
    }

    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']);
            }
    
            // 每个 tool_use 块对应一个 tool_result,全部在下一条 user 消息中返回
            $toolResults[] = ['type' => 'tool_result', 'tool_use_id' => $block->id, 'content' => $result];
        }
    }

    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
    
      # 每个 tool_use 块对应一个 tool_result,全部在下一条用户消息中返回
      tool_results << {type: "tool_result", tool_use_id: block.id, content: result}
    end
  3. 将结果返回给 Claude

    在延续同一对话的 user 消息中发回 tool_result。Claude 要么在同一会话中请求另一条命令,要么完成其回答:

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

    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

    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

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

    C#

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

    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

    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.")
            .addAssistantMessageOfContentBlockParams(
                List.of(
                    ContentBlockParam.ofToolUse(
                        ToolUseBlockParam.builder()
                            .id("toolu_01A09q90qw90lq917835lq9")
                            .name("bash")
                            .input(
                                ToolUseBlockParam.Input.builder()
                                    .putAdditionalProperty("command", JsonValue.from("ls *.py"))
                                    .build()
                            )
                            .build()
                    )
                )
            )
            .addUserMessageOfContentBlockParams(
                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

    use Anthropic\Messages\Model;
    use Anthropic\Messages\ToolBash20250124;
    
    $client = new Client();
    
    $response = $client->messages->create(
        model: 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

    client = Anthropic::Client.new
    
    response = client.messages.create(
      model: Anthropic::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

    当 stop_reason 为 tool_use 时,重复“运行并返回”的循环。有关完整循环,请参阅处理客户端工具的结果。

  4. 实施安全措施

    添加验证和限制。使用允许列表(allowlist)而非阻止列表(blocklist):阻止列表会遗漏任何未预料到的命令。该示例还会拒绝作为独立单词出现的 shell 运算符:

    Python

    import shlex
    
    ALLOWED_COMMANDS = {"ls", "cat", "echo", "pwd", "grep", "find", "wc", "head", "tail"}
    SHELL_OPERATORS = {"&&", "||", "|", ";", "&", ">", "<", ">>"}
    
    
    def validate_command(command):
        # 仅允许显式允许列表中的命令
        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"
    
        # 拒绝以独立单词形式书写的 shell 运算符
        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

    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 } {
      // 按空白字符拆分:足以用于绊线式检查
      const tokens = command.split(/\s+/).filter((token) => token.length > 0);
      if (tokens.length === 0) {
        return { ok: false, reason: "Empty command" };
      }
    
      // 仅允许显式允许列表中的命令
      const executable = tokens[0];
      if (!ALLOWED_COMMANDS.has(executable)) {
        return { ok: false, reason: `Command '${executable}' is not in the allowlist` };
      }
    
      // 拒绝以独立单词形式书写的 shell 运算符
      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 };
    }

    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)
    {
        // 按空白字符拆分:足以用于绊线式检查
        var tokens = command.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
        if (tokens.Length == 0)
        {
            return (false, "Empty command");
        }
    
        // 仅允许显式允许列表中的命令
        var executable = tokens[0];
        if (!allowedCommands.Contains(executable))
        {
            return (false, $"Command '{executable}' is not in the allowlist");
        }
    
        // 拒绝以独立单词形式书写的 shell 运算符
        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

    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) {
    	// 按空白字符拆分:足以用于绊线式检查
    	tokens := strings.Fields(command)
    	if len(tokens) == 0 {
    		return false, "Empty command"
    	}
    
    	// 仅允许显式允许列表中的命令
    	executable := tokens[0]
    	if !allowedCommands[executable] {
    		return false, fmt.Sprintf("Command %q is not in the allowlist", executable)
    	}
    
    	// 拒绝以独立单词形式书写的 shell 操作符
    	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

    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) {
        // 按空白字符拆分:足以用于绊线式检查
        List<String> tokens = List.of(command.trim().split("\\s+"));
        if (tokens.size() == 1 && tokens.get(0).isEmpty()) {
            return new Validation(false, "Empty command");
        }
    
        // 仅允许显式允许列表中的命令
        String executable = tokens.get(0);
        if (!ALLOWED_COMMANDS.contains(executable)) {
            return new Validation(false, "Command '" + executable + "' is not in the allowlist");
        }
    
        // 拒绝以独立单词形式书写的 shell 运算符
        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

    const ALLOWED_COMMANDS = ['ls', 'cat', 'echo', 'pwd', 'grep', 'find', 'wc', 'head', 'tail'];
    const SHELL_OPERATORS = ['&&', '||', '|', ';', '&', '>', '<', '>>'];
    
    function validateCommand(string $command): array
    {
        // 按空白字符拆分:足以用于绊线式检查
        $tokens = preg_split('/\\s+/', trim($command), -1, PREG_SPLIT_NO_EMPTY);
        if ($tokens === false || $tokens === []) {
            return [false, 'Empty command'];
        }
    
        // 仅允许显式允许列表中的命令
        $executable = $tokens[0];
        if (!in_array($executable, ALLOWED_COMMANDS, true)) {
            return [false, "Command '{$executable}' is not in the allowlist"];
        }
    
        // 拒绝以独立单词形式书写的 shell 运算符
        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

    require "shellwords"
    
    ALLOWED_COMMANDS = %w[ls cat echo pwd grep find wc head tail].freeze
    SHELL_OPERATORS = ["&&", "||", "|", ";", "&", ">", "<", ">>"].freeze
    
    def validate_command(command)
      # 仅允许显式允许列表中的命令
      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
    
      # 拒绝以单独单词形式书写的 shell 运算符
      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

    此检查是针对明显错误的绊线,而非强制执行边界。它会拒绝本页其他示例中使用的带空格的链式操作(&&)、管道和重定向。它无法捕获与单词粘连在一起的操作符,例如 cat data.txt|grep x,因为分词器会将 data.txt|grep 保留在一个令牌内。请决定您的应用程序允许哪些命令和操作符。真正的控制手段是隔离:在容器或虚拟机中运行整个会话(请参阅安全)。

处理错误

当命令失败或会话中断时,请告知 Claude 发生了什么。将消息作为 tool_result 内容返回,并将 is_error 设置为 true,以将该工具调用标记为失败。请参阅使用 is_error 处理错误。

命令执行超时

如果命令运行时间过长:

{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_01A09q90qw90lq917835lq9",
      "content": "Error: command did not finish within 30 seconds",
      "is_error": true
    }
  ]
}
命令未找到

如果命令不存在:

{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_01A09q90qw90lq917835lq9",
      "content": "bash: nonexistentcommand: command not found",
      "is_error": true
    }
  ]
}
权限被拒绝

如果存在权限问题:

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

遵循实现最佳实践

使用命令超时

永远不会完成的命令(例如等待输入的命令)会永久阻塞会话,因为其哨兵行永远不会到达。请为每条命令设置截止时间。截止时间过后,停止 shell 及该命令启动的所有进程,然后重启会话:

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:
            # 该进程组包含 shell 以及该命令启动的所有进程
            os.killpg(session.process.pid, signal.SIGKILL)
            session.restart()
            return f"Error: command did not finish within {timeout} seconds"

TypeScript

// 在会话中运行命令,如果命令挂起则替换该会话。
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 {
    // 该进程组包含 shell 以及该命令启动的所有进程
    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);
  }
}

C#

using System.Diagnostics;

// 在会话中运行命令,若命令挂起则替换该会话。
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;
    }

    // 停止 shell 及其启动的所有进程,然后启动一个新会话
    session.Process.Kill(entireProcessTree: true);
    session.Restart();
    return $"Error: command did not finish within {timeoutSeconds} seconds";
}

Go

// executeWithTimeout 运行一条命令,如果命令挂起则替换会话。
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):
		// 该进程组包含 shell 以及该命令启动的所有进程
		syscall.Kill(-session.cmd.Process.Pid, syscall.SIGKILL)
		session.Restart()
		return fmt.Sprintf("Error: command did not finish within %d seconds", timeoutSeconds)
	}
}

Java

// 在会话中运行命令,如果命令挂起则替换该会话。
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) {
        // 停止 shell 及其启动的所有进程,然后启动一个新会话
        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 在
// 管道读取时会阻塞,因此截止时间检查放在读取循环内:stream_select() 在每次
// fgets() 之前等待可读输出,以便循环能够检查截止时间。
function executeWithTimeout(BashSession $session, string $command, int $timeout = 30): string
{
    $sentinel = '__CLAUDE_BASH_DONE_' . bin2hex(random_bytes(16)) . '__'; // unique for each 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;
    }

    // 该进程组包括 shell 以及该命令启动的所有进程
    posix_kill(-proc_get_status($session->process)['pid'], 9); // 9 = SIGKILL
    $session->restart();
    return "Error: command did not finish within {$timeout} seconds";
}

Ruby

require "timeout"

# 在会话中运行命令,如果命令挂起则替换该会话。
def execute_with_timeout(session, command, timeout: 30)
  Timeout.timeout(timeout) { session.execute_command(command) }
rescue Timeout::Error
  # 该进程组包含 shell 以及该命令启动的所有进程
  Process.kill("KILL", -session.wait_thread.pid)
  session.restart
  "Error: command did not finish within #{timeout} seconds"
end

终止操作会停止挂起的命令及其启动的所有进程。将消息作为错误 tool_result 返回(请参阅处理错误),这会将该工具调用标记为失败。

维护会话状态

保持 bash 会话持久化,以维护环境变量和工作目录:

Python

# 在同一会话中运行的命令会保持状态
commands = [
    "cd /tmp",
    "echo 'Hello' > test.txt",
    "cat test.txt",  # The session is still in /tmp
]

TypeScript

// 在同一会话中运行的命令会保持状态
const commands = [
  "cd /tmp",
  "echo 'Hello' > test.txt",
  "cat test.txt" // The session is still in /tmp
];

C#

// 在同一会话中运行的命令会保持状态
string[] commands =
[
    "cd /tmp",
    "echo 'Hello' > test.txt",
    "cat test.txt", // The session is still in /tmp
];

Go

// 在同一会话中运行的命令会保持状态
commands := []string{
	"cd /tmp",
	"echo 'Hello' > test.txt",
	"cat test.txt", // The session is still in /tmp
}

Java

// 在同一会话中运行的命令会保持状态
List<String> commands = List.of(
    "cd /tmp",
    "echo 'Hello' > test.txt",
    "cat test.txt" // The session is still in /tmp
);

PHP

// 在同一会话中运行的命令会保持状态
$commands = [
    'cd /tmp',
    "echo 'Hello' > test.txt",
    'cat test.txt', // The session is still in /tmp
];

Ruby

# 在同一会话中运行的命令会保持状态
commands = [
  "cd /tmp",
  "echo 'Hello' > test.txt",
  "cat test.txt" # The session is still in /tmp
]
处理大型输出

截断大型输出以防止令牌限制问题:

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

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

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

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

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

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

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
记录所有命令

保留审计记录。让每条命令都通过同一个包装器执行,该包装器在命令运行前记录命令,并在命令完成后记录输出。即使命令挂起或导致会话中断,也仍会留下记录:

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

// 在会话中运行命令并保留其审计记录。
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;
}

C#

// 在会话中运行命令并保留其审计记录。
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

// executeAndLog 在会话中运行命令并保留其审计记录。
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

static final Logger AUDIT = Logger.getLogger("bash-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

// 在会话中运行命令并保留其审计记录。
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

require "logger"

AUDIT = Logger.new($stderr)

# 在会话中运行命令并保留其审计记录。
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

记录默认输出到 stderr;请将其指向文件或您的日志管道以便保留。请包含能将记录与您应用程序中的请求关联起来的任何信息,例如最终用户和 tool_use_id。

安全

警告

您的应用程序会运行 Claude 请求的任何命令。请在隔离环境(例如容器或虚拟机)中,以能够完成工作的最低权限用户身份运行会话。将每条命令都视为不受信任的输入。

除隔离之外,还应添加以下控制措施:

  • 在运行命令之前进行验证,使用允许列表而非阻止列表。请参阅实现 bash 工具。
  • 对 shell 进程设置资源限制(CPU、内存和磁盘),例如使用 ulimit。
  • 记录每条命令及其输出,以便您可以审计运行过的内容。
  • 在将输出返回给 Claude 之前,对其中的凭据和其他机密信息进行脱敏处理。

定价

bash 工具定义会向您的请求中添加以下 input tokens(输入令牌)。这是在每个模型的工具使用系统提示之外额外添加的,只要存在任何工具,该系统提示就会生效。

模型 额外输入令牌
Claude 4.7 及更高版本模型和 Claude Mythos Preview 325 个令牌
Claude 4.6 及更早版本模型 244 个令牌

以下内容会消耗额外的令牌:

  • 命令输出(stdout/stderr)
  • 错误消息
  • 大型文件内容

有关完整的定价详情,请参阅工具使用定价。

常见模式

开发工作流

  • 运行测试:pytest && coverage report
  • 构建项目:npm install && npm run build
  • 执行 Git 操作:git status && git add . && git commit -m "message"

有关在长时间运行的智能体工作流中将 Git 用作检查点和恢复机制的指导,请参阅状态管理最佳实践。

文件操作

  • 处理数据:wc -l *.csv && ls -lh *.csv
  • 搜索文件:find . -name "*.py" | xargs grep "pattern"
  • 创建备份:tar -czf backup.tar.gz ./data

系统任务

  • 检查资源:df -h && free -m
  • 管理进程:ps aux | grep python
  • 设置环境:export PATH=$PATH:/new/path && echo $PATH

限制

  • 不支持交互式命令: 会话无法运行 vim、less、密码提示或任何在 stdin 上等待输入的命令。
  • 不支持 GUI 应用程序: 会话仅限命令行。
  • 会话范围: Bash 会话状态位于客户端。您的应用程序负责在轮次之间维护 shell 会话。
  • 输出限制: API 不会截断工具结果(过大的请求会被拒绝)。请在将大型输出返回给 Claude 之前,在您的应用程序中对其进行截断。
  • 不支持流式传输: 只有当您的应用程序在下一个请求中返回 tool_result 时,输出才会到达 Claude。

与其他工具结合使用

Bash 工具与文本编辑器工具搭配使用效果很好:Claude 使用一个工具编辑文件,并使用另一个工具请求运行该文件的命令。

注意

如果您同时使用代码执行工具,Claude 将可以访问两个独立的执行环境:您本地的 bash 会话和 Anthropic 的沙盒容器。两者之间不共享状态。有关如何提示 Claude 区分不同环境的指导,请参阅将代码执行与其他执行工具结合使用。

后续步骤

  • 文本编辑器工具:查看和修改文本文件,以调试、修复和改进代码。

  • 使用 Claude 进行工具使用:将 Claude 连接到外部工具和 API。了解工具在何处运行、Claude 何时调用它们,以及哪种工具适合您的任务。

可用性

Bash 工具在所有模型上可用,并具备零数据保留和HIPAA 就绪资格。

API 支持

Messages API

  • 可用性:可用
状态 云平台
可用 Claude API, Claude Platform on AWS, Amazon Bedrock, Amazon Bedrock (InvokeModel), Claude Models on Google Cloud (Google managed), Microsoft Foundry

数据处理

数据处理概览

项目 零数据保留 HIPAA
状态 具备资格(不包括 涵盖的模型) 具备资格
详细信息 在您的环境中运行的客户端工具。 —

适用的端点:

  • /v1/messages 使用 bash 工具