Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Getting the output of a bash script into a Tcl/Expect script - an issue.

234 views
Skip to first unread message

Kenny McCormack

unread,
Mar 31, 2021, 12:32:26 PM3/31/21
to
I have a portion of an Expect script that looks something like:

set someVar [exec bash -c {
# Lots of bash script code that does stuff, then eventually calculates $Result
# ...
# Finally, echo Result back to the calling script.
echo $Result
}]

It works, but there is a slight problem. The problem is that anywhere in
the bash code that I want to do normal terminal I/O, I have to redirect
with either "< /dev/tty" or "> /dev/tty" or both. In particular, if I forget
to redirect, say, an "echo" statement, then the output of that echo
statement ends up in someVar - screwing up the Expect script, since it
expects to see only the actual result in that variable.

So, as I say, I've got it working, but there was a fair amount of
hair-pulling involved, as I kept forgetting to do the redirection. My
question is: Is there any cleaner way to do this? What I'd like is for
Tcl/Expect to read its final result from some other descriptor, other than
1 (say, 3). Then, the final echo would be:

echo $Result >&3

Is that possible in any way?

Or, any other suggestions to make this cleaner/easier to maintain?

Note: I could write the result to a temp file, then read that back in the
main script. That's do-able, but a little clunky.

--
They say compassion is a virtue, but I don't have the time!

- David Byrne -

Rich

unread,
Mar 31, 2021, 12:47:25 PM3/31/21
to
Kenny McCormack <gaz...@shell.xmission.com> wrote:
> I have a portion of an Expect script that looks something like:
>
> set someVar [exec bash -c {
> # Lots of bash script code that does stuff, then eventually
> # calculates $Result
> # ...
> # Finally, echo Result back to the calling script.
> echo $Result
> }]
>
> It works, but there is a slight problem. The problem is that
> anywhere in the bash code that I want to do normal terminal I/O, I
> have to redirect with either "< /dev/tty" or "> /dev/tty" or both.
> In particular, if I forget to redirect, say, an "echo" statement,
> then the output of that echo statement ends up in someVar - screwing
> up the Expect script, since it expects to see only the actual result
> in that variable.
>
> So, as I say, I've got it working, but there was a fair amount of
> hair-pulling involved, as I kept forgetting to do the redirection.
> My question is: Is there any cleaner way to do this?

One 'cleaner' way, if your code is as you show (meaning you have
control of the bash code) is to utilize Bash's grouping ability to
redirect the whole script output somewhere, then echo the result just
after the group terminates:

man bash:

{ list; }
list is simply executed in the current shell environment.
list must be terminated with a newline or semicolon.
This is known as a group command. The return status is
the exit status of list. Note that unlike the
metacharacters ( and ), { and } are reserved words and
must occur where a reserved word is permitted to be
recognized. Since they do not cause a word break, they
must be separated from list by whitespace or another
shell metacharacter.

So you could surround all of your bash code with {}'s, then redirect
all output from the {} group somewhere, then echo your result:


{ your normal
bash code
goes here; } >& /dev/null
echo $Result

Note, substitute whatever you like, for /dev/null.

Kenny McCormack

unread,
Mar 31, 2021, 1:25:51 PM3/31/21
to
In article <s4292p$9qc$1...@dont-email.me>, Rich <ri...@example.invalid> wrote:
...
>One 'cleaner' way, if your code is as you show (meaning you have
>control of the bash code) is to utilize Bash's grouping ability to
>redirect the whole script output somewhere, then echo the result just
>after the group terminates:
>
>man bash:
>
> { list; }

That's not bad.

Still in the category of a "workaround", but not bad.

I think I will go ahead with that unless/until I hear of a better way.

--
The book "1984" used to be a cautionary tale;
Now it is a "how-to" manual.

Ralf Fassel

unread,
Apr 1, 2021, 5:13:49 AM4/1/21
to
* gaz...@shell.xmission.com (Kenny McCormack)
| I have a portion of an Expect script that looks something like:
>
| set someVar [exec bash -c {
| # Lots of bash script code that does stuff, then eventually calculates $Result
| # ...
| # Finally, echo Result back to the calling script.
| echo $Result
| }]
>
| It works, but there is a slight problem. The problem is that anywhere in
| the bash code that I want to do normal terminal I/O, I have to redirect
| with either "< /dev/tty" or "> /dev/tty" or both. In particular, if I forget
| to redirect, say, an "echo" statement, then the output of that echo
| statement ends up in someVar - screwing up the Expect script, since it
| expects to see only the actual result in that variable.

For output, you could use stderr for the messages, and for input the
stdin of tcl is used anyway:

exec bash -c { echo type your input >&2 ; read line ; echo you typed $line } 2>@stderr

Note the final 'echo' is what 'exec' gets as result. Drawback is if you
'catch' the exec, you don't get any error messages from the script in
the calling code if anything goes wrong (since stderr is redirected).

HTH
R'

Ralf Fassel

unread,
Apr 1, 2021, 5:19:44 AM4/1/21
to
* Ralf Fassel <ral...@gmx.de>
| Drawback is if you 'catch' the exec, you don't get any error messages
| from the script in the calling code if anything goes wrong (since
| stderr is redirected).

Or, thinking about it, redirect and echo to stdout, final result goes to
stderr and catch the whole thing. If errorCode is NONE, all is well and
the final message is your result.

