Reputation: 529
I'm modifying the code below, but I have no idea how it works - enlightenment welcome. The issue is that there is a proc in it (cygwin_prefix
) which is meant to create a command, by either
The problem is that the proc returns nothing, but the script magically still works. How? Specifically, how does the line set command [cygwin_prefix filter_g]
actually manage to correctly set command
?
For background, the script simply execs filter_g < foo.txt > foo.txt.temp
. However, historically (this no longer seems to be the case) this didn't work on Cygwin, so it instead ran /usr/bin/env tclsh filter_g < foo.txt > foo.txt.temp
. The script as shown 'works' on both Linux (Tcl 8.5) and Cygwin (Tcl 8.6).
Thanks.
#!/usr/bin/env tclsh
proc cygwin_prefix { file } {
global cygwin
if {$cygwin} {
set status [catch { set fpath [eval exec which $file] } result ]
if { $status != 0 } {
puts "which error: '$result'"
exit 1
}
set file "/usr/bin/env tclsh $fpath"
}
set file
}
set cygwin 1
set filein foo.txt
set command [cygwin_prefix filter_g]
set command "$command < $filein > $filein.temp"
set status [catch { eval exec $command } result ]
if { $status != 0 } {
puts "filter error: '$result'"
exit 1
}
exit 0
Upvotes: 0
Views: 1852
Reputation: 137567
The key to your question is two-fold.
If a procedure doesn't finish with return
(or error
, of course) the result of the procedure is the result of the last command executed in that procedure's body.
(Or the empty string, if no commands were executed. Doesn't apply in this case.)
This is useful for things like procedures that just wrap commands:
proc randomPick {list} {
lindex $list [expr { int(rand() * [llength $list]) }]
}
Yes, you could add in return […]
but it just adds clutter for something so short.
The set
command, with one argument, reads the named variable and produces the value inside the var as its result.
A very long time ago (around 30 years now) this was how all variables were read. Fortunately for us, the $…
syntax was added which is much more convenient in 99.99% of all cases. The only place left where it's sometimes sensible is with computed variable names, but most of the time there's a better option even there too.
The form you see with set file
at the end of a procedure instead of return $file
had currency for a while because it produced slightly shorter bytecode. By one unreachable opcode. The difference in bytecode is gone now. There's also no performance difference, and never was (especially by comparison with the weight of exec
which launches subprocesses and generally does a lot of system calls!)
Upvotes: 2
Reputation: 246764
It's not required to use eval
for exec
. Building up a command as a list will protect you from, for example, path items that contain a space. Here's a quick rewrite to demonstrate:
proc cygwin_prefix { file } {
if {$::cygwin} {
set status [catch { set fpath [exec which $file] } result]
if { $status != 0 } {
error "which error: '$result'"
}
set file [list /usr/bin/env tclsh $fpath]
}
return $file
}
set cygwin 1
set filein foo.txt
set command [cygwin_prefix filter_g]
lappend command "<" $filein ">" $filein.temp
set status [catch { exec {*}$command } result]
if { $status != 0 } {
error "filter error: '$result'"
}
This uses {*}
to explode the list into individual words to pass to exec
.
Upvotes: 2