1

I'm trying to exec from tcl, passing a variable that can start with <, (or | or >) as an argument. tcl seems to think I want to direct the input of a file to the command instead of using a literal angle bracket.

set cmd "<hi>"
exec echo $cmd                # couldn't read file "hi>"
eval exec [list echo $cmd]    # couldn't execute "echo <hi>"
eval exec {*}[list echo $cmd] # couldn't read file "hi>"

I cant even get the expected <hi> when hard coding a string

eval exec {*}{"echo" <hi>}   # couldn't read file "hi>"
exec echo {"<hi>"}           # "<hi>"  # but I don't want quotes
exec echo {\<hi>}            # \<hi>   # nor a leading \
exec echo {<hi>}             # couldn't read file "hi>"
exec echo {*}[list "<hi>"]   # '
eval exec echo [list "<hi>"] # '

This is similar to TCL exec with special characters in list, but < throws off the solutions there

Also, maybe worth noting

1) having extra args allows | to go through, but not <

eval exec {*}[list echo {|foo bar} ] # |foo bar  # WORKS? WHAT?
eval exec {*}[list echo {|foo} ]     # illegal use of | or |& in command
eval exec {*}[list echo {<foo bar} ] # couldn't read file "foo"

2) extra spaces or characters are enough to side step the issue (but how to use those with user provided/parsed variable)

exec echo { <hi>}               #  <hi>    # with leading space
exec echo { |hi}                #  |hi     # with leading space
exec echo [list " " $cmd]       # { } <hi> # not useful
exec echo {*}[list " " $cmd]    #            couldn't read file "hi>"
exec echo [join [list "" $cmd]] #  <hi>    # leading space

exec echo [string trim [join [list "" $cmd]]] # couldn't read file "hi>"
Will
  • 1,206
  • 9
  • 22
  • You've found a long-standing issue in how `exec` (and `open |…`) works. Using a subordinate bash shell is the simplest workaround where you can't do it by redirection from a file or pipe. Also, redirection is the only technique that is binary-safe; you can't pass arbitrary binary data as a command line argument (because NUL bytes are always end-of-argument indicators to the OS). – Donal Fellows Mar 05 '19 at 22:48
  • The problem is that this is one place where Tcl's separation of language-level syntax from command-level syntax really doesn't help out. The fixes are conceptually simple, but wreck existing code and make it actually harder to launch subprocesses. I'm guessing we'll eventually have to make a separate command for this and let scripts choose which to use. (I really don't think we'll cut everyone over to it in 9.0 when that comes; simply too much pain for an issue that's fortunately rare in most code.) – Donal Fellows Mar 05 '19 at 22:52

2 Answers2

1

I'm not well versed with what you are trying to do, but with some searching around, I found this post (and since that didn't quite work...) and this post. Together, it seems like the following works:

% exec bash -c {echo "<hi>"}
<hi>

Quoting Colin on the first link:

Tcl's exec does not invoke bash or do this conversion by itself. You can make it work by explicitly calling bash from Tcl

Jerry
  • 70,495
  • 13
  • 100
  • 144
  • ahh that seems like it's a good way around the issue -- make another interpreter deal with the escaping. It gets me half way there. using a variable is the tricky part `set cmd {}; exec bash -c {*}[list echo $cmd]` still complains 'couldn't read file "hi>"' – Will Mar 05 '19 at 18:41
  • @Will sorry, I was looking at something else, I was playing about a bit and found this to work `exec bash -c [list echo '$cmd']` (using double quotes or even hardcoding the command caused "syntax error near unexpected token newline" – Jerry Mar 05 '19 at 19:35
  • You cannot expand the list here because it will result in `exec bash -c echo $cmd` which defeats the purpose of sending the whole command to bash. – Jerry Mar 05 '19 at 19:36
1

one way around this (stealing a bit from @jerry) is to write the command to a temp file and run it with a shell

   set f [file tempfile fn "/tmp/cmd"]
   # open $fn w # unnecessary
   puts $f $cmd
   close $f
   set shcmd "echo \"`cat $fn`\""
   exec sh -c $shcmd 
   file delete $fn
Will
  • 1,206
  • 9
  • 22