Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations gkittelson on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

How to execute shell command in tcl? 1

Status
Not open for further replies.

charmdream

Programmer
Oct 26, 2001
25
US
As title.
The shell command may include its arguments.
And can I use pipe-command(|) in the command?

thanks
 
The exec command allows you to execute another program, and optionally capture any output generated by it:

[tt]exec command ?arg? ?arg...?[/tt]

exec supports a variety of I/O redirection options, similar to those supported by standard Unix shells. See the reference page for a complete list. For example, the following command executes the Unix ls command and writes the output to the file ls-output.txt:

Code:
exec ls -l > ls-output.txt

If you don't redirect the standard output of the program, it becomes the return value of the exec command. For example, we could capture the output of ls and store it in a variable as follows:

Code:
set result [exec ls -l]

(Of course, you should always use the built-in Tcl command glob for getting directory listings, rather than calling out to OS programs like ls or dir. It's faster and platform independent.)

exec can handle program pipelines as well, even on Windows systems. With program pipelines, it's the output of the last program in the pipeline that becomes the return value of exec. For example (on Unix):

Code:
set num [sort < /etc/password | uniq | wc -l]

By default, exec blocks; that is, it waits until the program(s) finish before continuing with the rest of your script. That can be a problem sometimes, with programs that take a while to run. For example:

Code:
set list [exec du /]

might take several minutes to return on a system with a lot of disk space. And if a program never returns, your script never regains control.

You can get around this limitation somewhat by running a program in the background. Simply add an &quot;&&quot; as the last argument to your exec command:

Code:
exec du / > du-output.txt &

exec immediately returns in this case. The drawback is that your script doesn't get the output generated by the command. If you don't redirect the output to a file or another program, it's discarded.

As you can see, exec is a reasonable tool for executing short-duration programs and capturing their output, or as a simple program launcher. But it isn't appropriate for long-running programs, or programs with which you want your script to interact (send messages and read responses interactively). In these cases, you need Tcl's more sophisticated interprocess communication tools: interprocess pipes and sockets. If you're interested in these, we can continue this in another message.

By the way, more information about exec is available at the following page on The Tcler's Wiki: - Ken Jones, President
Avia Training and Consulting
866-TCL-HELP (866-825-4357) US Toll free
415-643-8692 Voice
415-643-8697 Fax
 
I got your meaning. But how to deal with the following
case:
There is a user program A, which will convert one
BIG file into another format file. (It will take long long
time if the file is huge). Now I want to display the
result one by one screen.
If running from the shell, I can use
$ A < input.file | more

But if running from tcl program, how to deal with it?
I have tried to use &quot;exec&quot; command, but I couldnot
work it out. :-(

Thanks
 
Well, as I indicated above, exec blocks until the program it runs finishes execution. So it sounds as though exec isn't going to fit your needs in this case. Instead, let's turn our attention to interprocess pipes.

In Tcl, you can create a pipe to a program with the open command. But instead of giving the name of a file to open, you give a &quot;|&quot; character folowed by the name of a program you want to execute. For example:

[tt]set fid [ignore][[/ignore]open &quot;| myprog arg1 arg2&quot; r+[ignore]][/ignore][/tt]

When Tcl executes an open command like this, it starts the program specified, and automatically sets up interprocess communication pipes to it. The return value of open is a channel identifier that you use in exactly the same way as a file identifier when you've opened a regular file. You can use gets to read output a line at a time, and you can use puts to send information to the program. But as far as the program executed is concerned, it's just reading from its standard input and writing to its standard output channels. So, to process the output of a program, you could do the following:

Code:
set fid [open &quot;| myprog&quot; r]
while {[gets $fid line] != -1} {

    # gets returns -1 if it encounters the
    # end of output from the program.
    # If it returns > -1, then we've read
    # a line of output and the characters
    # are stored in the variable &quot;line&quot;.

    # Process the line read as desired...
}
# Close our side of the pipe when we're done
close $fid

Writing is a bit trickier, because Tcl (like virtually all programs that use the standard I/O libraries) by default doesn't immediately send characters to the other program as soon as you puts them. Instead it buffers them until it's got a big block of data (typically 4KB) to send. This is usually more efficient, but not appropriate if we're interacting with another program that's expecting input a line at a time.

There are two solutions. One is to use the flush command to force Tcl to immediately send any characters written with puts. For example:

[tt]puts $fid &quot;Here's a line of input&quot;
flush $fid[/tt]

Another option is to use the fconfigure command to change the communication settings of the channel. In particular, we can set the -buffering option to &quot;line&quot;, which forces Tcl to automatically flush its buffer after every complete line written. For example:

[tt]set fid [ignore][[/ignore]open &quot;| myprog&quot; r+[ignore]][/ignore]
fconfigure $fid -buffering line

# Now each line written with puts is
# immediately sent to the other program
# automatically.

puts $fid &quot;Here's your first line.&quot;
puts $fid &quot;And a second line...&quot;[/tt]

There are still opportunities for Tcl to block, even with this approach. Specifically, gets will block if there's not a complete line of data to read. It will return only when there's a complete line available. Also, you need to be careful if you're integrating code like this into a GUI program. It's easy to get the GUI to lock up while you're trying to read and process data. To prevent this, you need to go to an event-driven approach, where you use the fileevent command to register callbacks, which allow you to be notified when data is available to read, and handle it at that time.

Event-driven I/O is yet another lengthy topic (at least if you do it correctly). That's why I've got a 1-day course entirely devoted to interprocess communication in Tcl using pipes and sockets. The basics are easy, but fitting them all together into a robust, elegant solution requires a bit of thought and planning.

Still, it's a lot easier doing this in Tcl than it is in Java, or even Perl... - Ken Jones, President
Avia Training and Consulting
866-TCL-HELP (866-825-4357) US Toll free
415-643-8692 Voice
415-643-8697 Fax
 
Thanks a lot for your help.

But is there other way that I can make use of the
&quot;more&quot; utility to display the converted result?

Otherwise, how can I control the line number to be
displayed in different window in the &quot;while&quot; process?

I have tried the following method:
call C function to fork new process
( MyProg < input.file | more) in tcl program
It can display the result one screen by one, but the
problem is:
when I press &quot;q&quot; to quit, the sub-processes created
by MyProg will still running until killing them.

BTW, how can I start a new unix terminal to execute
the unix command in tcl?

Thanks again. :)
 
I don't know what avia has to say on this, but it really
looks to me like you could use expect with this particular problem.
In fact in expect the piece of code you just explained
could be written in theory..

if [fork] {
#parent stuff
} else {
#child stuff
exec xterm -e &quot;cat $file | more&quot;
exp_continue
}

This doesn't give you a handle on the xterm process though.
(Plus, I have never had any luck with the -e option from
this state;)-> I would just wrap the command in a script and exec it, this seems to work.))

