Apache Tcl 常用 CLI 命令与工具详解

tclsh 命令行工具全解:交互、管道、脚本三种模式差异、命令行参数与退出码实测,set、expr、string、list、dict、file、exec、clock、info 等内建命令逐一附真实回显。

Apache TclApache Tcl2026/10/12

Apache Tcl 常用 CLI 命令与工具详解

一、tclsh 的三种运行模式

1.1 模式判定

tclsh 8.6 有三种调用方式,tcl_interactive 变量与回显行为不同(Ubuntu 22.04 服务器 + Tcl 8.6.12 实测,演示账号 root):

调用方式tcl_interactive提示符命令结果回显
tclsh8.61%有(每条命令的结果自动打印)
printf ... | tclsh8.60无无(只执行 puts 等显式输出)
tclsh8.6 脚本.tcl0无无

交互模式的"结果回显"是它最大的易用性来源:输入 expr {2**10} 直接看到 1024。脚本模式必须显式 puts,否则不产生任何输出。

1.2 错误处理差异(重要)

同样一段错误脚本,两种模式语义不同——这是实测中最容易踩的坑:

$ cat > /root/tcl-demo/err.tcl <<'EOF'
puts "before"
error "boom"
puts "after"
EOF
$ tclsh8.6 /root/tcl-demo/err.tcl 2>&1
before
boom
    while executing
"error "boom""
    (file "/root/tcl-demo/err.tcl" line 2)
$ echo $?
1

文件脚本遇错立即停止,退出码 1。而管道(stdin)方式遇错会继续执行后续命令:

$ printf "puts \"before\"\nerror \"boom\"\nputs \"after\"\n" | tclsh8.6 2>&1
before
boom
after
$ echo $?
0

结论:生产脚本一律走文件方式执行,靠退出码判断成败;管道方式只适合交互式调试。

二、命令行参数与启动选项

2.1 语法

tclsh ?-encoding name? ?fileName arg arg ...?

tclsh 仅识别 -encoding name 一个选项;其余参数第一个作为脚本文件,剩余全部进入 argv。

2.2 -encoding 选项实测

tclsh8.6 -encoding utf-8 /root/tcl-demo/hello.tcl
hello, Apache Tcl
running with tclsh

2.3 tclsh --help 实测(全文沉淀)

tclsh 不支持 GNU 风格的帮助选项,--help 与 -help 均被静默忽略(实测无任何输出,随后因标准输入为空而正常退出):

$ tclsh8.6 --help
$ echo $?
0
$ tclsh8.6 -help
$ echo $?
0

这就是 tclsh --help 的完整行为:没有帮助文本、没有报错、退出码 0。查询用法请使用手册页 man tclsh 或官方在线手册。

2.4 脚本文件不存在

$ tclsh8.6 /root/tcl-demo/no-such.tcl
couldn't read file "/root/tcl-demo/no-such.tcl": no such file or directory
$ echo $?
1

三、内建命令逐条实测

以下会话均为交互模式实测回显(locale 为 C.UTF-8)。

3.1 set、puts 与变量操作

% set site "quzuo.net"
quzuo.net
% set port 8080
8080
% puts "$site:$port"
quzuo.net:8080
% append site "/docs"
quzuo.net/docs
% incr port 10
8090
% set msg [format "%s listens on port %d" $site $port]
quzuo.net/docs listens on port 8090
% unset msg

set 返回被设置的值;append 原地追加字符串;incr 整数自增(incr port 默认加 1);unset 删除变量。

3.2 expr 表达式求值

% expr {2**10}
1024
% expr {7 / 2}
3
% expr {7 / 2.0}
3.5
% expr {10 % 3}
1
% expr {3.14 * 2}
6.28
% expr {sqrt(2)}
1.4142135623730951
% expr {1e308 * 10}
Inf
% set level [expr {$port > 8000 ? "high" : "low"}]
high

要点:表达式体务必用花括号包裹(字节码编译且避免注入);整数除法向下取整,需要小数时让任一操作数带小数点。

3.3 string 字符串处理

