Consider the following short Expect program:
#!/usr/bin/expect
puts $::argc
puts $::argv
If I invoke it as follows, it correctly identifies that there are four elements, but the natural tcl representation of the argv array is not directly passable to a shell.
./Test.exp x y z "a b c"
4
x y z {a b c}
Is it possible to force TCL to output the arguments in a shell-friendly way so that I could pass them to another program via send
?
Just to be explicit, I know I can directly pass the arguments to spawn
or exec
.
However, I would like to know if it is feasible to send
the arguments to a spawned shell (e.g, bash
), which requires correct shell quoting.
This is useful for sending a sequence of commands to bash
using arguments passed to the expect script and being able to log the whole pseudo-interactive session.
The lazy way is to use bash
itself to do the escaping. That way you don't have to account for every special character and edge case that can come up; the shell already knows about them and can handle them for you.
#!/usr/bin/env tclsh
# Take a list and return a single string with the elements of that list
# escaped in a way that bash can split back up into individual elements
proc quote_args {raw} {
string map {"\n" " "} [exec bash -c {printf "%q\n" "$@"} bash {*}$raw]
}
# Demonstrate usage by calling a shell with a single argument including the
# escaped arguments
set quoted_argv [quote_args $argv]
puts "Escaped: $quoted_argv"
puts [exec bash -c "printf \">%s<\\n\" $quoted_argv"]
Example usage:
$ ./args.tcl x y z "a b c"
Escaped: x y z a\ b\ c
>x<
>y<
>z<
>a b c<
$ ./args.tcl x y z $'a b\nc' # Works with embedded newlines
Escaped: x y z $'a b\nc'
>x<
>y<
>z<
>a b
c<
$ ./args.tcl 'a b$c' # And things that look like shell parameters that shouldn't be substituted
Escaped: a\ b\$c
>a b$c<