2

How Can I access bash variables in tcl(expect) script.

I have bash file say f1.sh which set some variables like

export var1=a1
export var2=a2

These variable I need to use in my expect script .

I tried using this in my script which does not work

system "./f1.sh"
puts "var1 is $::env(var1)"
puts "var2 is $::env(var2)"

But this does not seems to work.

I see that non of the variable from f1.sh are getting set as environment variable.

system "./f1.sh" << # Is this command in my script right ?

How I need to access these bash variables from tcl file.

hek2mgl
  • 152,036
  • 28
  • 249
  • 266
user2622965
  • 23
  • 1
  • 3

4 Answers4

1

I would say that this problem is rather general. First I met this problem, when I wanted to initialize Microsoft Visual Studio environment (which is done using .cmd script) in PoserShell. Later I've faced this problem with other scripting languages in any combinations (Bash, Tcl, Python etc.).

Solution provided by Hai Vu is good. It works well, if you know from the beginning, which variables you need. However, if you are going to use script for initialization of some environment it my contains dozens of variables (which you don't even need to know about, but which are needed for normal operation of the environment).

In general, the solution for the problem is following:

  1. Execute script and at the end print ALL environment variables and capture the output.
  2. Match lines of output for the pattern like "variable=value", where is what you want to get.
  3. Set environment variables using facilities of your language.

I do not have ready made solution, but I guess, that something similar to this should work (note, that snippets below was not tested - they are aimed only to give an idea of the solution):

  1. Execute script; print vars and capture the output (argument expanding - {*} - requires Tcl 8.5, here we can go without it, but I prefer to use it):

    set bashCommand {bash -c 'myScriptName arg1 arg2 2>&1 >/dev/null && export -p'}
    if [catch {*}${bashCommand} output] {
        set errMsg "ERROR: Failed to run script."
        append errMsg "\n" $output
        error $errMsg
    }
    
    ;# If we get here, output contains the output of "export -p" command
    
  2. Parse the output of the command:

    set vars [dict create]
    foreach line [split $output "\n"] {
        regex -- {^declare -x ([[:alpha:]_]*)=\"(.*)\"$} $line dummy var val
        ;# 3. Store var-val pair of set env var.
    }
    
  3. Store var-val pair or set env var. Here several approaches can be used:

3.1. Set Tcl variables and use them like this (depending on context):

set $var $val

or

variable $var $val

3.2. Set environment variable (actually, sub-case of 3.1):

global ::env
set ::env($var) $val

3.3 Set dict or array and use it within your application (or script) without modification of global environment:

set myEnv($var) val          ;# set array
dict set myEnvDict $var $val ;# set dict

I'd like to repeat, that this is only the idea of the receipt. And more important, that as most of the modern scripting languages support regexes, this receipt can provide bridge between almost arbitrary pair of languages, but not only Bash<->Tcl

Dmitrii Semikin
  • 2,134
  • 2
  • 20
  • 25
0

You can use a here-document, like this:

#!/bin/bash
process=ssh
expect <<EOF
  spawn $process
  ...
EOF
hek2mgl
  • 152,036
  • 28
  • 249
  • 266
0

Exported variables are only passed from a parent process to it's children, not the other way around. The script f1.sh (actually the bash instance that's running the script) gets it's own copies of var1 and var2 and it doesn't matter if it changes them, the changes are lost when it exits. For variable exporting to work, you would need to start the expect script from the bash script.

In f1.sh, printf what you want to return...

printf '%s\n%s\n' "$var1" "$var2"

...and read it with exec in Tcl:

lassign [split [exec ./f1.sh] \n] var1 var2
potrzebie
  • 1,768
  • 1
  • 12
  • 25
0

Perhaps I did not look hard enough, but I don't see any way to do this. When you execute the bash script, you create a different process. What happens in that process does not propagate back to the current process.

We can work-around this issue by doing the following (thanks to potrzebie for the idea):

  1. Duplicate the bash script to a temp script
  2. Append to the temp script some commands at the end to echo a marker, and a list of variables and their values
  3. Execute the temp script and parse the output
  4. The result is a list of alternating names and values. We use this list to set the environment variables for our process.

    #!/usr/bin/env tclsh
    
    package require fileutil
    
    # Execute a bash script and extract some environment variables
    proc getBashVar {bashScript varsList} {
        # Duplicate the bash script to a temp script
        set tempScriptName [fileutil::tempfile getBashVar]
        file copy -force $bashScript $tempScriptName
    
        # Append a marker to the end of the script. We need this marker to
        # identify where in the output to begin extracting the variables.
        # After that append the list of specified varibles and their values.
        set f [open $tempScriptName a]
        set marker "#XXX-MARKER"
        puts $f "\necho \\$marker"
        foreach var $varsList {
            puts $f "echo $var  \\\"$$var\\\" "
        }
        close $f
    
        # Execute the temp script and parse the output
        set scriptOutput [exec bash $tempScriptName]
        append pattern $marker {\s*(.*)}
        regexp $pattern $scriptOutput all vars
    
        # Set the environment
        array set ::env $vars
    
        # Finally, delete the temp script to clean up
        file delete $tempScriptName
    }
    
    # Test
    getBashVar f1.sh {var1 var2}
    puts "var1 = $::env(var1)"
    puts "var2 = $::env(var2)"
    
Hai Vu
  • 37,849
  • 11
  • 66
  • 93