I am launching command prompt as:
eval exec [auto_execok start] &
It returns me the pid, and launches command prompt.
Can I control the launched shell? Or is there any other way.
Right now I am sending command at the time of launch like:
eval exec [auto_execok start] "cd Desktop" &
But I want to control the shell for further operations.
You can't control anything launched that way (other than whatever you can do with the pid); it's specifically requesting to have no control at all with the & at the end.
Some programs can be controlled somewhat when they are launched as pipelines.
set pipeline [open |cmd "r+"]
fconfigure $pipeline -buffering line -blocking 0
puts $pipeline "dir"
while {![fblocked $pipeline]} {
set line [gets $pipeline]
puts "I have read: $line"
}
Even more control can be done via Expect, an extension package.
However, the command prompt window can't be controlled by either of these mechanisms; most programs that open windows can't.
I suspect you're trying to reinvent a batch file the hard way.
If you need to perform a series of tasks using cmd.exe, spawn it using
set cmd [open |cmd.exe "r+"]
and then simply write your batch script to that stream:
puts $cmd $myscript
To explain: shells (Tcl's own tclsh and wish included) usually have two modes of execution: interactive and non-interactive. The fisrt is enabled when the shell is started "as is", the second—when it's started in a pipeline1. In interactive mode, the shell would display you its prompt and accept commands—interactively.
In a non-interactive mode, it will just read commands on its standard input stream and execute them as it reads them.
The cmd.exe of Windows is no exception, so you can open |cmd.exe it in "read/write" mode (r+) and write the script composed in its batch command language to its standard input stream which will be bound to the stream object open returns.
If a process started with open or exec writes anything to its standard error stream and/or quits with a non-zero exit code, those commands will raise an exception (that is, error handling is mostly covered for you).
1 Well, for Windows, it's harder to define what "interactive" vs "non-interactive" means, but it's somewhat irrelevant to the question at hand.
Related
I am trying to execute a TCL file from another TCL file.
Example: FileA.tcl is executing FileB.tcl
FileA.tcl has these commands:
open {|FileB.tcl >& FileB.log &} #Executing FileB as a background process
I get this error message: can't read output from command: standard output was redirected.
Also how to capture file descriptor?
In general, there are typically three file descriptors used to communicate between a parent process and a child process. These are the child process's stdin, stdout and stderr (in theory there can be others but it's totally non-standard).
By default, if you do exec THING then the stdin is from /dev/null (or equivalent on Windows), the stdout is a pipe connecting back to the parent process that will become the result of the exec, and the stderr is a pipe connecting back to the parent process that will become the error produced by the exec (if anything's written there).
If there's an & at the end, then the subprocess is run disconnected from the parent process; stdout defaults to going to the parent's real stdout and stderr defaults to going to the parent's real stderr.
If you use open | (which uses the same subprocess launching engine as exec) then you take control over more of that yourself. In particular, when open | is in read mode (default) then stdin is still from /dev/null, but stdout is the pipe that is the result of open | and stderr is collected and will produce an error when you close the main pipe. (If you want to control stdin then you need a non-default open mode, and you can have things bidirectional if you open using a read-write mode. Bidirectional pipes can be a little tricky with buffering and a bit of care is needed to avoid blocking due to all the bits and pieces operating semi-independently.)
All of that is modified by what redirections you use.
Relevant sections of the documentation are:
>& fileName
Both standard output from the last command and standard
error from all commands are redirected to the file named
fileName, overwriting its previous contents.
If the last arg is "&" then the pipeline will be executed in background. In this case the exec command will return a list whose elements are the process identifiers for all of the subprocesses in the pipeline. The standard output from the last command in the pipeline will go to the application's standard output if it has not been redirected, and error output from all of the commands in the pipeline will go to the application's standard error file unless redirected.
In your case, you've got:
open {|FileB.tcl >& FileB.log &}
That's in pure read mode (stdin is from /dev/null by default) and has both stdout and stderr redirected to a file. And is backgrounded. That means there's no pipe there to read from, and no way to interact with the child process at all (other than using the process ID if you could get that). open rejects that; it wants to return you a pipe! If you want such a disconnected child process, use exec.
exec FileB.tcl >& FileB.log &
Within tclsh I can run the following and get the expected output:
% exec bash -c "ulimit -v"
50331648
However within a Tcl script nothing is returned. No error, no output, nothing. There's clearly some gotcha with exec'ing 'bash -c' that I can't work out.
Alternatively, is there a native way in Tcl that I can get the system's memory limit to avoid having to do it this way in the first place?
In an interactive tclsh session, the REPL helpfully prints the output of commands/expressions. That's not the case in a non-interactive program.
exec returns the output of the command: you just need to capture it with the usual command substitution:
set output [exec bash -c "ulimit -v"]
puts $output
The code that you wrote should work; I can't identify why bash would silently fail to run ulimit -v. Even if the script was running in an environment where that was privileged information (why!?) one would still expect to get an error message of some form. That's a very weird problem!
Tcl's base command set doesn't expose any access to memory limits, whether for reading or writing. The simplest workaround that doesn't call an external program is the tclbsd package (apparently it mostly works on most other Unixes as well), which exposes a command that should help:
package require BSD
set limit [bsd::rlimit get soft virtual]
I'm launching multiple tclsh from inside a TCL script to emulate multi threading. However the calls all fail.
I've simplified the problem down to a test where a TCL proc launches a tclsh.
proc launch_tcl {} {
set cmd "tclsh script.tcl"
set pid [ eval $cmd & ]
}
This produces : invalid command name "tclsh"
I can give following lines on the TCL prompt and they work fine.
set cmd "tclsh script.tcl"
set pid [ eval $cmd & ]
I have tried tclsh with full path to the binary as well with same failure.
Why does the same commands fail inside the proc?
Thanks,
Gert
While Tcl syntax looks more related to shell syntax like bash or tcsh tcl is actually more closely related to Perl or PHP or Ruby. Tcl only interprets tcl code thus just typing the name of another executable does not launch that executable*.
Just like Perl or Ruby (or indeed C and C++) tcl does indeed have mechanisms to launch executables. For that you need the exec command:
exec tclsh script.tcl
Warning on how exec works:
Unlike other languages where the command to spawn external binaries accept a string, tcl's exec is more closely related to C or javascript in that it accepts structured data. If you try to do this:
exec "tclsh script.tcl"
You'll get an error complaining "tclsh script.tcl" does not exist. It will look for an executable called "tclsh script.tcl" (because unix, from the very beginning, allows program names to contain spaces). Instead you need to pass the program name and each individual argument separately. Similarly if you tried:
exec tclsh "arg1 arg2"
The exec command will execute tclsh and pass the string "arg1 arg2" as the first argument.
See the documentation of exec for more info: https://www.tcl.tk/man/tcl/TclCmd/exec.htm
What to do if you have a command in a string?
Don't store commands in strings. There's no real safe way to separate arguments in strings in tcl. Instead compose your command as a list and then use the {*} operator to expand the list when you call exec:
set cmd [list tclsh script.tcl]
exec {*}$cmd
*Note: There may be confusion about this because in interactive mode tcl DOES indeed launch executables if you type it. But this is only a feature of interactive mode.
Is it possible to run some interactive shell from inside tclsh? Obviously it's easy to run an interactive shell such as bash or tclsh from inside bash, but I have not found a way to do the reverse.
If I run "exec tclsh" or "exec bash" from inside tclsh I don't get a prompt until I type "exit" and hit enter, or I use "ctrl-C" which kills the parent tclsh.
I would prefer not to use an external package, if at all possible.
Provided you don't want to pass values (other than the exit code) back to the calling Tcl code you can do it pretty easily by redirecting the standard channels so that Tcl doesn't capture them:
exec tclsh <#stdin >#stdout 2>#stderr
This will work for pretty much any subprocess (I've just tested it with vi) and is what tclsh actually does magically for you in interactive mode if it decides to try running a subprocess.
If you want to do anything more complex than that, you probably need to look into using Expect as there's a very long list of tricky gotchas otherwise.
I don't program in TCL but I do use them such as tkcvs and tkdiff. I notice that they declare themselves as shell script
#!/bin/sh
#-*-tcl-*-
What's more, running them through tclsh doesn't work either and I get error like this:
Initialization failed
The second line in the header baffles me too because AFAIK, shell only looks at the #! line. How's this working?
Tcl scripts are normally setup to run using slightly more than you have shown. It is typically and most robustly done like the following:
#!/bin/sh
# \
exec tclsh "$0" ${1+"$#"}
They use the shell initially because there was no standard installation location for tcl so the location could not be relied on. Instead, by starting the system shell and using that to start the tclsh executable you could be certain to run the installed tclsh as long as it was present on the PATH. The shell evaluates the script and sees the exec tclsh "$0" which causes it to execute the installed tclsh binary and pass it $0 (the script file name) as the first argument, which re-runs the script using the tcl interpreter.
The second line in my example comments out the third line when the script is evaluated by the tcl interpreter. The backslash causes the second and third lines to be treated as a single comment when read by tclsh so that tcl doesn't try and run the exec again.
In your snipped the # -*-tcl-*- is marker to indicate the mode to be used by emacs when editing the file. See the documentation.
There is not really enough to go on for the error message. It doesn't seem to be from the Tcl interpreter itself. (That is 'git grep' in the tcl sources does not match that string).