Search code examples
escapingtcl

TCL exec with variable arg string starting with specials characters like <, >, or |


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>"

Solution

  • 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