ZetCode

输入和输出

最后修改于 2023 年 10 月 18 日

在本章中,我们将使用 Tcl 中的输入和输出操作。 Tcl 有几个用于进行 io 的命令。 我们介绍其中的几个。

Tcl 使用称为通道的对象来读写数据。 可以使用 opensocket 命令创建通道。 有三个标准通道可供 Tcl 脚本使用,无需显式创建它们。 它们由操作系统为每个新应用程序自动打开。 它们是 stdinstdoutstderr。 标准输入 stdin 用于脚本读取数据。 标准输出 stdout 用于脚本写入数据。 标准错误 stderr 用于脚本写入错误消息。

在第一个示例中,我们使用 puts 命令。 它具有以下概要

puts ?-nonewline? ?channelId? string

channelId 是我们要写入文本的通道。 channelId 是可选的。 如果未指定,则假定为默认值 stdout

#!/usr/bin/tclsh

puts "Message 1"
puts stdout "Message 2"
puts stderr "Message 3"

puts 命令将文本写入通道。

puts "Message 1"

如果未指定 channelId,则默认情况下我们会写入 stdout

puts stdout "Message 2"

此行与前一行执行相同的操作。 我们仅显式指定了 channelId。

puts stderr "Message 3"

我们写入标准错误通道。 默认情况下,错误消息将转到终端。

$ ./printing.tcl 
Message 1
Message 2
Message 3

示例输出。

read 命令

read 命令用于从通道读取数据。 可选参数指定要读取的字符数。 如果省略,该命令将从通道读取所有数据,直到结尾。

#!/usr/bin/tclsh

set c [read stdin 1]

while {$c != "q"} {

    puts -nonewline "$c"
    set c [read stdin 1]
}

该脚本从标准输入通道读取一个字符,然后将其写入标准输出,直到遇到字符 q。

set c [read stdin 1]

我们从标准输入通道 (stdin) 读取一个字符。