% set s "Apache Tcl"
Apache Tcl
% string length $s
10
% string toupper $s
APACHE TCL
% string tolower $s
apache tcl
% string index $s 0
A
% string range $s 7 9
Tcl
% string match "Apa*" $s
1
% string map {" " "-"} $s
Apache-Tcl
% string trim "  hello  "
hello
% string repeat "ab" 3
ababab
% string reverse $s
lcT ehcapA
% string replace $s 0 6 "Tcl"
TclTcl

3.4 list 列表处理

% set langs {Tcl C Go Python}
Tcl C Go Python
% llength $langs
4
% lindex $langs 0
Tcl
% lindex $langs end
Python
% lrange $langs 1 2
C Go
% lappend langs Rust
Tcl C Go Python Rust
% lsort $langs
C Go Python Rust Tcl
% join $langs " | "
Tcl | C | Go | Python | Rust
% split "a,b,c" ","
a b c
% lassign {10 20 30} p q r
% puts "$p $q $r"
10 20 30

lassign 把列表解构到多个变量(无剩余元素时无回显)。

3.5 dict 字典处理

% set cfg [dict create host 127.0.0.1 port 6379 timeout 3000]
host 127.0.0.1 port 6379 timeout 3000
% dict size $cfg
3
% dict get $cfg host
127.0.0.1
% dict keys $cfg
host port timeout
% dict exists $cfg port
1
% dict set cfg db 0
host 127.0.0.1 port 6379 timeout 3000 db 0
% dict unset cfg timeout
host 127.0.0.1 port 6379 db 0
% dict for {k v} $cfg { puts "$k = $v" }
host = 127.0.0.1
port = 6379
db = 0

3.6 流程控制:if、while、foreach、switch

% if {1 < 2} { puts "yes" } else { puts "no" }
yes
% set i 0
0
% while {$i < 3} { puts "i=$i"; incr i }
i=0
i=1
i=2
% foreach x {a b c} { puts -nonewline "$x," }
a,b,c,% puts ""

% foreach {k v} {color red size 10} { puts "$k -> $v" }
color -> red
size -> 10
% switch [string tolower GO] { go {puts "matched go"} default {puts "no match"} }
matched go

3.7 proc 过程定义

% proc add {a b} { return [expr {$a + $b}] }
% add 3 5
8
% proc greet {name {prefix "Hello"}} { return "$prefix, $name!" }
% greet "Tcl"
Hello, Tcl!
% greet "Tcl" "Hi"
Hi, Tcl!
% proc sum {args} { set t 0; foreach n $args { incr t $n }; return $t }
% sum 1 2 3 4
10

第二参数为默认值({prefix "Hello"}),args 形参收集可变参数。

3.8 file 与文件 IO

% set fd [open /root/tcl-demo/demo.txt w]
file3
% puts $fd "line1: hello tcl"
% puts $fd "line2: 42"
% close $fd
% file exists /root/tcl-demo/demo.txt
1
% file size /root/tcl-demo/demo.txt
27
% set fd [open /root/tcl-demo/demo.txt r]
file3
% set content [read $fd]
line1: hello tcl
line2: 42

% close $fd
% llength [split [string trimright $content "\n"] "\n"]
2
% file dirname /root/tcl-demo/demo.txt
/root/tcl-demo
% file tail /root/tcl-demo/demo.txt
demo.txt
% file extension /root/tcl-demo/demo.txt
.txt
% file mtime /root/tcl-demo/demo.txt
1791807274
% file mkdir /root/tcl-demo/sub
% file delete -force /root/tcl-demo/sub

open 返回通道句柄(如 file3);逐行读取用 while {[gets $fd line] >= 0} {...};大文件避免一次性 read。

3.9 exec 子进程调用

% exec date +%F
2026-10-12
% exec uname -sr
Linux 6.8.0-138-generic
% set out [exec echo hello]
hello
% catch {exec ls /no/such/dir} msg
1
% set msg
ls: cannot access '/no/such/dir': No such file or directory

exec 直接执行外部命令并返回 stdout;子进程非零退出会抛错,配合 catch 捕获后 stderr 文本落在结果变量中。

3.10 clock 时间处理

% clock seconds
1791807274
% clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S"
2026-10-12 12:14:34
% clock clicks
1791807274398321

clock seconds 为 epoch 秒;clock clicks 是高精度微秒计数,用于耗时测量(配合 time)。

3.11 info 自省

