From: Uwe Klein on
Sooraj S wrote:
> Thanks for your help. Now i want to read and write to the same file. I
> want to get alex's current number and change it to 33333
>
> my_code
> ========
> set user "alex"
> set new_num "33333"
>
> set $in [open "data.txt" r+]
> while {[gets $in line] >= 0 } {
> if {[string match $user:* $line] == 1} {
> set len [string length $user]
> set old_num [string replace $line 0 $len ]
> puts "Old number :$old_num" //this
> will print 11111
>
> # here i want to write $new_num to the file...how will i get
> the index#
> }
> }
> close $in

following our conversation I would not be too surprised if
this is a homework assignment?

uwe
From: Bruce on
Sooraj S wrote:
> Thanks for your help. Now i want to read and write to the same file. I
> want to get alex's current number and change it to 33333
>
> my_code
> ========
> set user "alex"
> set new_num "33333"
>
> set $in [open "data.txt" r+]
> while {[gets $in line] >= 0 } {
> if {[string match $user:* $line] == 1} {
> set len [string length $user]
> set old_num [string replace $line 0 $len ]
> puts "Old number :$old_num" //this
> will print 11111
>
> # here i want to write $new_num to the file...how will i get
> the index#
> }
> }
> close $in

OK, you have 2 options, read everything into memeory,
update any values you want, write the whole thing out.

or

Read each line, modify if necessary, write out the
line to temp file, and when done, copy temp file
over original file.

Unless the file is many gigabytes, it's easier to read
it all in on go (imho)

here is a quick version...

# read data as a blob
set in [open "data.txt" r]
set data [read $in]
close $in

# parse data blob
foreach line [split $data \n] {
foreach {user num} [split $line :] {break}
set UserMap([string trim $user]) [string trim $num]
}
#now UserMap is a associative array with all users mapped to number


#modify at will
# - delete old user
unset UserMap($someOldName)
# - add new user
set UserMap($newUser) $newNumber
# modify user
set UserMap($currUser) $modNumber


# write data back out
set out [open "data.txt" w]
foreach {user number} [array get UserMap] {
puts $out "$user: $num"
}
close $out

# or write sorted version
set out [open "data.txt" w]
foreach user [lsort [array names UserMap]] {
puts $out "$user: $UserMap($user)"
}




Bruce

From: Bezoar on
On Jul 16, 12:23 pm, Bruce <Bruce_do_not_...(a)example.com> wrote:
> Sooraj S wrote:
> > Thanks for your help. Now i want to read and write to the same file. I
> > want to get alex's current number and change it to 33333
>
> > my_code
> > ========
> > set user  "alex"
> > set new_num "33333"
>
> > set $in [open "data.txt" r+]
> > while {[gets $in line] >= 0 } {
> >     if {[string match $user:* $line] == 1} {
> >         set len [string length $user]
> >         set old_num [string replace $line 0 $len ]
> >         puts "Old number :$old_num"                           //this
> > will print 11111
>
> >         # here i want to write $new_num to the file...how will i get
> > the index#
> >     }
> > }
> > close $in
>
> OK, you have 2 options, read everything into memeory,
> update any values you want, write the whole thing out.
>
> or
>
> Read each line, modify if necessary, write out the
> line to temp file, and when done, copy temp file
> over original file.
>
> Unless the file is many gigabytes, it's easier to read
> it all in on go (imho)
>
> here is a quick version...
>
> # read data as a blob
> set in [open "data.txt" r]
> set data [read $in]
> close $in
>
> # parse data blob
> foreach line [split $data \n] {
>    foreach {user num} [split $line :] {break}
>    set UserMap([string trim $user]) [string trim $num]}
>
> #now UserMap is a associative array with all users mapped to number
>
> #modify at will
> #  - delete old user
> unset UserMap($someOldName)
> # - add new user
> set UserMap($newUser) $newNumber
> # modify user
> set UserMap($currUser) $modNumber
>
> # write data back out
> set out [open "data.txt" w]
> foreach {user number} [array get UserMap] {
>    puts $out "$user: $num"}
>
> close $out
>
> # or write sorted version
> set out [open "data.txt" w]
> foreach user [lsort [array names UserMap]] {
>    puts $out "$user: $UserMap($user)"
>
> }
>
> Bruce

