0
votes

I am trying to set-up multiple incr's for each entry in a list. I thought that I could assign an integer to each list entry...

set list { 
      {/run      00}
      {/run/shm  00}
      {/boot     00}
}

and use the following code as part of a foreach loop to increment the value...

lset list 1 [expr {[lindex $list 1] + 1}]

What I am finding is that the value increments correctly but when the code executes a second and third time the value has reset to 00, so it never increases past 1 on each pass.

If I set up a basic increment for a standard variable as part of the code..

set counter 00
incr counter 

it quite happily increments on each run of the code and the counter increases by 1 until I break the code.

Any advise or help in getting this working would be much appreciated. I am definitely not a tcl expert so if I am trying to accomplish this the wrong way please let me know. :)

Thanks in advance for your help.

2

2 Answers

1
votes

If you change your data structure slightly to flatten it out instead of using a list of pairs, it becomes usable as a dict. And there's a dict incr command:

This adds the given increment value (an integer that defaults to 1 if not specified) to the value that the given key maps to in the dictionary value contained in the given variable, writing the resulting dictionary value back to that variable. Non-existent keys are treated as if they map to 0. It is an error to increment a value for an existing key if that value is not an integer. The updated dictionary value is returned.

Example usage:

% set list {/run 0 /run/shm 0 /boot 0}
/run 0 /run/shm 0 /boot 0
% dict incr list /boot
/run 0 /run/shm 0 /boot 1
% puts $list
/run 0 /run/shm 0 /boot 1

If you want to do this in a command, you have to pass by name and use upvar so the changes are made in the right stack frame:

% proc demo {fstab_} {
    upvar 1 $fstab_ fstab
    dict incr fstab /run
  }
% demo list
/run 1 /run/shm 0 /boot 1
% puts $list
/run 1 /run/shm 0 /boot 1

And to update every value:

% foreach dir [dict keys $list] { dict incr list $dir }
% puts $list
/run 2 /run/shm 1 /boot 2
0
votes

I would expect that doing this:

for {set idx 0} {$idx < [llength $list]} {incr idx} {
    lset list $idx 1 [expr {[lindex $list $idx 1] + 1}]
}

would increment every numeric value in that list, which is what I believe you want to do. However, doing this:

foreach pair $list {
    lset pair 1 [expr {[lindex $pair 1] + 1}]
}

will not work. Tcl conceptually copies the sublist-items out of the main list in foreach so that the changes to the pairs aren't reflected back. Also, conceptually Tcl also copies the value to hand to foreach in the first place. Of course, these copies are not actually real, as that would be very expensive! Instead Tcl uses shared references with copy-on-write-to-shared semantics, a system that works very well given that we can check the sharing status very cheaply (which is enabled by Tcl's threading model; values are never shared between threads, so sharing-state decisions can be lock-free and local).

A consequence of this is that Tcl explicitly rejects weird at-a-distance state changes of the kinds that cause weird bugs sometimes in languages with different semantics. If you're changing something, it'd better be a variable (as those are the main mutable things) and you'll have it right there in front of you when you do the change.