I want to capture the stdout of a command in a variable, similarly to command substitution in Bash:
#!/bin/bash
x="$(date)"
echo $x
I tried doing the same in tclsh but it doesn't do what I want:
#!/bin/tclsh
set x [date]
echo $x
If I execute the script withtclsh myscript.tclsh it gives an error:
invalid command name "date"
while executing
"date "
invoked from within
"set x [ date ]"
On the other hand, if I open a TCL interactive shell with tclsh, it does not give an error and the echo line prints an empty string.
Why is my program giving different results when I execute the script with or without the REPL? And it there a way to capture the output of a shell command and store it in a variable, similarly to command substitution in Bash?
When not using Tcl interactively, you need to explicitly use the exec command to run a subprocess.
set x [exec date]
# Tcl uses puts instead of echo
puts $x
In interactive use, the unknown command handler guesses that that's what you wanted. In some cases. Be explicit in your scripts, please!
You should probably replace running a date subprocess with the appropriate calls to the built-in clock command:
# Get the timestamp in seconds-from-the-epoch
set now [clock seconds]
# Convert it to human-readable form
set x [clock format $now -format "%a %d %b %Y %H:%M:%S %Z"]
(That almost exactly matches the output of date on this system. The spacing isn't quite the same, but that doesn't matter for a lot of uses.)
Related
Want to use some custom function (written in tcl) in Unix pipeline ie grep patt file.rpt | tclsh summary.tcl. How to make tcl script to take output from the pipeline, process and out on the commandline as if a normal unix command?
This is very easy! The script should read input from stdin (probably with gets) and write output to stdout (with puts; this is the default destination).
Here's a very simple by-line filter script:
set lineCount 0
while {[gets stdin line] >= 0} {
incr lineCount
puts stdout "$lineCount >> $line <<"
}
puts "Processed $lineCount lines in total"
You probably want to do something more sophisticated!
The gets command has two ways of working. The most useful one here is the one where it takes two arguments (channel name, variable name), writes the line it has read from the channel into the variable, and returns the number of characters read or -1 when it has an EOF (or would block in non-blocking mode, or has certain kinds of problems; you can ignore these cases). That works very well with the style of working described above in the sample script. (You can distinguish the non-success cases with eof stdin and fblocked stdin, but don't need to for this use case.)
i am invoking script(tclscript) from the current script seeing this "invalid command name error" the tcl script just checks the proper version of package is installed or not.
#!/bin/tclsh
# i am doing this for multiple packages in a loop
set list {/usr/local/script}
lappend list -check
lappend list -package
lappend list tcl-devel
lappend list version
[eval exec $list]
output:
invalid command name "
checking the version [ ok ] #expected output
-checks successful! #expected output
"
while executing
"[eval exec $list]"
dont understand why i get this "invalid command name error"can anyone help
The problem is that you've successfully run the command, have got the results back, and are then trying to use those results as the name of a command because you put [brackets] around the eval exec. Either remove the brackets, or put a command name before them so that you use the result as an argument.
set list …
# Leaving out the details of how you build the list
eval exec $list
set list …
# Leaving out the details of how you build the list
set result [eval exec $list]
puts "result is \"$result\""
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, {*}
I am running one tcl script who is taking file as a input by "stdin".The problem is that its taking the file content as a filename and throwing error while running the script on command line processor.
tcl script is
#!/bin/sh
# SystemInfo_2.tcl \
exec tclsh "$0" ${1+"$#"}
set traps [read stdin];
#set traps "snmp trap test"
set timetrap [clock format [clock seconds]];
set trapout [open Database_traps_event.txt a+];
set javaout [open JavaTrapOutput.txt a+];
puts $trapout $timetrap;
puts $trapout $traps;
puts $trapout "Before executing java program";
set javaprogargs "open {|java -cp mysql-connector-java-5.1.10.jar;. EventAlarmHandling \"$traps\"} r";
puts $trapout $javaprogargs;
set javaprogram [eval $javaprogargs];
puts $trapout "Execution of java is over"
while { [gets $javaprogram line] != -1 } {
puts $javaout $line;
}
close $javaprogram;
puts $trapout "After excution of java program\r\n\r\n\r\n\r\n\r\n";
close $trapout;
close $javaout;
exit;
input file content is -
<UNKNOWN>
UDP: [192.168.1.19]:60572->[0.0.0.0]:0
.iso.org.dod.internet.mgmt.mib-2.system.sysUpTime.sysUpTimeInstance 1:9:58:56.61
.iso.org.dod.internet.snmpV2.snmpModules.snmpMIB.snmpMIBObjects.snmpTrap.snmpTrapOID.0 .iso.org.dod.internet.snmpV2.snmpModules.snmpMIB.snmpMIBObjects.snmpTraps.linkDown
.iso.org.dod.internet.mgmt.mib-2.interfaces.ifTable.ifEntry.ifIndex.1 8
.iso.org.dod.internet.mgmt.mib-2.interfaces.ifTable.ifEntry.ifAdminStatus.8 up
.iso.org.dod.internet.mgmt.mib-2.interfaces.ifTable.ifEntry.ifOperStatus.8 down
From command line it ran like below
E:\eventAlarmHandling>tclsh TclTempFile.tcl < traps.txt
couldn't read file "UNKNOWN>
UDP: [192.168.1.19]:60572->[0.0.0.0]:0
.iso.org.dod.internet.mgmt.mib-2.system.sysUpTime.sysUpTimeInstance 1:9:58:56.61
.iso.org.dod.internet.snmpV2.snmpModules.snmpMIB.snmpMIBObjects.snmpTrap.snmpTrapOID.0 .iso.org.dod.intern
et.snmpV2.snmpModules.snmpMIB.snmpMIBObjects.snmpTraps.linkDown
.iso.org.dod.internet.mgmt.mib-2.interfaces.ifTable.ifEntry.ifIndex.1 8
.iso.org.dod.internet.mgmt.mib-2.interfaces.ifTable.ifEntry.ifAdminStatus.8 up
.iso.org.dod.internet.mgmt.mib-2.interfaces.ifTable.ifEntry.ifOperStatus.8 down": No error
while executing
"open {|java -cp mysql-connector-java-5.1.10.jar;. EventAlarmHandling "<UNKNOWN>
UDP: [192.168.1.19]:60572->[0.0.0.0]:0
.iso.org.dod.internet.mgmt.mib..."
("eval" body line 1)
invoked from within
"eval $javaprogargs"
invoked from within
"set javaprogram [eval $javaprogargs]"
(file "TclTempFile.tcl" line 26)
So clearly in command line its showing that "couldn't read file UNKNOWN> ......"
So please explain it that whats happening here in command line.I am new to tcl.So hoping that someone help me out.
Thanks
You're having problems with one of the trickier bits of how pipelines work in Tcl. If we look at the documentation carefully, we see:
If the first character of fileName is “|” then the remaining characters of fileName are treated as a list of arguments that describe a command pipeline to invoke, in the same style as the arguments for exec.
That means you have to have the first character be | and the rest, after stripping that first character, be a proper list. In your case, you've not got that. Instead, you're doing:
set javaprogargs "open {|java -cp mysql-connector-java-5.1.10.jar;. EventAlarmHandling \"$traps\"} r";
That's pretty complicated anyway. Let's build this in the idiomatic fashion instead:
set CPsep ";"
set classpath [list mysql-connector-java-5.1.10.jar .]
set javaprogargs [list open |[list \
java -cp [join $classpath $CPsep] EventAlarmHandling $traps]]
It helps to split the classpath out; it's got a ; character in it (on Windows; you'll need to change that if you port to Linux or OSX) and it's nicer to use list in Tcl to build things and then join to convert into what Java expects.
We also no longer need any backslash-quoted substrings in there (except the one I put in to keep lines short and readable); the pattern of list commands there will add everything that is required. Note the |[list …] there: that's non-idiomatic everywhere in Tcl except when creating a pipeline when it is recommended practice as it is doing in reverse what open expects to parse.
The other thing you're running into is this:
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).
[…]
< fileName
The file named by fileName is opened and used as the standard input for the first command in the pipeline.
Your argument from $traps starts with a < and so it triggers this rule.
Unfortunately, there's no simple workaround for this and this is a severe, known, and very annoying limitation of the pipeline creation code. The only known techniques for dealing with this are to move to transferring that data by either a file or via the subprocess's standard input, both of which require modifying the subprocess's implementation. If you can make that Java program read from System.in (a good idea anyway, so you don't hit Windows's command line length limitations!) then you can pass the value like this:
set CPsep ";"
set classpath [list mysql-connector-java-5.1.10.jar .]
set javaprogargs [list open |[list \
java -cp [join $classpath $CPsep] EventAlarmHandling << $traps]]
That is just by adding a << in there immediately before the value.
What I want to do:
I want to pass the current date and time to a VHDL testbench so I can create nicer report file names.
The Problem:
The Top level VHDL file that is being called from my simulation TCL scripts is a configuration and I don't know how to pass the genericto it. The interesing part of the TCL script is the following:
set reportfilename "report_$testcase_id"
append reportfilename [clock format [clock seconds] -format _%Y-%m-%d_%H:%M]
vsim -t 10fs -gG_TC_REPORTFILE=$reportfilename -novopt work.CFG_TB_TOP_tc0027 - wlf result_$testcase_id.wlf
The resulting VSIM call looks like this:
# vsim -t 10fs -wlf result_tc0027.wlf -novopt -gG_TC_REPORTFILE=report_tc0027_2014-03-05_13:22 work.CFG_TB_TOP_tc0027
The problem is that G_TC_REPORTFILE is not a generic of CFG_TB_TOP_tc0027.vhd, but of one of the modules configured IN CFG_TB_TOP_tc0027.vhd
The relevant part of CFG_TB_TOP_tc0027.vhd:
configuration CFG_TB_TOP_tc0027 of TB_TOP_CFG is
for testcaseexecution
for TB_TOP_E_INST : TB_TOP_E
-------------------------------------------------------------
-- Testbench configuration
-------------------------------------------------------------
use entity work.TB_TOP_E(TB_TOP_sim)
generic map (
G_TC_STIMULUSFILE => "./testcases/tc0027.txt", --
G_TC_REPORTFILE => "./results/report_tc0027.txt",
G_TB_VNR => "V_03.10",
G_TC_NR => 27); --
for TB_TOP_A_sim
How would I pass the value from the TCL file to the generic G_TC_REPORTFILE of the TB_TOP_E entity? Can I somehow add a generic to the CFG file, or can I somehow specify for which entity the generic in the TCL file is meant? Best-case-scenario would be if I would not have to edit the vhdl files, only the TCL scripts.
The simulator is ModelSim SE 10.0b, I'm using VHDL 2008.
I have just found the answer myself:
You can indeed specify a path to the generic you want to overwrite. I changed the TCL file like this:
set reportfilename "./results/report_$testcase_id"
append reportfilename [clock format [clock seconds] -format _%Y-%m-%d_%H-%M.txt]
vsim -t 10fs -GTB_TOP_CFG/TB_TOP_E_INST/G_TC_REPORTFILE=$reportfilename -novopt work.CFG_TB_TOP_tc0027 -wlf result_$testcase_id.wlf
Which produces the following VSIM call:
# vsim -t 10fs -wlf result_tc0027.wlf -novopt -GTB_TOP_CFG/TB_TOP_E_INST/G_TC_REPORTFILE=./results/report_tc0027_2014-03-05_14-06.txt work.CFG_TB_TOP_tc0027
In addition to adding the path TB_TOP_CFG/TP_TOP_E_INST/ I also had to remove the : from the timestamp, as modelsim/vhdl did not handle it.