
文档教程【免费下载链接】learnxinyminutes-docsCode documentation written as code! How novel and totally my idea!项目地址https://gitcode.com/gh_mirrors/le/learnxinyminutes-docs点击查看免费下载TclTool Command Language由 John Ousterhout 为电路设计工具而创建是一款一切皆字符串的命令式脚本语言脚本、列表与过程体都是字符串全部编程功能含循环与数学运算都以例程routine形式暴露。本文以本仓库 es/tcl.md 西班牙语版教学文档为主体骨架结合英文原版 tcl.md、德语版 de/tcl.md 与俄语版 ru/tcl.md 进行交叉印证系统讲解 Tcl 的命令语法、变量与命名空间模型、内置例程、列表处理、eval用法以及协程机制读完你不仅能读懂任何 Tcl 脚本还能用tclsh亲手验证每一段示例代码。Tcl 是什么设计理念与核心特性Tcl 最初是 John Ousterhout 为自己编写的电路设计工具开发的可复用脚本语言1997 年凭此获得了 ACM 软件系统奖。它可以作为嵌入式脚本语言使用也可以作为通用编程语言甚至可以作为一个可移植的 C 库单独使用——即便完全不需要脚本能力该 C 库也能提供动态字符串、列表、哈希表等数据结构以及加载动态库、字符串格式化与编码转换、文件系统操作、网络操作等可移植功能。本仓库中的西班牙语版 es/tcl.md 列出了 Tcl 几项突出的特性便捷的跨平台网络 API完全虚拟化的文件系统可堆叠stackable的 I/O 通道异步机制深入到内核完整的协程coroutine支持公认健壮且易用的线程模型。一切皆字符串却又不止于字符串Tcl 与 Lisp 有很多共同点但 Lisp 以列表为语言通货Tcl 则以字符串为通货所有值都是字符串列表是有既定格式的字符串过程procedure体即脚本同样是字符串而非代码块。为了性能Tcl 内部会对这些值缓存结构化表示例如列表例程直接操作内部缓存表示仅当脚本确实需要时才更新字符串表示其 copy-on-write写时复制设计允许脚本作者传递大数据值而不产生额外的内存开销。从源码结构看这一字符串表示 内部结构化缓存的双表示模型正是 Tcl 既能保持一切皆字符串统一模型、又能高效处理列表与哈希的核心原因。文档还特别指出过程默认会被自动字节编译byte-compiled除非它们使用了uplevel、upvar、trace这类动态例程。这一点对理解 Tcl 性能特征很重要——大量使用动态栈操作的代码会失去字节编译优化。为什么说编程于 Tcl 是一种享受英文原版 tcl.md 的导言评价道Tcl 会吸引那些觉得 Lisp、Forth 或 Smalltalk 有趣的 hacker也会吸引只想快速完成工作、希望工具屈服于自己意志的工程师与科学家。它把所有程序化功能都暴露为例程——包括其他语言通常内建在语法里的循环和数学运算——这使得它能融入任何项目所需的领域特定功能背后它的语法甚至比 Lisp 更轻几乎不挡路。运行环境tclsh 解释器文档中的示例脚本以#! /bin/env tclsh作为 shebang说明示例面向 Tcl 官方解释器tclshTcl Shell。在 Linux/macOS 上安装 Tcl 后即可用tclsh直接运行.tcl脚本或进入交互式 REPL 逐条验证下面的命令。本文所有代码片段都可以原样粘贴到tclsh中运行。一、设计准则Tcl 不是 Sh 也不是 C西班牙语版文档开篇就给出了一条重要的劝诫Tcl 既不是 Sh 也不是 C。之所以要专门强调是因为 shell 的标准引号习惯在 Tcl 里几乎能用很多人因此捡起 Tcl 后继续沿用其他语言的语法——开始没问题但脚本一旦复杂起来很快就会陷入沮丧。更关键的一条准则是花括号braces是引号机制不是代码块或列表的构造语法。Tcl 根本没有代码块和列表字面量语法这两样东西。花括号用于转义特殊字符因此非常适合引住过程体和应被解释为列表的字符串。理解这一点是读懂 Tcl 一切语法现象的前提。二、语法基础命令、替换与引号命令结构脚本 命令序列 例程调用一个脚本由换行符或分号分隔的命令组成每个命令都是对某个例程的一次调用第一个单词是要调用的例程名后续单词是传给例程的参数单词之间以空白分隔。由于每个参数本身就是命令里的一个单词、即已经是字符串所以可以不加引号set part1 Sal set part2 ut; set part3 ations这里set就是例程名part1与Sal是它的两个参数。变量替换美元符号美元符号引入变量替换多个变量可以拼接set greeting $part1$part2$part3当set只给一个变量名时它返回该变量的值set part3 ;# 返回该变量的值脚本替换方括号左右方括号包裹一个将被求值、并以结果替换进单词的脚本set greeting $part1$part2[set part3]内嵌脚本可以由多个命令组成由最后一个命令提供替换结果set greeting $greeting[ incr i incr i incr i ] puts $greeting ;# 输出 Salutations3命令名本身也是字符串命令中的每个单词都是字符串包括例程名因此命令名也可以参与替换。给定变量赋值set action pu下面三个命令完全等价puts $greeting ${action}ts $greeting [set action]ts $greeting第三个命令展示了用set的返回值拼出命令名这一技巧。反斜杠抑制与赋予特殊含义反斜杠可以抑制字符的特殊含义set amount \$16.42反斜杠也可以给某些字符赋予特殊含义如\nputs lots\nof\n\n\n\n\n\nnewlines花括号引号完全字面量用花括号包裹的单词不受任何特殊解释或替换影响唯一的例外是花括号前的反斜杠在匹配右花括号时不被计入用于转义花括号本身set somevar { This is a literal $ sign, and this \} escaped brace remains uninterpreted }双引号引号空白失去特殊意义在双引号包裹的单词中空白字符失去特殊意义但替换仍然生效set name Neo set greeting Hello, $name复杂变量名与三种取值方式变量名可以是任意字符串例如带空格的变量名set {first name} New花括号形式的变量替换能处理更复杂的变量名set greeting Hello, ${first name}而set本身永远可以替代变量替换且能处理所有变量名set greeting Hello, [set {first name}]展开操作符{*}要把一个列表展开进命令使用展开操作符{*}。下面两个命令等价set name Neo set {*}{name Neo}数组装变量的变量数组是一种特殊的变量充当其他变量的容器set person(name) Neo set person(destiny) {The One} set greeting Hello, $person(name)注意这里person(name)与person(destiny)的括号写法——这是 Tcl 数组元素的标准访问方式。variable与namespace eval命名空间初体验variable用于声明或赋值变量。与set不同——set解析变量名时同时看全局命名空间和当前命名空间——variable只使用当前命名空间variable name Newnamespace eval在命名空间不存在时创建它一个命名空间可以同时容纳例程和变量namespace eval people { namespace eval person1 { variable name Neo } }两个或更多冒号在变量名中分隔命名空间组件namespace eval people { set greeting Hello $person1::name }两个或更多冒号同样在例程名中分隔命名空间组件proc people::person1::speak {} { puts {I am The One.} }完全限定名fully-qualified names以两个冒号开头set greeting Hello $::people::person1::name三、没有更多语法了西班牙语版文档用一个小节郑重宣告其余所有功能都通过例程实现从这一点开始不再有新的语法。接下来学习 Tcl 的全部内容就是了解各个例程的行为以及它们赋予参数的语义。这种最小语法内核 例程化扩展的设计正是 Tcl 能轻易嵌入领域特定功能的原因——连if、for、while都是例程这一点在后面的内置例程一节会反复出现。四、变量与命名空间每个变量和每个例程都关联到某个命名空间。西班牙语版文档用一个极端的例子来揭示 Tcl 的本质可以删掉全局命名空间得到一个什么也干不了的解释器。全局命名空间的名字实际上是空字符串唯一能表示它的方式是作为完全限定名proc delete_global_namespace {} { namespace delete :: }这个例子本身没什么实际用处但它生动地说明命名空间机制是 Tcl 运行时的根基。set与variable的解析差异因为set始终同时盯着全局命名空间和当前命名空间文档建议声明变量或赋值时更安全地使用variable如果全局命名空间里已存在名为name的变量这里用set会赋值给全局变量而不是当前命名空间的变量而variable只操作当前命名空间namespace eval people { namespace eval person1 { variable name Neo } }一旦变量在命名空间中声明set就会看到它而不是看到全局命名空间中同名的变量namespace eval people { namespace eval person1 { variable name set name Neo } }反过来如果set必须新建一个变量它总是相对于当前命名空间创建unset name namespace eval people { namespace eval person1 { set name neo } } set people::person1::name绝对名总是以全局命名空间的名字空字符串开头后跟两个冒号set ::people::person1::name Neo过程中访问命名空间变量在过程内部variable把当前命名空间中的变量链接到局部作用域namespace eval people::person1 { proc fly {} { variable name puts $name is flying! } }注意namespace eval people::person1这种写法等于直接在完全限定路径上创建或进入嵌套命名空间。五、内置例程数学、流程控制、列表与元编程expr数学与表达式求值数学运算用expr完成set a 3 set b 4 set c [expr {$a $b}]由于expr会自己执行变量替换必须用花括号把表达式包起来防止 Tcl 先做变量替换原文档在此指向 Tcl Wiki 上著名的 Brace your expr-essions 讨论建议读者在tclsh里对比[expr $a $b]与[expr {$a $b}]的行为差异。expr同时理解变量替换和脚本替换set c [expr {$a [set b]}]expr还提供一组数学函数set c [expr {pow($a,$b)}]数学运算符也是例程数学运算符以例程形式存在于::tcl::mathop命名空间::tcl::mathop:: 5 3例程可以从其他命名空间导入namespace import ::tcl::mathop:: set result [ 5 3]字符串比较与运算符回退非数值必须加引号eq等运算符可以把操作限制为纯字符串比较set name Neo expr {{Bob} eq $name}一般运算符如在数值运算不可行时回退为字符串比较expr {{Bob} $name}proc定义新例程proc greet name { return Hello, $name! }可以指定多个参数proc greet {greeting name} { return $greeting, $name! }如前所述花括号并不构造代码块——每个值包括proc的第三个参数都是字符串所以上一个命令可以完全不用花括号重写用反斜杠转义空白、美元符号和引号proc greet greeting\ name return\ \\$greeting,\ \$name!\args可变参数当最后一个参数是字面量args时所有额外传入的参数被收集到一个列表并赋给argsproc fold {cmd first args} { foreach arg $args { set first [$cmd $first $arg] } return $first } fold ::tcl::mathop::* 5 3 3 ;# - 45这里fold把::tcl::mathop::*作为命令逐项折叠演示了把例程当值传递的函数式风格。条件执行if是例程条件执行也以例程形式实现if {3 4} { puts {This will never happen} } elseif {4 4} { puts {This will also never happen} } else { puts {This will always happen} }注意elseif与else分支体同样用花括号引住——它们是传给if例程的字符串参数。循环for与while是例程循环同样以例程实现。for的第一和第三个参数被当作脚本处理第二个参数被当作表达式处理set res 0 for {set i 0} {$i 10} {incr i} { set res [expr {$res $i}] } unset reswhile的第一个参数也被当作表达式set i 0 while {$i 10} { incr i 2 }列表字符串、格式与安全操作列表是字符串列表项以空白分隔set amounts 10\ 33\ 18 set amount [lindex $amounts 1]列表项内部的空白必须被引住set inventory {item 1 item\ 2 {item 3}}修改列表时通常更推荐使用列表例程lappend inventory {item 1} {item 2} {item 3}花括号和反斜杠可用于在列表中格式化更复杂的值。一个列表看起来与脚本完全一样唯一区别是换行符和分号失去特殊含义并且没有脚本或变量替换。这一特性使 Tcl 具有同像性homoiconic——代码与数据用同一种结构表达。下面这个列表里有三个元素set values { one\ two {three four} five\{six }由于列表与其他值一样都是字符串字符串操作可以直接施加于列表但有破坏列表格式的风险set values {one two three four} set values [string map {two \{} $values] ;# $values 不再是格式正确的列表获取格式正确列表的可靠方法是使用专门的列表例程set values [list one \{ three four] lappend values { } ;# 把单个空格作为一个列表项加入eval把值当作脚本求值eval用于把一个值当作脚本求值eval { set name Neo set greeting Hello, $name }一个列表总能作为单个命令组成的脚本传给evaleval {set name Neo} eval [list set greeting Hello, $name]因此用eval时应当用list构造想要的命令set command {set name} lappend command {Archibald Sorbisol} eval $command常见陷阱不用列表函数构造命令一个常见错误是构造命令时不使用列表函数。下面的代码用append字符串拼接而非lappend列表追加导致eval执行时set收到过多参数而报错set command {set name} append command { Archibald Sorbisol} try { eval $command ;# 错误{set name Archibald Sorbisol} 中 set 参数过多 } on error {result eoptions} { puts [list {received an error} $result] }同样的错误很容易通过subst触发——因为subst把变量替换进字符串时不会做列表格式化set replacement {Archibald Sorbisol} set command {set name $replacement} set command [subst $command] try { eval $command ;# 与上面相同的错误 } trap {TCL WRONGARGS} {result options} { puts [list {received another error} $result] }而list能为替换正确格式化值——list会把内含空白的值括进花括号set replacement [list {Archibald Sorbisol}] set command {set name $replacement} set command [subst $command] eval $command这正是list最常见的用途为向脚本中替换而格式化值。try ... on error ...与try ... trap {TCL WRONGARGS} ...展示了 Tcl 8.6 的错误处理例程trap可按错误代码精确捕获特定错误类别。apply即席匿名例程apply求值一个两元素列表把它当作例程set cmd {{greeting name} { return $greeting, $name! }} apply $cmd Whaddup Neo第三个元素可用于指定应用该例程的命名空间set cmd [list {greeting name} { return $greeting, $name! } [namespace current]] apply $cmd Whaddup Neouplevel与upvar操作调用栈uplevel在调用栈的更高层级求值一个脚本proc greet {} { uplevel {puts $greeting, $name} }配合list构造命令uplevel可以安全地修改调用者的变量proc set_double {varname value} { if {[string is double $value]} { uplevel [list variable $varname $value] } else { error [list {not a double} $value] } }upvar把调用栈当前层级的变量链接到更高层级的变量即按引用传递proc set_double {varname value} { if {[string is double $value]} { upvar 1 $varname var set var $value } else { error [list {not a double} $value] } }注意upvar 1 $varname var中的数字1它表示向上回溯一层调用栈。uplevel/upvar属于文档导言中提到的动态例程——使用它们的过程无法享受字节编译优化。rename与重定义while证明一切皆例程既然循环是例程就可以先删掉内建的while再用proc定义一个新的rename ::while {} proc while {condition script} { if {[uplevel 1 [list expr $condition]]} { uplevel 1 $script tailcall [namespace which while] $condition $script } }这段代码用rename ::while {}把内建while改名成空串即删除然后以uplevel在调用者层级求值条件与循环体用tailcall实现尾递归继续循环——tailcall用新命令替换当前调用栈帧从而避免深递归耗尽栈空间。这个例子是Tcl 一切皆例程的最佳证明。协程coroutine、yield与yieldtocoroutine创建一条新的调用栈、一个进入该栈的新例程然后立即调用该例程yield在该栈中挂起求值并把控制权交还给调用方栈。下面的countdown演示了基础协程proc countdown count { # 向协程创建者返回一个值从而暂时挂起本调用栈 yield [info coroutine] while {$count 1} { yield [incr count -1] } return 0 } coroutine countdown1 countdown 3 coroutine countdown2 countdown 5 puts [countdown1] ;# - 2 puts [countdown2] ;# - 4 puts [countdown1] ;# - 1 puts [countdown1] ;# - 0 catch { puts [countdown1] ;# - invalid command name countdown1 } cres copts puts $cres puts [countdown2] ;# - 3要点coroutine countdown1 countdown 3创建名为countdown1的命令每次调用它协程从上次yield处继续。第一次yield [info coroutine]把协程名返回给创建者当计数递减到 1 后协程返回 0 并结束此后调用countdown1会抛出 invalid command name 错误由catch捕获。两个协程各自持有独立的调用栈与局部状态互不干扰。协程栈之间还能互相让渡控制权——yieldto把当前栈的控制权转交给另一条协程栈。文档中的经典传递盐与胡椒示例展示了三个协程a、b、c如何通过pass例程互相传递请求与响应proc pass {whom args} { return [yieldto $whom {*}$args] } coroutine a apply {{} { yield set result [pass b {please pass the salt}] puts [list got the $result] set result [pass b {please pass the pepper}] puts [list got the $result] }} coroutine b apply {{} { set request [yield] while 1 { set response [pass c $request] puts [list [info coroutine] is now yielding] set request [pass a $response] } }} coroutine c apply {{} { set request [yield] while 1 { if {[string match *salt* $request]} { set request [pass b salt] } else { set request [pass b huh?] } } }} # 启动一切 apass用yieldto把控制权连同参数交给whom指定的协程并返回对方让渡回来的结果b在其中扮演中转站c根据请求内容是否包含salt决定应答。这是 Tcl 协程支持完整的直观体现——协程不仅可用于惰性生成器如countdown还能构建复杂的协作式调度。在 learnxinyminutes-docs 仓库中的定位与维护本仓库learnxinyminutes-docs以用代码写成的文档为理念README.md 说明每个文件都是带注释的有效代码式教程。Tcl 教程在仓库中拥有完整的翻译矩阵英文原版 tcl.mdfilename: learntcl.tclfrontmatter 含name: Tcl与贡献者信息、西班牙语版 es/tcl.md译者 Héctor Romojaro、德语版 de/tcl.md译者 Martin Schimandl以及俄语版 ru/tcl.md。四个版本代码示例完全一致仅在导言、注释语言与末尾参考资料上存在差异——这使它们成为对照学习 Tcl 术语如 braces 在西班牙语版中对应 las llaves的好材料。仓库通过 lint/frontmatter.py 对每个 Markdown 文件的 YAML frontmatter 做规范性检查它校验contributors、translators、name、filename等允许的键及其类型例如 contributor 条目必须是[名字, 链接]形式的二元列表并用 yamllint 做 YAML 语法检查。这意味着本文所依据的 es/tcl.md 等文档都经过统一的元数据格式约束其translators字段即由此机制维护。若想为仓库贡献新的 Tcl 相关翻译或修正可参考 CONTRIBUTING.md 的规范尽量用代码示例代替文字说明、代码块行宽控制在 80 字符内、并为 issue/PR 加上[language/lang-code]前缀标签。结语从语法到哲学的完整闭环回顾整篇教程可以提炼出 Tcl 的三层递进第一层是极小的语法内核——命令、单词、替换、引号四件事第二层是没有更多语法——if、for、while、数学运算全部例程化语言的可扩展性因此没有边界重定义while一节就是证明第三层是高级运行时机制——命名空间、调用栈操作uplevel/upvar、同像的列表-脚本统一表示以及完整的协程支持。掌握了 es/tcl.md 中这些例程的语义你就不再需要学习任何新语法剩下的只是熟悉 Tcl 标准库中数百个例程的行为——而这正是 Tcl 文档与 Wiki 存在的意义。赞分享文档教程【免费下载链接】learnxinyminutes-docsCode documentation written as code! How novel and totally my idea!项目地址https://gitcode.com/gh_mirrors/le/learnxinyminutes-docs点击查看免费下载相关推荐使用 R 语言完成统计分析learnxinyminutes-docs 西班牙语版 R 教程全解使用 R 语言完成统计分析learnxinyminutes docs 西班牙语版 R 教程全解 本篇技术指南以 es/r.md https://link.gi文档教程Learn X in Y minutesDart 语言西班牙语速成教程learnxinyminutes-docs 仓库解读Learn X in Y minutesDart 语言西班牙语速成教程learnxinyminutes docs 仓库解读 本文以 es/dart.md文档教程Julia 语言速成指南全解析learnxinyminutes-docs 西班牙语教程精讲与 0.3 到 1.x 语法对照Julia 语言速成指南全解析learnxinyminutes docs 西班牙语教程精讲与 0.3 到 1.x 语法对照 本文以 learnxinyminu文档教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考