if {[catch {exec bash -c { echo type your input ; read line ; echo you typed $line >&2 } >@stdout} msg] && $::errorCode eq "NONE"} {
# $msg is the script result
}

R'

heinrichmartin

unread,
Apr 2, 2021, 4:14:30 AM4/2/21
to
On Wednesday, March 31, 2021 at 6:32:26 PM UTC+2, Kenny McCormack wrote:
> I have a portion of an Expect script that looks something like:
>
> set someVar [exec bash -c {
> # Lots of bash script code that does stuff, then eventually calculates $Result
> # ...
> # Finally, echo Result back to the calling script.
> echo $Result
> }]

You are writing of Expect, but you are using pure Tcl [exec]. You could switch to an interactive approach, i.e. [expect] prompt and [exp_send] the commands. Your final $Result will be just another iteration. (I played a bit, but -c seems to override -s -- otherwise, it would be a simple solution to -c everything and then interactively (-s) extract the result.)

Note: you want to [stty -echo] or the bash output is mangled with the echo of the input.

> So, as I say, I've got it working, but there was a fair amount of
> hair-pulling involved, as I kept forgetting to do the redirection. My
> question is: Is there any cleaner way to do this? What I'd like is for
> Tcl/Expect to read its final result from some other descriptor, other than
> 1 (say, 3). Then, the final echo would be:

We too have sh-files that prepare the environment, how do I get the environment variables to Tcl? Constrain the format of these files? Or rewrite a sh-interpreter? Or actually use one! I came up with this wrapper:

#!/bin/sh
# displays shell variables in tcl syntax
# usage: $0 <single_file.sh>
# start with a clean environment
env -i /bin/sh --noprofile --norc -- << EOF
# export all
set -a
# source the file, but suppress output
. "$1" &> /dev/null
# print as tcl list
expect -c 'puts [array get env]'
EOF

That can be used with [exec] and returns a Tcl dictionary (also suitable for [array set]). It's actually using four processes to achieve the task (sh, env, sh, expect), but performance is not an issue here.

Note: I am using expect, because my version of tclsh does not have -c or similar.

Kenny McCormack

unread,
Apr 5, 2021, 2:07:02 PM4/5/21
to
In article <4e4335ad-7ff8-45c0...@googlegroups.com>,
heinrichmartin <martin....@frequentis.com> wrote:
>On Wednesday, March 31, 2021 at 6:32:26 PM UTC+2, Kenny McCormack wrote:
>> I have a portion of an Expect script that looks something like:
>>
>> set someVar [exec bash -c {
>> # Lots of bash script code that does stuff, then eventually calculates $Result
>> # ...
>> # Finally, echo Result back to the calling script.
>> echo $Result
>> }]
>
>You are writing of Expect, but you are using pure Tcl [exec]. You could
>switch to an interactive approach, i.e. [expect] prompt and [exp_send]
>the commands. Your final $Result will be just another iteration. (I
>played a bit, but -c seems to override -s -- otherwise, it would be a
>simple solution to -c everything and then interactively (-s) extract
>the result.)

This is actually not too bad. Treat the bash part of the script as just
another Expect process - controlled with the usual spawn/expect/interact
commands. It turns out not to be convenient to do this in my current use
case, but worth keeping in mind for future projects.

Something like:

spawn bash -c {
# stuff that eventually generates a string like: Result: myresult
}
interact -o -re {Result: ([^\r]*)\r\n}
set Result $interact_out(1,string)

--
This is the GOP's problem. When you're at the beginning of the year
and you've got nine Democrats running for the nomination, maybe one or
two of them are Dennis Kucinich. When you have nine Republicans, seven
or eight of them are Michelle Bachmann.

heinrichmartin

unread,
Apr 5, 2021, 3:36:08 PM4/5/21
to
On Monday, April 5, 2021 at 8:07:02 PM UTC+2, Kenny McCormack wrote:
> heinrichmartin wrote:
> >On Wednesday, March 31, 2021 at 6:32:26 PM UTC+2, Kenny McCormack wrote:
> >> I have a portion of an Expect script that looks something like:
> >>
> >> set someVar [exec bash -c {
> >> # Lots of bash script code that does stuff, then eventually calculates $Result
> >> # ...
> >> # Finally, echo Result back to the calling script.
> >> echo $Result
> >> }]
> >
> >You are writing of Expect, but you are using pure Tcl [exec]. You could
> >switch to an interactive approach, i.e. [expect] prompt and [exp_send]
> >the commands. Your final $Result will be just another iteration. (I
> >played a bit, but -c seems to override -s -- otherwise, it would be a
> >simple solution to -c everything and then interactively (-s) extract
> >the result.)
> This is actually not too bad. Treat the bash part of the script as just
> another Expect process - controlled with the usual spawn/expect/interact
> commands. It turns out not to be convenient to do this in my current use
> case, but worth keeping in mind for future projects.
>
> Something like:
>
> spawn bash -c {
> # stuff that eventually generates a string like: Result: myresult
> }
> interact -o -re {Result: ([^\r]*)\r\n}
> set Result $interact_out(1,string)

[expect] is the command you are looking for. [interact] kind of moves yourself out of the way and let's the user take over interaction with the spawned process.

If you already have an anchor in your output, you could just [regexp] that from the overall output, i.e. [exec] with no headache about redirection, then [regexp {(?:^|\r|\n)Result: ([^\r\n]*)[\r\n]*$} $output].
0 new messages