how to pass command line parameter containing '<' to 'exec' - tcl

$ date > '< abcd'
$ cat '< abcd'
<something>
$ tclsh8.5
% exec cat {< abcd}
couldn't read file " abcd": no such file or directory
whoops. This is due to the the specification of 'exec'.
If an arg (or pair of args) has one of the forms described below then it is used by exec to control the flow of input and output among the subprocess(es). Such arguments will not be passed to the subprocess(es). In forms such as “< fileName”, fileName may either be in a separate argument from “<” or in the same argument with no intervening space".
Is there a way to work around this?

Does the value have to be passed as an argument? If not, you can use something like this:
set strToPass "< foo"
exec someProgram << $strToPass
For filenames, you can (almost always) pass the fully qualified name instead. The fully qualified name can be obtained with file normalize:
exec someProgram [file normalize "< foo"] ;# Odd filename!
But if you need to pass in an argument where < (or >) is the first character, you're stuck. The exec command always consumes such arguments as redirections; unlike with the Unix shell, you can't just use quoting to work around it.
But you can use a helper program. Thus, on Unix you can do this:
exec /bin/sh -c "exec someProgram \"$strToPass\""
(The subprogram just replaces itself with what you want to run passing in the argument you really wanted. You might need to use string map or regsub to put backslashes in front of problematic metacharacters.)
On Windows, you have to write a batch file and run that, which has a lot of caveats and nasty side issues, especially for GUI applications.

One simple solution: ensure the word does not begin with the redirection character:
exec cat "./< abcd"
One slightly more complex:
exec sh -c {cat '< abcd'}
# also
set f {< abcd}
exec sh -c "cat '$f'"
This page on the Tcl Wiki talks about the issue a bit.

Have you tried this?
% exec {cat < abcd}

Try:
set myfile "< abcd"
exec cat $myfile

Related

How to download and then use the file in the same tcl script?

I'm new using Tcl and I have the following script:
proc prepare_xml {pdb_id} {
set filename [exec wget ftp://ftp.ebi.ac.uk/pub/databases/msd/sifts/xml/$pdb_id.xml.gz]
set filename_unzip [exec gunzip "$pdb_id.xml.gz"]
set ready_xml [exec sed -i "/entry /c\<entry>" "$pdb_id.xml"]
return $ready_xml
}
The expected output is the file "filename" uncompress and modified. However, when I execute it the first time, it only downloads the file and it does not uncompress it. If I execute it for a second time, I obtained the expected output and a second copy of the original downloaded file.
Can anyone help me with this? I've tried with after and vwait commands but it doesn't work.
Thank you :)
It's hard to say for sure as you're not describing whether any errors are thrown (that'd be the only reason for the code to not run to completion), but I'd expect something like this to be the right approach:
proc prepare_xml {pdb_id} {
# Double quotes on next line just because of Stack Overflow highlighter
set url "ftp://ftp.ebi.ac.uk/pub/databases/msd/sifts/xml/$pdb_id.xml.gz"
set file $pdb_id.xml
append sedcode {/entry /} "c\\\n" {<entry>}
exec wget -q -O - $url | gunzip -c | sed $sedcode > $file
return $file
}
Firstly, I'm keeping complicated bits in (local) variables to stop the exec line from getting too long. Secondly, I've put all the subprocesses together in the one pipeline. Thirdly, I'm using -q and -O - with wget, and -c with gunzip; look up what they do if you don't understand them. Fourthly, I've put the scriptlet for sed in braces where possible to stop there from being trouble with backslashes, but I've used append and a non-backslashed section to make the pattern because the syntax of c in sed is downright weird (it needs a backslash-newline sequence immediately after on at least some platforms…)
I'd actually use native Tcl code to extract and transform the data if I was doing it for me, but that's a rather larger change.

In Tcl, how do I prevent a variable's contents from being interpreted by the shell when passed to `exec`?

I'm maintaining some old code and found that the following piece...
if {[catch {exec -- echo $html_email > $file} ret]} {
puts $ret
return 0
}
...breaks due to the first character of an HTML email being <, i.e.
couldn't read file "html>
<title>cama_Investigate 00000560554PONY1</title>
<style type="text/css">
...
...
...
which is interpreted as an I/O redirect operator. Previously this wasn't an issue because we were starting the emails with some headers, e.g.
append html_email "Content-Type : text/html; charset=us-ascii\n"
append html_email "Content-Disposition: inline\n"
I'm going to rewrite all this to use Tcl's native file I/O, so this question is mainly academic: What is the proper way to guard a variable's contents from being interpreted by the shell when passed to exec?
I'm using Tcl 8.0.5 and csh, but I'm interested in a general answer if possible.
Tcl's exec is funky, alas. It insists on interpreting an argument that starts with a < character as a redirect. (There are a few other ones too, but you're a bit less likely to hit them.) There isn't a good general workaround either except to write the data to a temporary file and redirect from that.
set ctr 0
while 1 {
set filename /tmp/[pid].[incr ctr].txt
# POSIX-style flags; write-only, must create or generate error
if {[catch {open $filename {WRONLY CREAT EXCL}} f] == 0} break
}
puts $f $html_email
close $f
exec echo <$filename >$file
file delete $filename
This is horribly complicated! We can do much better by changing what program we use. If instead of using echo we use cat, we can use exec's heredoc syntax:
exec cat <<$html_email >$file
Since in this case the characters are being passed directly via a pipeline (which is how Tcl does this) there's far less to go wrong. Yet it's still silly since Tcl's entirely capable of writing to files directly, more portably, and with less overhead:
set f [open $file "w"]
puts $f $html_email
close $f
Yes, this is actually a hugely simplified version of the general replacement from the first example above. Let's do the simple things that are much more obviously correct since then there's less to surprise in the future.
You can invoke the intended command indirectly, routing it through a shell:
exec -- csh -c "echo '$html_email'" > $file
or
exec -- csh -c "exec echo '$html_email'" > $file

