7

If somebody wants to call external program (which was passed as a Bash argument) from Bash and also pass it command line options (which were also passed as a Bash arguments) the solution is fairy simple:

TCL_SCRIPT="$1"
shift
TCL_SCRIPT_ARGUMENTS="$@"
expect -f "$TCL_SCRIPT" "$TCL_SCRIPT_ARGUMENTS" 2>&1

Is something similar possible in TCL/Expect ?

EDIT: So far I've come with this hack (there are Bash equivalents in comments), which seems that it is working. Can somebody explain lshift procedure?

# http://wiki.tcl.tk/918#pagetocc7993a2b
proc lshift {inputlist} {
  upvar $inputlist argv
  set arg  [lindex $argv 0]
  #set argv [lrange $argv 1 end] ;# below is much faster - lreplace can make use of unshared Tcl_Obj to avoid alloc'ing the result
  set argv [lreplace $argv[set argv {}] 0 0]
  return $arg
}

# BASH: TCL_SCRIPT="$1"
set script [lindex $argv 0]

# BASH: shift
lshift argv

# BASH: TCL_SCRIPT_ARGUMENTS="$@"
set arguments $argv
Wakan Tanka
  • 7,542
  • 16
  • 69
  • 122
  • 2
    That script, as written, does not work with arguments with spaces. Just for the record. You don't store `$@` in a variable, you just use it where you need it. That aside, I'm not sure I understand the question. Are you asking about how to pass arguments to the commands `spawn`ed via `expect`? What does your `expect` script look like? – Etan Reisner Jan 20 '15 at 17:55
  • 2
    If you are using tcl8.6, you could use `exec {*}$::argv 2>@1`. Else, there is always `eval exec $::argv 2>@1`. – user43791 Jan 20 '15 at 17:58
  • @EtanReisner What do you mean by it wont work with spaces? For this purpose I am using qoutes and also `$@` and not `$*` which also represents all positional variables http://tldp.org/LDP/abs/html/internalvariables.html – Wakan Tanka Jan 20 '15 at 18:42
  • @user43791 seems that `$::argv` is the array containing all arguments. Can you please explain what `{*}` stands for? – Wakan Tanka Jan 20 '15 at 18:45
  • 1
    I mean if you have an argument with spaces you will lose the word expansion behaviour that makes `$@` safe when you stuff it into a string. Try the following to see what I mean. `c() { printf 'argc: %s\n' "$#"; printf '$@: %s\n' "$@"; TCL_SCRIPT_ARGUMENTS="$@"; printf 'TCL_SCRIPT_ARGUMENTS: %s\n' "$TCL_SCRIPT_ARGUMENTS"; }; c foo 'bar baz' quux` (Add `set -x` to the start of that function too see it even more clearly.) – Etan Reisner Jan 20 '15 at 18:47
  • @EtanReisner This is interesting I've never though about it this way. Except the fact that you've mentioned (losing the word expansion behavior) the another interesting thing to me is why is `printf '$@: %s\n' "$@";` is called three times when I call it only once? It has something to do with the expansion? Or in another words when `expect -f "$TCL_SCRIPT" "$@"` it expands to three independent expect processes? That seems unlikely to me. – Wakan Tanka Jan 20 '15 at 19:04
  • 1
    `printf` repeatedly uses the format to consume all its input. So when you say `printf '%s\n' a b c` it needs to use the `%s\n` format three times to consume it all. Most commands don't work that way. – Etan Reisner Jan 20 '15 at 19:09
  • @EtanReisner Thank you. One more question. Is it more safe to use: `expect -f "$TCL_SCRIPT" "$@" 2>&1` instead of `expect -f "$TCL_SCRIPT" "$TCL_SCRIPT_ARGUMENTS" 2>&1` – Wakan Tanka Jan 20 '15 at 19:22
  • 1
    @WakanTanka `{*}$var` is the `tcl` equivalent of `"$@"` : it will expand the list so that each list element will be a properly quoted argument. So, say we have `set lst {item1 "item 2" item3}`, `MyProc {*}$lst` is the equivalent of `MyProc item1 {item 2} item3` – user43791 Jan 20 '15 at 21:51

2 Answers2

14

To literally translate your example

set program [lindex $argv 0]
set arguments [lrange $argv 1 end]
spawn $program {*}$arguments

{*} is Tcl's "list expansion" syntax (rule 5 of Tcl's 12 rules of syntax). It splits a list into its element in the current command.

If $argv is foo bar baz, then

spawn [lindex $argv 0] [lrange $argv 1 end]

will invoke foo with 1 argument: "bar baz"

spawn [lindex $argv 0] {*}[lrange $argv 1 end]

will invoke foo with 2 arguments: "bar" and "baz"


Tangentially, I would code your lshift proc like this:

proc lshift {varname} {      
    upvar 1 $varname var
    set var [lassign $var first]
    return $first
}

Then:

expect1.6> set argv {foo bar baz}
foo bar baz
expect1.7> set script [lshift argv]
foo
expect1.8> set script
foo
expect1.9> set argv
bar baz
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
0

I found @glenn jackman's answer to be insufficient for older TCL, namely versions before 8.5, and especially for older Unicies running Expect scipts as well. I went searching and found a note that the "lassign" function is not available in TCL before 8.5: (http://wiki.tcl.tk/1530 , in section "In Tcl prior to 8.5, foreach was used to achieve the functionality of lassign:").

On that same page, under the heading "Example: Perl-ish shift", there is a sentance stating "On the other hand, Hemang Lavana observes that TclXers already have lvarpop ::argv, an exact synonym for shift.") that redirects to this page for "lvarpop": http://wiki.tcl.tk/1965

The code at the end of that page is a simplified version, reproduced here for convenience:

proc lvarpop {upVar {index 0}} {
    upvar 1 $upVar list
    if {![info exists list]} { return "-1" }
    set top [lindex $list $index]
    set list [lreplace $list $index $index]
    return $top
}

In my testing, this works for zero to practically an infinite number of space-separated args. For the TCL-challenged such as myself, this is a godsend.

Ibrahim Khan
  • 20,616
  • 7
  • 42
  • 55
dbc
  • 1