Bruce's method is fine but assumes that all lines will be strictly
user:id on each line comments or other formats are not handled nor is
the original ordering of the file. Using regsub can help you there as
follows.

#!/bin/sh
# the next line restarts using wish \
exec /opt/usr8.6b.2/bin/tclsh8.6 "$0" ${1+"$@"}
# regsub returns number of substitutions so will only be 1 when it
hits
# the user. Regexp used will handle leading and trailing spaces
and tabs around user
# and the id . Also handles any false alarms e.g if chuck is your
user then achuck:2344
# wont match. Any line like a comment will not match regex and
line will
# be unchanged and be written to the temp file. The order is
maintained

proc changeUser { user newid file } {
set retval 0; # 0 means did not find user in file 1 means
successfully changed user
set fd [ open $file r 0666]
set ofd [ open ${file}.new w 0666] ; # put catches around these
two open calls to return file
# system errors
set reg [subst -nocommands {^[ \t]*($user)[ \t]*:
[ \t]*([0-9]+)} ]
while { ![eof $fd ] && [ gets $fd line] != -1 } {
if { [regsub $reg $line "\\1:$newid" line ] == 1 } {
set retval 1
# you could break out of loop here and just write each line to
outfile
# to avoid the regexp call if you know your user appears
only once.
}
puts $ofd $line
}
close $ofd
close $fd
if { [ catch { file copy -force -- ${file}.new $file } err ] !=
0 } {
set retval 0; # could not copy file
}
return $retval
}

# test

set fd [ open testfile w ]
set origbuff " adrian:2345
achuck : 2322
chuck : 2322
andrea:23443
#this is a comment
// could chuck also be this"
set expectedbuff " adrian:2345
chuck:1111
andrea:23443
#this is a comment
// could chuck also be this
"
puts $fd $origbuff
close $fd

if { [changeUser chuck 1111 testfile ] } {
puts "Change successful"
} else {
puts "Change not successful"
}
# lets check to be sure
set fd [ open testfile r ]
set buffer [read $fd ]
if { $buffer eq $expectedbuff } {
puts "It really did work"
} else {
puts "It really DID NOT work"
puts "expected:\n$expectedbuff\n====\nnewbuff:\n$buffer\n========"
}
file delete testfile