To do it all in expect would maybe look something like
this:

set cmd &quot;MyProg < input.file | more&quot;
set out &quot;\r\n\[a-zA-Z\].*&quot;

if [fork] {
exp_continue ;#or parent stuff
} else {
;#relies heavily on examples in exploring expect
;# in the child
spawn pty
stty raw -echo < $spawn_id(slave,name)
if {![catch {regexp &quot;.*(.)(.)&quot; $spawn_id mat a1 a2} err_reg]} {
if {[string compare a1 &quot;/&quot;]} {
set a1 &quot;0&quot;
exec xterm -S$c1$c2$spawn_out(slave,fd) &
catch {close -slave} err_close
spawn -noecho $env(SHELL)
send -i $xterm $cmd
}
do_err_diag [info globals err*]
} else {
do_err_diag [info globals err*]
exit
}

Where &quot;do_err_diag&quot;, is a procedure for reporting
errors, the parent either continues, or is occupied
by the original procedure, and the child either
succeeds with the cmd or fails, and a rotuine would
still be needed to anticipate what would happen when
the command returns with info: would you:
expect $out {action}
or simply drop into interact -i $xterm???

I haven't tested any of it, just ideas..

Good Luck



 
Well, it's going to be very difficult to programatically control a program like more properly from plain, ol' Tcl. As marsd suggested, this is the kind of interaction that Expect was created to handle. Though of course, if you're working on Windows, then you've got the issue that the Windows port of Expect isn't really supported, and it's based on a very old branch of the code.

But if you're on Unix, you can use an approach like the one marsd showed above. I, too, would try swiping as much code as I could from Don Libes' Exploring Expect. Personally, I've never tried having Expect spawn off xterms and then try to interact with them, so I don't have any specific tips to help you out there.

Having said all that... If all you're really wanting to do is to let a user see the output of a program, I'd probably just create a Text widget in my application and stuff into it all the output as the program generates it. - Ken Jones, President
Avia Training and Consulting
866-TCL-HELP (866-825-4357) US Toll free
415-643-8692 Voice
415-643-8697 Fax
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top