% info exists site
1
% info commands string*
string
% info commands ex*
exec expr exit
% info args add
a b
% info body add
 return [expr {$a + $b}]
% info complete {set a 1}
1

info commands <模式> 检索命令名,info args/info body 反查过程签名与实现,常用于运行时诊断。

3.12 namespace 命名空间

% namespace eval net { variable region "cn-north-1"; proc ping {} { return "pong" } }
% net::ping
pong
% namespace eval net { puts "region=$region" }
region=cn-north-1
% namespace children
::zlib ::oo ::net ::msgcat ::tcl
% namespace current
::

四、package require 与内置扩展包

% package require Tcl
8.6.12
% package require http
2.9.5
% package require csv
0.8.1
% package require struct::list
1.8.4
% csv::split {root,"admin user",42} ,
root {admin user} 42

http 是解释器自带包;csv、struct::list 来自 tcllib。tcllib 的统计模块同样即装即用(先 package require math::statistics 加载,实测均值计算):

$ printf "puts [math::statistics::mean {1 2 3 4 5}]\n" | tclsh8.6
3.0

五、退出码体系

实测汇总:

场景命令示例退出码
正常结束(隐式)tclsh8.6 hello.tcl0
显式 exit脚本内 exit0
自定义码脚本内 exit 33
文件脚本抛错脚本内 error "boom"1
脚本文件不存在tclsh8.6 no-such.tcl1
管道脚本抛错后走到 EOFprintf 'error "x"' | tclsh8.60(继续执行语义)
shell 层面未安装tclsh(命令不存在)127

运维脚本建议:错误路径显式 exit 1,分类错误用 exit 2/exit 3 约定语义,不要依赖默认抛错的堆栈文本。

六、info commands 统计

内建命令数量随初始化状态浮动(交互模式实测):

% llength [info commands]
101

首次调用 parray 等 auto_load 过程后,计数变为 102;该数字包含启动初始化与自动加载产生的内部过程(如 unknown、auto_execok、history),不是固定 ABI 值,不要在脚本中写死断言。另实测 8.6.8(CentOS 8 仓库版)交互会话同为 101,且与 8.6.12 管道模式下的命令名列表逐项一致。

七、常见问题

Q1:脚本里写的表达式在交互模式能看到结果,跑脚本却没有输出?

交互模式有结果回显,脚本模式没有。解决:显式 puts [expr {...}]。

Q2:管道方式执行时出错却返回退出码 0?

这是 stdin 模式的"遇错继续"语义(见 1.2 节实测)。解决:生产任务一律 tclsh8.6 文件名 执行,文件方式遇错停止并返回 1。

Q3:tclsh --help 没有输出,是安装坏了吗?

不是。tclsh 仅识别 -encoding,--help/-help 被静默忽略并因 stdin 为空正常退出(退出码 0,见 2.3 节)。用 man tclsh 或官方手册查询用法。

Q4:exec 调用的命令失败,脚本直接中断?

exec 对子进程非零退出抛异常。用 if {[catch {exec cmd} out]} { ... } 或 try ... on error 包裹,stderr 文本在 $out 中。

Q5:交互模式粘贴多行 proc 定义后行为异常?

交互模式逐行解析,跨行花括号体依赖续行提示。建议过程体保持单行(如本文示例),或把定义写入 .tclshrc/脚本文件后 source 加载。

八、参考链接

  1. tclsh 手册页(Tcl 8.6):https://www.tcl-lang.org/man/tcl8.6/TclCmd/tclsh.htm
  2. Tcl 命令手册总目录:https://www.tcl-lang.org/man/tcl8.6/TclCmd/contents.htm
  3. expr 手册页:https://www.tcl-lang.org/man/tcl8.6/TclCmd/expr.htm
  4. string 手册页:https://www.tcl-lang.org/man/tcl8.6/TclCmd/string.htm
  5. dict 手册页:https://www.tcl-lang.org/man/tcl8.6/TclCmd/dict.htm
  6. exec 手册页:https://www.tcl-lang.org/man/tcl8.6/TclCmd/exec.htm
  7. Tcllib 官方站点:https://core.tcl-lang.org/tcllib/
  8. Tcl 官方 Wiki:https://wiki.tcl-lang.org/