I am still confuse about eval and exac scenario as below;
1st scenario: exec ping "stackoverflow.com" -n 1
2nd scenario: eval exec [list ping //nologo "stackoverflow.com" -n 1]
3rd scenario: [list eval exec [list ping //nologo "stackoverflow.com" -n 1]]
The questions as below;
1. Difference tree above?
2. what is value number 1?
3. which one is good to use it?
Thanks in advance.
Starting with Tcl 8.5 (current is 8.6.8), the expansion
operator {*} (which breaks a list
into its component words) was added, and eval is rarely needed except
when evaluating scripts and script fragments.
With older versions of Tcl, eval is used instead of the expansion operator.
With the use of the expansion operator, #2 would become:
exec {*}[list ping /nologo "stackoverflow.com" -n 1]
There's nothing wrong with your #1, but there are a couple of common
patterns with the usage of exec where #2 is more useful.
a) Saving the command to be executed allows you to reuse it for a retry
or for debugging.
b) Commands can be built in a dynamic fashion.
foreach {host} [list stackoverflow.com stack_typo_exchange.com superuser.com] {
set cmd [list ping /nologo $host -n 1]
try {
exec {*}$cmd
} on error {err res} {
puts "ERROR: exec: $cmd"
puts " result: $res"
}
}
Older versions of Tcl would use the catch command:
if { [catch {eval exec $cmd}] } {
puts "ERROR: exec: $cmd"
}
Your #3 is (usually) not correct code. It is creating a list out of the return value from eval exec.
References: Tcl / argument expansion, try, catch, exec
Related
I have 2 scripts
listing file 1.mac:
proc setByUpvar { value } {
return [expr {$value *2}]
}
set ex_result [catch {
set c [setByUpvar 12 ]
echo "$c [argument -this]"
sym run macro -file C:/Custom/2.mac
echo "$c [argument -this]"
} errors]
listing file 2.mac:
proc setByUpvar { value } {
return [expr {$value *2}]
}
set ex_result [catch {
set c [setByUpvar 4]
echo "$c [argument -this]"
} errors]
first macro calls second macro
after run file 2.mac, in 1.mac variable $c has value from 2.mac
How protect value $c in 1.mac from overriding in 2.mac?
changing variable name in 2.mac not good for me
Tcl normally doesn't protect you against that. Script files are not normally isolated from each other. By policy/design.
For light isolation, you can put the code in the script files inside a namespace eval:
namespace eval ::mac1 {
proc setByUpvar { value } {
return [expr {$value *2}]
}
# Setting variables directly at the namespace-eval level is a bit tricky so avoid that
proc task {} {
variable ex_result
variable errors
variable c
set ex_result [catch {
set c [setByUpvar 12 ]
echo "$c [argument -this]"
sym run macro -file C:/Custom/2.mac
echo "$c [argument -this]"
} errors]
}
task
}
In this case, the code can see the other variables, but not by default. It has to go looking elsewhere.
For strong isolation, create an interpreter (with interp create) and source the script within it. Interpreters can't reach other interpreters (except under very defined circumstances that don't matter here) so the code really can't have any impact on the other script. However, you might lose access to special commands and so on; isolation really is isolation. And this is a comparatively expensive option. (Not as expensive as starting another process.)
set isolated [interp create]
interp eval $isolated [list source 1.mac]
interp delete $isolated
You can strengthen the isolation by making the interpreter be safe (disables OS access) and you can weaken it by poking callback holes with interp alias to provide the special commands you actually want.
proc myFoo args {
puts "this is a callback in the master interpreter: arguments=$args"
}
interp alias $isolated foo {} myFoo
How any of this will interact with the non-standard Tcl commands you're running (sym, argument) is a complete unknown to me. Standard commands are known, as are procedures using them.
I was wondering how you would find the name of the test you're running in tcl from the test itself? I couldn't find this on google.
I'm calling another proc and passing the name of the test that is calling it, as an argument. So I would like to know which tcl command can do that for me.
This isn't an encouraged use case… but you can use info frame 1 to get the information if you use it directly inside the test.
proc example {contextTest} {
puts "Called from $contextTest"
return ok
}
tcltest::test foo-1.1 {testing the foo} {
example [lindex [dict get [info frame 1] cmd] 1]
} ok
This assumes that you're using Tcl 8.5 or later, but Tcl 8.5 is the oldest currently-supported Tcl version so that's a reasonable restriction.
I read your comments ("source ... instade of my test name") as follows: You seem to source the Tcl script file containing the tests (and Donal's instrumented tcltest), rather than batch-running the script from the command line: tclsh /path/to/your/file.tcl In this setting, there will be an extra ("eval") stack frame which distorts introspection.
To turn Donal's instrumentation more robust, I suggest actually walking the Tcl stack and watching out for a valid tcltest frame. This could look as follows:
package req tcltest
proc example {} {
for {set i 1} {$i<=[info frame]} {incr i} {
set frameInfo [info frame $i]
set frameType [dict get $frameInfo type]
set cmd [dict get $frameInfo cmd]
if {$frameType eq "source" && [lindex $cmd 0] eq "tcltest::test"} {
puts "Called from [lindex $cmd 1]"
return ok
}
}
return notok
}
tcltest::test foo-1.1 {testing the foo} {
example
} ok
This will return "Called from foo-1.1" both, when called as:
$ tclsh test.tcl
Called from foo-1.1
and
$ tclsh
% source test.tcl
Called from foo-1.1
% exit
The Tcl version used (8.5, 8.6) is not relevant. However, your are advised to upgrade to 8.6, 8.5 has reached its end of life.
I have a case when the Tcl script runs a process, which does fork(), leaves the forked process to run, and then the main process exits. You can try it out simply by running any program that forks to background, for example gvim, provided that it is configured to run in background after execution: set res [exec gvim].
The main process theoretically exits immediately, the child process runs in background, but somehow the main process hangs up, doesn't exit, stays in zombie state (reports as <defunct> in ps output).
In my case the process I'm starting prints something, I want that something and I want that the process exit and I state it done. The problem is that if I spawn the process using open "|gvim" r, then I cannot also recognize the moment when the process has finished. The fd returned by [open] never reports [eof], even when the program turns into zombie. When I try to [read], just to read everything that the process might print, it hangs up completely.
What is more interesting, is that occasionally both the main process and the forked process print something and when I'm trying to read it using [gets], I get both. If I close the descriptor too early, then [close] throws an exception due to broken pipe. Probably that's why [read] never ends.
I need some method to recognize the moment when the main process exits, while this process could have spawned another child process, but this child process may be completely detached and I'm not interested what it does. I want something that the main process prints before exitting and the script should continue its work while the process running in background is also running and I'm not interested what happens to it.
I have a control over the sources of the process I'm starting. Yes, I did signal(SIGCLD, SIG_IGN) before fork() - didn't help.
Tcl clears up zombies from background process calls the next time it calls exec. Since a zombie really doesn't use much resources (just an entry in the process table; there's nothing else there really) there isn't a particular hurry to clean them up.
The problem you were having with the pipeline was that you'd not put it in non-blocking mode. To detect exit of a pipeline, you're best off using a fileevent which will fire when either there's a byte (or more) to read from the pipe or when the other end of the pipe is closed. To distinguish these cases, you have to actually try to read, and that can block if you over-read and you're not in non-blocking mode. However, Tcl makes working with non-blocking mode easy.
set pipeline [open |… "r"]
fileevent $pipeline readable [list handlePipeReadable $pipeline]
fconfigure $pipeline -blocking false
proc handlePipeReadable {pipe} {
if {[gets $pipe line] >= 0} {
# Managed to actually read a line; stored in $line now
} elseif {[eof $pipe]} {
# Pipeline was closed; get exit code, etc.
if {[catch {close $pipe} msg opt]} {
set exitinfo [dict get $opt -errorcode]
} else {
# Successful termination
set exitinfo ""
}
# Stop the waiting in [vwait], below
set ::donepipe $pipeline
} else {
# Partial read; things will be properly buffered up for now...
}
}
vwait ::donepipe
Be aware that using gvim in a pipeline is… rather more complex than usual, as it is an application that users interact with.
You might find it easier to run a simple exec in a separate thread, provided your version of Tcl is thread-enabled and the Thread package is installed. (That ought to be the case if you're using 8.6, but I don't know if that's true.)
package require Thread
set runner [thread::create {
proc run {caller targetVariable args} {
set res [catch {
exec {*}$args
} msg opt]
set callback [list set $targetVariable [list $res $msg $opt]]
thread::send -async $caller $callback
}
thread::wait
}]
proc runInBackground {completionVariable args} {
global runner
thread::send -async $runner [list run [thread::id] $completionVariable {*}$args]
}
runInBackground resultsVar gvim …
# You can do other things at this point
# Wait until the variable is set (by callback); alternatively, use a variable trace
vwait resultsVar
# Process the results to extract the sense
lassign $resultsVar res msg opt
puts "code: $res"
puts "output: $msg"
puts "status dictionary: $opt"
For all that, for an editor like gvim I'd actually expect it to be run in the foreground (which doesn't require anything like as much complexity) since only one of them can really interact with a particular terminal at once.
Your daemon can also call setsid() and setpgrp() to start a new session and to detach from the process group. But these don't help with your problem either.
You will have to do some process management:
#!/usr/bin/tclsh
proc waitpid {pid} {
set rc [catch {exec -- kill -0 $pid}]
while { $rc == 0 } {
set ::waitflag 0
after 100 [list set ::waitflag 1]
vwait ::waitflag
set rc [catch {exec -- kill -0 $pid}]
}
}
set pid [exec ./t1 &]
waitpid $pid
puts "exit tcl"
exit
Edit: Another unreasonable answer
If the forked child process closes the open channels, Tcl will not wait on it.
Test program:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
int
main (int argc, char *argv [])
{
int pid;
FILE *o;
signal (SIGCHLD, SIG_IGN);
pid = fork ();
if (pid == 0) {
/* should also call setsid() and setpgrp() to daemonize */
printf ("child\n");
fclose (stdout);
fclose (stderr);
sleep (10);
o = fopen ("/dev/tty", "w");
fprintf (o, "child exit\n");
fclose (o);
} else {
printf ("parent\n");
sleep (2);
}
printf ("t1 exit %d\n", pid);
return 0;
}
Test Tcl program:
#!/usr/bin/tclsh
puts [exec ./t1]
puts "exit tcl"
At first you say:
I need some method to recognize the moment when the main process exits, while this process could have spawned another child process, but this child process may be completely detached and I'm not interested what it does.
later on you say:
If the forked child process closes the open channels, Tcl will not wait on it.
these are two contradictory statements. One one hand you are only interested in the parent process and on the other whether or not the child has finished even thought you also state you aren't interested in child processes that have detached. Last I heard forking and closing the childs copies of the parents stdin,stdout and stderr is detaching (i.e.daemonizing the child process ). I wrote this quick program to run the above included simple c program and as expected tcl knows nothing of the child process. I called the compiled version of the program /tmp/compile/chuck. I did not have gvim so I used emacs but as emacs does not generate text I wrap the exec in its own tcl script and exec that. In both cases, the parent process is waited for and eof is detected. When the parent exits the Runner::getData runs and the clean up is evaluated.
#!/bin/sh
exec /opt/usr8.6.3/bin/tclsh8.6 "$0" ${1+"$#"}
namespace eval Runner {
variable close
variable watch
variable lastpid ""
array set close {}
array set watch {}
proc run { program { message "" } } {
variable watch
variable close
variable lastpid
if { $message ne "" } {
set fname "/tmp/[lindex $program 0 ]-[pid].tcl"
set out [ open $fname "w" ]
puts $out "#![info nameofexecutable]"
puts $out " catch { exec $program } err "
puts $out "puts \"\$err\n$message\""
close $out
file attributes $fname -permissions 00777
set fd [ open "|$fname " "r" ]
set close([pid $fd]) "file delete -force $fname "
} else {
set fd [ open "|$program" "r" ]
set close([pid $fd]) "puts \"cleanup\""
}
fconfigure $fd -blocking 0 -buffering none
fileevent $fd readable [ list Runner::getData [ pid $fd ] $fd ]
}
proc getData { pid chan } {
variable watch
variable close
variable lastpid
set data [read $chan]
append watch($pid) "$data"
if {[eof $chan]} {
catch { close $chan }
eval $close($pid) ; # cleanup
set lastpid $pid
}
}
}
Runner::run /tmp/compile/chuck ""
Runner::run emacs " Emacs complete"
while { 1 } {
vwait Runner::lastpid
set p $Runner::lastpid
catch { exec ps -ef | grep chuck } output
puts "program with pid $p just ended"
puts "$Runner::watch($p)"
puts " processes that match chuck "
puts "$output"
}
Output :
note I exited out of emacs after the child reported that it was exiting.
[user1#linuxrocks workspace]$ ./test.tcl
cleanup
program with pid 27667 just ended
child
parent
t1 exit 27670
processes that match chuck avahi 936 1 0 2016 ?
00:04:35 avahi-daemon: running [linuxrocks.local] admin 27992 1 0
19:37 pts/0 00:00:00 /tmp/compile/chuck admin 28006 27988 0
19:37 pts/0 00:00:00 grep chuck
child exit
program with pid 27669 just ended
Emacs complete
Ok, I found the solution after a long discussion here:
https://groups.google.com/forum/#!topic/comp.lang.tcl/rtaTOC95NJ0
The below script demonstrates how this problem can be solved:
#!/usr/bin/tclsh
lassign [chan pipe] input output
chan configure $input -blocking no -buffering line ;# just for a case :)
puts "Running $argv..."
set ret [exec {*}$argv 2>#stderr >#$output]
puts "Waiting for finished process..."
set line [gets $input]
puts "FIRST LINE: $line"
puts "DONE. PROCESSES:"
puts [exec ps -ef | grep [lindex $argv 0]]
puts "EXITING."
The only problem that remains is that there's still no possibility to know that the process has exited, however the next [exec] (in this particular case probably the [exec ps...] command did this) cleans up the zombie (No universal method for that - the best you can do on POSIX systems is [exec /bin/true]). In my case it was enough that I get one line that the parent process had to print, after which I can simply "let it go".
Still, it would be nice if [exec] can return me somehow the PID of the first process and there's a standard [wait] command that can block until the process exits or check its running state (this command is currently available in TclX).
Note that [chan pipe] is available only in Tcl 8.6, you can use [pipe] from TclX alternatively.
There is tcl procedure which executes command stored in tcl list.
For example:
catch { exec $list}
List looks something like:
--option1 op1 --option2 op2 --option3 op3 ...
One of options is regexp that looks like:
(.*[/\])?(sh|bash)(\.exe)?
After substitution by exec option looks like:
{(.*[/\])?(sh|bash)(\.exe)?}
But what I need is:
"(.*[/\])?(sh|bash)(\.exe)?"
What can I do in such situation?
When a list is converted to a string, it is converted to a canonical form that will convert back to the same list.
What you are seeing are the quoting characters that are used to ensure that the canonical form converts back correctly.
So the value is correct.
exec $list only passes a single argument to exec. exec takes a series of words as arguments, not a list which contains words.
The exec command should be:
catch { exec {*}$list }
The {*} syntax converts the list into its component words.
In older versions of tcl, the eval statement must be used:
catch { eval exec $list }
References: exec, eval, {*} (section 5 of Tcl)
exec is going to execute commnad as subprocess
See semples :
works :
exec ps aux | grep tclsh
not working :
exec "ps aux | grep tclsh"
exec [list ps aux | grep tclsh]
but works fine :
eval exec "ps aux | grep tclsh"
eval exec [list ps aux | grep tclsh]
So we have got evaluating before executing - creation command(exec ps aux | grep tclsh) and invoking it. So eval doesn't care about type.
For current situation with options: exec someExecutable --option1 op1 --option2 op2 --option3 (.*[/\])?(sh|bash)(\.exe)?
I would recoment such solution:
set op3 {"(.*[/\])?(sh|bash)(\.exe)?"}
lappend cmd [someExecutable --option1 op1 --option2 op2 --option3 $op3]
set cmd [join $cmd]
eval exec $cmd
where [join $cmd] creates string from list - and there is not any { }
I would like to export a variable depending on result of a binary command. My TCL script is this:
set A ""
exec sh -c "export A=\"`/usr/local/cuda/samples/1_Utilities/deviceQuery/deviceQuery -noprompt | grep ^Device | wc -l`\""
puts $A
if { $A == "1" } {
set CUDA_VISIBLES_DEVICES 0
} else {
set CUDA_VISIBLES_DEVICES 1
}
With this script, when I execute puts $A I don't get anything in terminal... so in if command I don't know what I evaluating...
My "export" must return ONLY 1 or 0...
Sorry about my poor TCL level.
Thanks.
I guess what you want is something like this:
set a [exec /usr/local/cuda/samples/1_Utilities/deviceQuery/deviceQuery -noprompt | grep ^Device | wc -l]
You set variable a in TCL context and assign the command's return value (i.e. the output text) to it.
The problem is that your exec'd command runs in its own process, so when you set a variable A, that A only exists as a shell variable for the life of that process. When the process exits, A goes away.
The exec command returns the stdout of the command that was exec'd. If you want the result of the command to be in a Tcl variable, you need to set the variable to be the result of the exec:
set A [exec ...]
For more information on the exec command, see the exec man page.