Reputation: 75589
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.
Upvotes: 1
Views: 115
Reputation: 75589
I ended up implementing my own version while waiting for other answers, so I am posting for completeness, since the end result looks a little different from the others.
#!/usr/bin/expect
puts $::argc
puts $::argv
# tcl 8.5 does not have lmap, so this implementation is placed here.
proc map {fun list} {
set res {}
foreach element $list {lappend res [$fun $element]}
set res
}
# Turn an individual argument into a string that the shell likes.
proc transformArg {arg} {
if {[string first "'" $arg] == -1} {
return "'$arg'"
}
proc wrapInSingleQuote {x} {return "'$x'"}
return [join [map wrapInSingleQuote [split $arg {'}] ] {"'"}]
}
foreach arg $::argv {
lappend changedArg [transformArg $arg]
}
puts [join "$changedArg" " "]
Upvotes: 0
Reputation: 52579
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<
Upvotes: 4
Reputation: 26727
The string to be ouput needs to be consrtucted:
#!/usr/bin/expect
set output {}
foreach arg $argv {
set escaped_arg [string map {"\\" "\\\\" "\"" "\\\""} $arg]
if {[regexp { } $escaped_arg] || [regexp {'} $escaped_arg]} {
lappend output "\"$escaped_arg\""
} else {
lappend output $escaped_arg
}
}
puts $argc
puts [join $output " "]
Upvotes: -1