Bezoar
From: Bezoar on
On Jul 18, 2:24 am, Bezoar <cwjo...(a)gmail.com> wrote:
> On Jul 16, 12:23 pm, Bruce <Bruce_do_not_...(a)example.com> wrote:
>
>
>
> > Sooraj S wrote:
> > > Thanks for your help. Now i want to read and write to the same file. I
> > > want to get alex's current number and change it to 33333
>
> > > my_code
> > > ========
> > > set user  "alex"
> > > set new_num "33333"
>
> > > set $in [open "data.txt" r+]
> > > while {[gets $in line] >= 0 } {
> > >     if {[string match $user:* $line] == 1} {
> > >         set len [string length $user]
> > >         set old_num [string replace $line 0 $len ]
> > >         puts "Old number :$old_num"                           //this
> > > will print 11111
>
> > >         # here i want to write $new_num to the file...how will i get
> > > the index#
> > >     }
> > > }
> > > close $in
>
> > OK, you have 2 options, read everything into memeory,
> > update any values you want, write the whole thing out.
>
> > or
>
> > Read each line, modify if necessary, write out the
> > line to temp file, and when done, copy temp file
> > over original file.
>
> > Unless the file is many gigabytes, it's easier to read
> > it all in on go (imho)
>
> > here is a quick version...
>
> > # read data as a blob
> > set in [open "data.txt" r]
> > set data [read $in]
> > close $in
>
> > # parse data blob
> > foreach line [split $data \n] {
> >    foreach {user num} [split $line :] {break}
> >    set UserMap([string trim $user]) [string trim $num]}
>
> > #now UserMap is a associative array with all users mapped to number
>
> > #modify at will
> > #  - delete old user
> > unset UserMap($someOldName)
> > # - add new user
> > set UserMap($newUser) $newNumber
> > # modify user
> > set UserMap($currUser) $modNumber
>
> > # write data back out
> > set out [open "data.txt" w]
> > foreach {user number} [array get UserMap] {
> >    puts $out "$user: $num"}
>
> > close $out
>
> > # or write sorted version
> > set out [open "data.txt" w]
> > foreach user [lsort [array names UserMap]] {
> >    puts $out "$user: $UserMap($user)"
>
> > }
>
> > Bruce
>
> Bruce's method is fine but assumes that all lines will be strictly
> user:id on each line  comments or other formats are not handled nor is
> the original ordering of the file. Using regsub can help you there as
> follows.
>
> #!/bin/sh
>  # the next line restarts using wish \
>  exec /opt/usr8.6b.2/bin/tclsh8.6  "$0" ${1+"$@"}
>     # regsub returns number of substitutions so will only be 1 when it
> hits
>     # the user. Regexp used will handle leading and trailing spaces
> and tabs around user
>     # and the id . Also handles any false alarms e.g if chuck is your
> user then achuck:2344
>     # wont match.  Any line like a comment will not match regex and
> line will
>     # be unchanged and be written to the temp file.  The order is
> maintained
>
> proc changeUser { user newid  file } {
>     set retval 0; # 0 means did not find user in file  1 means
> successfully changed user
>     set fd [ open $file r 0666]
>     set ofd [ open ${file}.new w 0666]  ; # put catches around these
> two open calls to return file
>     # system errors
>     set reg  [subst -nocommands {^[ \t]*($user)[ \t]*:
> [ \t]*([0-9]+)} ]
>     while { ![eof $fd ] && [ gets $fd line] != -1 } {
>         if { [regsub $reg $line "\\1:$newid" line ] == 1 } {
>             set retval 1
>             # you could break out of loop here and just write each line to
> outfile
>             # to avoid the regexp call if you know your user appears
> only once.
>         }
>         puts $ofd $line
>     }
>     close $ofd
>     close $fd
>     if { [ catch { file copy -force -- ${file}.new $file } err ] !=
> 0  } {
>         set retval 0; # could not copy file
>     }
>     return $retval
>
> }
>
> # test
>
> set fd [ open testfile w ]
> set origbuff " adrian:2345
> achuck : 2322
> chuck : 2322
> andrea:23443
> #this is a comment
> // could chuck also be this"
> set expectedbuff " adrian:2345
> chuck:1111
> andrea:23443
> #this is a comment
> // could chuck also be this
> "
> puts $fd $origbuff
> close $fd
>
> if { [changeUser chuck 1111 testfile ] } {
>   puts "Change successful"} else {
>
>  puts "Change not successful"}
>
> # lets check to be sure
> set fd [ open testfile r ]
> set buffer [read $fd  ]
> if { $buffer eq $expectedbuff } {
>   puts "It really did work"} else {
>
>   puts "It really DID NOT work"
>   puts "expected:\n$expectedbuff\n====\nnewbuff:\n$buffer\n========"}
>
> file delete testfile
>
> Bezoar

I should add that if the file is small ( a relative measure that
varies by system and how much memory you've got) you could change the
changeUser like so. Where the whole file is read in and regsub is run
once ( not the -line option on regsub) :

proc changeUser { user newid file } {
set retval 0; # 0 means did not find user in file 1 means
successfully changed user
set fd [ open $file r 0666]
set buffer [read $fd];
close $fd
set reg [subst -nocommands {^[ \t]*($user)[ \t]*:
[ \t]*([0-9]+)} ]
if { [regsub -line -- $reg $buffer "\\1:$newid" buffer] } {
set ofd [ open ${file} w 0666] ;
puts -nonewline $ofd $buffer
close $ofd
set retval 1
}
return $retval
}

Bezoar