while {$c != "q"} {

我们继续读取字符,直到按下 q。

gets 命令

gets 命令从通道读取下一行,返回该行中除换行符以外的所有内容。

#!/usr/bin/tclsh

puts -nonewline "Enter your name: "
flush stdout
set name [gets stdin]

puts "Hello $name"

该脚本请求用户输入,然后打印一条消息。

puts -nonewline "Enter your name: "

puts 命令用于将消息打印到终端。 -nonewline 选项禁止换行符。

flush stdout

Tcl 在内部缓冲输出,因此使用 puts 写入的字符可能不会立即出现在输出文件或设备上。 flush 命令强制立即显示输出。

set name [gets stdin]

gets 命令从通道读取一行。

$ ./hello.tcl
Enter your name: Jan
Hello Jan

该脚本的示例输出。

pwd 和 cd 命令

Tcl 具有 pwdcd 命令,类似于 shell 命令。 pwd 命令返回当前工作目录,而 cd 命令用于更改工作目录。

#!/usr/bin/tclsh

set dir [pwd]

puts $dir

cd ..

set dir [pwd]
puts $dir

在此脚本中,我们打印当前工作目录。 然后我们更改工作目录并再次打印工作目录。

set dir [pwd]

pwd 命令返回当前工作目录。

cd ..

我们将工作目录更改为当前目录的父目录。 我们使用 cd 命令。

$ ./cwd.tcl 
/home/janbodnar/prog/tcl/io
/home/janbodnar/prog/tcl

示例输出。

glob 命令

Tcl 具有一个 glob 命令,该命令返回与模式匹配的文件名。

#!/usr/bin/tclsh

set files [glob *.tcl]

foreach file $files {

    puts $file
}

该脚本将所有扩展名为 .tcl 的文件打印到控制台。

set files [glob *.tcl]

glob 命令返回与 *.tcl 模式匹配的文件列表。

foreach file $files {

    puts $file
}

我们遍历文件列表,并将列表中的每个项目打印到控制台。

$ ./globcmd.tcl 
attributes.tcl
allfiles.tcl
printing.tcl
hello.tcl
read.tcl
files.tcl
globcmd.tcl
write2file.tcl
cwd.tcl
readfile.tcl
isfile.tcl
addnumbers.tcl

这是 globcmd.tcl 脚本的示例输出。

使用文件

file 命令操作文件名和属性。 它有很多选项。

#!/usr/bin/tclsh

puts [file volumes]
[file mkdir new]

该脚本打印系统已安装的卷并创建一个新目录。

puts [file volumes]

file volumes 命令返回系统上已安装的卷的绝对路径。

[file mkdir new]

file mkdir 创建一个名为 new 的目录。

$ ./voldir.tcl 
/
$ ls -d */
doc/  new/  tmp/

在 Linux 系统上,有一个已安装的卷 - 根目录。 ls 命令确认创建了 new 目录。

在以下代码示例中,我们将检查文件名是常规文件还是目录。

#!/usr/bin/tclsh

set files [glob *]

foreach fl $files {

    if {[file isfile $fl]} {
        
        puts "$fl is a file"
    } elseif { [file isdirectory $fl]} {
        
        puts "$fl is a directory"
    }
}

我们遍历当前工作目录中的所有文件名,并打印它是文件还是目录。

set files [glob *]

使用 glob 命令,我们创建一个当前目录的文件和目录名称列表。

if {[file isfile $fl]} {

如果相关文件名是一个文件,我们执行 if 命令的主体。

} elseif { [file isdirectory $fl]} {

file isdirectory 命令确定文件名是目录还是目录。 请注意,在 Unix 上,目录是文件的一种特殊情况。

puts 命令可用于写入文件。

#!/usr/bin/tclsh

set fp [open days w]

set days {Monday Tuesday Wednesday Thursday Friday Saturday Sunday}

puts $fp $days
close $fp

在脚本中,我们打开一个文件进行写入。 我们将一周的日期写入一个文件。

set fp [open days w]

我们打开一个名为 days 的文件进行写入。 open 命令返回一个通道 ID。

set days {Monday Tuesday Wednesday Thursday Friday Saturday Sunday}

此数据将要写入该文件。

puts $fp $days

我们使用 open 命令返回的通道 ID 写入文件。

close $fp

我们关闭打开的通道。

$ ./write2file.tcl
$ cat days 
Monday Tuesday Wednesday Thursday Friday Saturday Sunday

我们运行该脚本并检查 days 文件的内容。

在以下脚本中,我们将从文件读取数据。

$ cat languages 
Python
Tcl
Visual Basic
Perl
Java
C
C#
Ruby
Scheme

我们有一个名为 languages 的简单文件,位于一个目录中。

#!/usr/bin/tclsh

set fp [open languages r]
set data [read $fp]

puts -nonewline $data

close $fp

我们从提供的文件读取数据,读取其内容并将数据打印到终端。

set fp [open languages r]

我们通过以只读模式打开 languages 文件来创建一个通道。

set data [read $fp]

如果未向 read 命令提供第二个参数,则它将从文件读取所有数据,直到文件结尾。

puts -nonewline $data

我们将数据打印到控制台。

$ ./readfile.tcl 
Python
Tcl
Visual Basic
Perl
Java
C
C#
Ruby
Scheme

readfile.tcl 脚本的示例运行。

eof 命令检查所提供的通道的行尾。

#!/usr/bin/tclsh

set fp [open languages]

while {![eof $fp]} {
    puts [gets $fp]
}

close $fp

我们使用 eof 命令读取文件的内容。

while {![eof $fp]} {
    puts [gets $fp]
}

该循环将继续,直到 eof 在遇到文件结尾时返回 true。 在主体内,我们使用 gets 命令从文件读取一行。

$ ./readfile2.tcl 
Python
Tcl
Visual Basic
Perl
Java
C
C#
Ruby
Scheme

readfile2.tcl 脚本的示例运行。

下一个脚本执行一些附加的文件操作。

#!/usr/bin/tclsh

set fp [open newfile w]

puts $fp "this is new file"
flush $fp

file copy newfile newfile2
file delete newfile

close $fp

我们打开一个文件并将一些文本写入其中。 复制该文件。 然后删除原始文件。

file copy newfile newfile2

file copy 命令复制一个文件。

file delete newfile

使用 file delete 命令删除原始文件。

在最后一个示例中,我们使用文件属性。

#!/usr/bin/tclsh

set files [glob *]

set mx 0

foreach fl $files {
    
    set len [string length $fl]

    if { $len > $mx} {
        
        set mx $len
    }
}

set fstr "%-$mx\s %-s"
puts [format $fstr Name Size]

set fstr "%-$mx\s %d bytes"
foreach fl $files {

    set size [file size $fl]

    puts [format $fstr $fl $size]

}

该脚本创建两列。 在第一列中,我们包含文件名。 在第二列中,我们显示文件的大小。

foreach fl $files {
    
    set len [string length $fl]

    if { $len > $mx} {
        
        set mx $len
    }
}

在此循环中,我们找出最长的文件名。 这将用于格式化输出列。

set fstr "%-$mx\s %-s"
puts [format $fstr Name Size]

在这里,我们打印列的标题。 为了格式化数据,我们使用 format 命令。

set fstr "%-$mx\s %d bytes"
foreach fl $files {

    set size [file size $fl]

    puts [format $fstr $fl $size]

}

我们遍历文件列表并打印每个文件名及其大小。 file size 命令确定文件的大小。

$ ./attributes.tcl 
Name           Size
attributes.tcl 337 bytes
newfile2       17 bytes
allfiles.tcl   75 bytes
printing.tcl   83 bytes
languages      51 bytes
hello.tcl      109 bytes
days           57 bytes
read.tcl       113 bytes
files.tcl      140 bytes
globcmd.tcl    82 bytes
write2file.tcl 134 bytes
doc            4096 bytes
cwd.tcl        76 bytes
tmp            4096 bytes
readfile.tcl   98 bytes
isfile.tcl     219 bytes

示例运行。

在本章中,我们介绍了 Tcl 中的输入/输出操作。