用tcl语言处理文件

用tcl语言处理文件,tcl,Tcl,TCL的第一次海报和新手,请原谅我的知识 我找到了一些关于stackoverflow的示例,并利用这些帮助创建了一个脚本 我需要修改一个文件的几行,我已经尝试了以下方法(参见代码)。我似乎可以添加感兴趣的行,但它没有将其写入正确的位置。例如,如果我要替换第3行,则会在第3行之后添加一行 此外,如果有多行操作,则删除后续行 最后,有人可以建议用名称而不是行号来识别感兴趣的行的最佳方法。名称的格式始终为Filter.HpOrd\n= 其中n是0…k info.dat中的数据 AA BB Filter

TCL的第一次海报和新手,请原谅我的知识

我找到了一些关于stackoverflow的示例,并利用这些帮助创建了一个脚本

我需要修改一个文件的几行,我已经尝试了以下方法(参见代码)。我似乎可以添加感兴趣的行,但它没有将其写入正确的位置。例如,如果我要替换第3行,则会在第3行之后添加一行 此外,如果有多行操作,则删除后续行

最后,有人可以建议用名称而不是行号来识别感兴趣的行的最佳方法。名称的格式始终为
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
是一个关键工具-但这很复杂,通常不是一个好主意。写入到单独的文件,并在成功完成写入后将其移动到第一个文件的位置(可能保留原始文件作为备份)要可靠得多。