Using globs in Perl replace one liner in TCL script

I want to run one Perl one liner in TCL script as below:
exec perl -i -pe {s/SUBSTRING/REPLACING_STRING/g} testFile;
This works fine. But, if I want to modify all the files like below:
exec perl -i -pe {s/SUBSTRING/REPLACING_STRING/g} *;
it gives me the error message:
Can't open '*': No such file or directory.
while executing
exec perl -i -pe {s/SUBSTRING/REPLACING_STRING/g} *;
I tried bracing the '*', but did not solve the problem. Requesting for help...
Assuming that the files a, b, and c are present in the current working directory, executing echo * in the shell prints a b c. This is because the shell command evaluator recognizes wildcard characters and splices in a list of zero or more file names where the wildcard expression was found.
Tcl's command evaluator does not recognize wildcard characters, but passes them unsubstituted to the command that was invoked. If that command can work with wildcards it will do so. The exec command doesn't, which means it will pass the wildcard expression as is to the shell command named by the command string.
Testing this, we get
% exec echo *
*
because what we asked the shell to execute was simply
echo *
If we want a wildcard expression expanded to a list of file names, we need an explicit call to the glob command:
% exec echo [glob *]
"a b c"
which still isn't quite right, since the list wasn't automatically spliced into the command string: instead the shell got
echo {a b c}
(Note: I’m faking echo on Windows here, the actual output might be different.)
To both expand and splice the list of file names, we need this:
% exec echo {*}[glob *]
a b c
The {*} prefix tells the Tcl command evaluator to substitute the following argument as if the words resulting from it were arguments in the original command line.
echo a b c
This example, with a more concise explanation than I've given here, is in the documentation for exec:
"If you are converting invocations involving shell globbing, you should remember that Tcl does not handle globbing or expand things into multiple arguments by default. Instead you should write things like this:"
exec ls -l {*}[glob *.tcl]
PS:
If one has loaded the fileutil package:
package require fileutil
this can be written as a one-liner in Tcl too:
foreach file [glob *] {::fileutil::updateInPlace $file {apply {str {regsub -all SUBSTRING $str REPLACING_STRING}}}}
or with line breaks and indentation for readability:
foreach file [glob *] {
::fileutil::updateInPlace $file {
apply {str {
regsub -all SUBSTRING $str REPLACING_STRING
}}
}
}
Documentation: apply, exec, fileutil package, foreach, glob, package, regsub, {*}

How to zip multiple files through tcl script in Linux box?

I have set of code in tcl where I'm trying to achieve to zip the files but I'm getting below error
zip warning: name not matched: a_1.txt a_2.txt a_3.txt a_4.txt
On other hand I'm doing same thing from command prompt I'm able to execute successfully.
#!/usr/local/bin/tclsh
set outdir /usr/test/
set out_files abc.10X
array set g_config { ZIP /usr/bin/zip }
set files "a_1.txt a_2.txt a_3.txt a_4.txt"
foreach inp_file $files {
append zipfiles "$inp_file "
}
exec $g_config(ZIP) $outdir$out_files zipfiles
Tcl really cares about the boundaries between words, and doesn't split things up unless asked to. This is good as it means that things like filenames with spaces in don't confuse it, but in this case it causes you some problems.
To ask it to split the list up, precede the read of the word from the variable with {*}:
exec $g_config(ZIP) $outdir$out_files {*}$files
This is instead of this:
exec $g_config(ZIP) $outdir$out_files $files
# Won't work; uses "strange" filename
or this:
exec $g_config(ZIP) $outdir$out_files zipfiles
# Won't work; uses filename that is the literal "zipfiles"
# You have to use $ when you want to read from a variable and pass the value to a command.
Got a very old version of Tcl where {*} doesn't work? Upgrade to 8.5 or 8.6! Or at least use this:
eval {exec $g_config(ZIP) $outdir$out_files} $files
(You need the braces there in case you put a space in outdir…)

TCL : How to use variable inside grep in exec

How can i use variable inside grep in exec in TCL
"^STRING__${abc}__STRING__${abc}\[\[:space:\]\]*="
Tried Ways,
exec grep "^STRING__${abc}__STRING__${abc}\[\[:space:\]\]*="
exec grep {^STRING__${abc}__STRING__${abc}\[\[:space:\]\]*=}
exec grep {{^STRING__${abc}__STRING__${abc}\[\[:space:\]\]*=}}
Tried this above solutions, but not able to execute properly.
Thanks
I take it that your grep requires the brackets to be escaped? The first Way above will strip away the backslashes, and the second and third won't substitute the variable.
The easiest way to do this is probably to use format:
set regex [format {^STRING__%1$s__STRING__%1$s\[\[:space:\]\]*=} $abc]
exec grep $regex
The principle of it is to write the regex string as you want it to be inside the braces and replace the variable occurrences with %s specifiers, or %1$s to put the same string in more than one place, add the string to be inserted and call format on it.
If you don't need the backslashes after all, it's safe to remove them from the format string (as long as the braces are in place around it).
Documentation: format, set