用tcl语言处理文件
TCL的第一次海报和新手,请原谅我的知识 我找到了一些关于stackoverflow的示例,并利用这些帮助创建了一个脚本 我需要修改一个文件的几行,我已经尝试了以下方法(参见代码)。我似乎可以添加感兴趣的行,但它没有将其写入正确的位置。例如,如果我要替换第3行,则会在第3行之后添加一行 此外,如果有多行操作,则删除后续行 最后,有人可以建议用名称而不是行号来识别感兴趣的行的最佳方法。名称的格式始终为用tcl语言处理文件,tcl,Tcl,TCL的第一次海报和新手,请原谅我的知识 我找到了一些关于stackoverflow的示例,并利用这些帮助创建了一个脚本 我需要修改一个文件的几行,我已经尝试了以下方法(参见代码)。我似乎可以添加感兴趣的行,但它没有将其写入正确的位置。例如,如果我要替换第3行,则会在第3行之后添加一行 此外,如果有多行操作,则删除后续行 最后,有人可以建议用名称而不是行号来识别感兴趣的行的最佳方法。名称的格式始终为Filter.HpOrd\n= 其中n是0…k info.dat中的数据 AA BB Filter
Filter.HpOrd\n=
其中n
是0…k
info.dat中的数据
AA
BB
Filter.HpOrd_1 = 2
Filter.HpOrd_2 = 2
Filter.HpOrd_3 = 0.1
Filter.HpOrd_4 = 0.2
CC
DD
EE
FF
代码:
set fd [open "info.dat" r+]
set i 0
while { [gets $fd line] != -1 } {
set line [split $line "\n"]
incr i
if {$i == 3} {
set nLine [lreplace $line 0 0 Filter.LoPass]
puts $fd [join $nLine "\n"]
}
if {$i == 6} {
set nLine [lreplace $line 0 0 Filter.Butterworth]
puts $fd [join $nLine "\n"]
}
}
close $fd
TCL只是一种元语言,setfd[open“info.dat”r+]
与一般的文件描述符处理相关。如果您打开一个文件描述符“r+”,您可以读写该文件描述符,但一个文件描述符始终指向文件中的一个点
使用“r+”时,文件描述符最初指向文件的开头。然后从文件中获取$fd line
一行,因此$fd
指向后面第二行的开始。现在您将$fs[join$nline“\n”]
从第二行开始盲目地覆盖,以此类推
通常不能替换一个文件中的行,但在关闭两个文件后,将写入第二个文件并移动该文件。可以使用seek覆盖,但可以从文件中的某个点覆盖。因此,您输入的内容应该始终与您以前读过的内容大小相同。普通文件(基本上在所有编程语言中)是面向字节/字符的,而不是面向行的。这意味着1)需要使用seek
操作返回要覆盖的行的开头,2)除非新行的长度与旧行的长度完全相同,否则将在新行周围出现存根行
你还有其他问题<代码>设置行[split$line”\n“]
不起任何作用:您刚刚从gets
中读取了line
,因此保证其中没有任何新行[join$nLine”\n“]
不会像您可能认为的那样:它将用单个换行符替换$line
中的任何空格序列,但不会在字符串末尾放置任何换行符
除非您的文件大得离谱,否则我建议您这样做:
替换为行号
proc lineReplace args {
set lines [split [lindex $args end] \n]
foreach {n line} [lrange $args 0 end-1] {
set index [incr n -1]
if {$index > 0} {
lset lines $index $line
}
}
join $lines \n
}
package require fileutil
fileutil::updateInPlace info.dat {
lineReplace
3 Filter.LoPass
6 Filter.Butterworth
}
在“前端”中,您仅指定要使用的命令,然后指定成对的行号/新行文本
在“后端”(lineReplace
命令)中,参数args
将包含这些数字/行对,并在末尾作为单个项目包含文件的完整内容。然后将文件内容拆分为一个行列表,并为每个数字/行对替换该列表中的一个项目。最后,行列表被连接回一个字符串,每行之间有换行符。此字符串由lineReplace
返回到fileutil::updateInPlace
,它用返回的字符串替换文件中的旧内容
替换为名称
proc lineReplaceByName args {
set lines [split [lindex $args end] \n]
foreach {name line} [lrange $args 0 end-1] {
set index [lsearch $lines $name*]
if {$index > 0} {
lset lines $index $line
}
}
join $lines \n
}
fileutil::updateInPlace info.dat {
lineReplaceByName
Filter.HpOrd_1 Filter.LoPass
Filter.HpOrd_4 Filter.Butterworth
}
在这种情况下,“后端”通过在每行开头搜索给定名称来计算行号。如果找不到名称,则跳过替换操作。否则和以前一样
仅替换名称
proc lineReplaceByName args {
set lines [split [lindex $args end] \n]
foreach {name line} [lrange $args 0 end-1] {
set index [lsearch $lines $name*]
if {$index > 0} {
lset lines $index $line
}
}
join $lines \n
}
fileutil::updateInPlace info.dat {
lineReplaceByName
Filter.HpOrd_1 Filter.LoPass
Filter.HpOrd_4 Filter.Butterworth
}
如果您不想替换整行,而只是替换其中的名称部分,则需要进行一些更改。如果您100%确定1)名称中从不包含任何空格,2)名称和=
之间始终存在空格,则您可以将lset lines$index$line
替换为lset lines$index 0$line
。如果你想玩得更安全,你可以用
lset lines $index [regsub {.+(?=\s*=\s*)} [lindex $lines $index] $line]
它使用正则表达式查找=
字符前面的字符区域(可以选择在其周围使用空格),然后用您提供的文本替换该字符区域
fileutil
包是Tcl的Tcllib
配套库的一部分
文档:带有纯Tcl的包、、、、、:
# the input and output file handles
set fin [open info.dat r]
set fout [file tempfile fname]
# process the file
while {[gets $fin line] != -1} {
puts $fout [string map {
"Filter.HpOrd_1" "Filter.LoPass"
"Filter.HpOrd_4" "Filter.Butterworth"
} $line]
}
close $fin
close $fout
# backup the original and overwrite it
file link -hard info.dat.bak info.dat
file rename -force -- $fname info.dat
感谢Gents的示例@Glenn我在您的示例中遇到以下错误,错误:无法创建新链接“info.dat.bak”:该路径已存在我创建了一个临时文件并重写了它,这似乎奏效了。看起来在创建硬链接之前,您必须删除.bak文件(如果存在)。从Glenn的示例开始,我将采取一些小步骤,然后再转到您的示例。可以就地重写文件-
chan truncate
是一个关键工具-但这很复杂,通常不是一个好主意。写入到单独的文件,并在成功完成写入后将其移动到第一个文件的位置(可能保留原始文件作为备份)要可靠得多。