SQL Scripts - Does the equivalent of a #define exist? - mysql

I have a script that I use to construct both the tables and stored procedures. For example I have a column of type varchar. varchar requires a size parameter, that size I also use as parameters in stored procedures and within those procedures.
is it possible to have thequivalentnt of a #define for its size, so I can easily adjust the size without the necessity of having to change ithroughht the whole of the script?
I am using MySql workbench.
EDIT
I have tried SET and DECLARE
I have a script - this is (abridged)
CREATE TABLE `locations`
(
`location` VARCHAR(25) NOT NULL
);
...
CREATE PROCEDURE AddLocation (IN location VARCHAR(25)
BEGIN
...
END$$
What I am trying to achieve is replace the values 25 in the script with a constant - similar to a #define at the top of the script that creates the table and stored procedures, so I am able to easily change the 25 to another number.
Anybody has found a solution to this problem?

The C Pre Processor (cpp) is historically associated with C (hence the name), but it really is a generic text processor that can be used (or abused) for something else.
Consider this file, named location.src (more on that later).
// C++ style comments works here
/* C style works also */
-- plain old SQL comments also work,
-- but you should avoid using '#' style of comments,
-- this will confuse the C pre-processor ...
#define LOCATION_LEN 25
/* Debug helper macro */
#include "debug.src"
DROP TABLE IF EXISTS test.locations;
CREATE TABLE test.locations
(
`location` VARCHAR(LOCATION_LEN) NOT NULL
);
DROP PROCEDURE IF EXISTS test.AddLocation;
delimiter $$
CREATE PROCEDURE test.AddLocation (IN location VARCHAR(LOCATION_LEN))
BEGIN
-- example of macro
ASSERT(length(location) > 0, "lost or something ?");
-- do something
select "Hi there.";
END
$$
delimiter ;
and file debug.src, which is included:
#ifdef HAVE_DEBUG
#define ASSERT(C, T) \
begin \
if (not (C)) then \
begin \
declare my_msg varchar(1000); \
set my_msg = concat("Assert failed, file:", __FILE__, \
", line: ", __LINE__, \
", condition ", #C, \
", text: ", T); \
signal sqlstate "HY000" set message_text = my_msg; \
end; \
end if; \
end
#else
#define ASSERT(C, T) begin end
#endif
When compiled with:
cpp -E location.src -o location.sql
you get the code you are looking for, with cpp expanding #define values.
When compiled with:
cpp -E -DHAVE_DEBUG location.src -o location.sql
you get the same, plus the ASSERT macro (posted as a bonus, to show what could be done).
Assuming a build with HAVE_DEBUG deployed in a testing environment (in 5.5 or later since SIGNAL is used), the result looks like this:
mysql> call AddLocation("Here");
+-----------+
| Hi there. |
+-----------+
| Hi there. |
+-----------+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
mysql> call AddLocation("");
ERROR 1644 (HY000): Assert failed, file:location.src, line: 24, condition length(location) > 0, text: lost or something ?
Note how the file name, line number, and condition points right at the place in the source code in location.src where the assert is raised, thanks again to the C pre processor.
Now, about the ".src" file extension:
you can use anything.
Having a different file extension helps with makefiles, etc, and prevents confusion.
EDIT: Originally posted as .xql, renamed to .src for clarity. Nothing related to xml queries here.
As with any tools, using cpp can lead to good things, and the use case for maintaining LOCATION_LEN in a portable way looks very reasonable.
It can also lead to bad things, with too many #include, nested #ifdef hell, macros, etc that at the end obfuscate the code, so your mileage may vary.
With this answer, you get the whole thing (#define, #include, #ifdef, __FILE__, __LINE__, #C, command line options to build), so I hope it should cover it all.

Have you tried SET?
here is an example :
SET #var_name = expr
more examples here :
http://dev.mysql.com/doc/refman/5.0/en/user-variables.html

It sounds like you're looking for user defined data types. Unfortunately for us all mySQL doesn't yet support user defined data types like SQL Server, Oracle, and others do.
Here's a list of supported data types:
http://dev.mysql.com/doc/refman/5.0/en/data-types.html

For those that are interested:
I ended up writing a PHP script because:
a) The machine that can access the database does not belong to me and I cannot access the C
preprocessor
b) The other the two answers do not work.
c) Seemed the simplest solution
Here is the script for those who might find it useful. I am using it to define the tables column
widths and then use those same values in the stored procedures. This is due to the column
widths have not yet been fully decided for production.
I have also built in that you can define strings that last over a few lines. This has the advantage that I can obey the 80 column width (hence printing looks readable).
Here is the script
<?php
if (1==count($argv))
{
?>
Processing #defines from stdin and send to SQL server:
This script will remove
1. #define <name> <integer>
2. #define <name> '<string>'
3. #define <name> '<string>' \
'<continuation of string>'
and replace the occurances of name with the #define value as specified
<name> is upper case alpha numberics or underscores, not starting with a
digit.
The arguments of this script is passed to the mysql executable.
<?php
exit(1);
}
function replace(&$newValues, $a, $b, $c)
{
return $a . (array_key_exists($b, $newValues) ? $newValues[$b] : $b) . $c;
}
// The patterns to be used
$numberPattern='/^#define[ \t]+([A-Z_][A-Z0-9_]*)[ \t]+(0|([1-9][0-9]*))'.
'[ \t]*[\r\n]+$/';
$stringPattern= '/^#define[ \t]+([A-Z_][A-Z0-9_]*)[ \t]+\''.
'((\\\'|[^\'\r\n])*)\'[ \t]*(\\\\{0,1})[\n\r]+$/';
$continuationPattern='/^[ \t]*\'((\\\'|[^\'\r\n])*)\'[ \t]*'.
'(\\\\{0,1})[\n\r]+$/';
// String to be evaluated to replace define values with a new value
$evalStr='replace($newValues, \'\1\', \'\2\', \'\3\');';
array_splice($argv, 0, 1);
// Open up the process
$mysql=popen("mysql ".implode(' ', $argv), 'w');
$newValues=array(); // Stores the defines new values
// Variables to control the replacement process
$define=false;
$continuation=false;
$name='';
$value='';
while ($line=fgets(STDIN))
{
$matches=array();
// #define numbers
if (!$define &&
1 == preg_match($numberPattern, $line, $matches))
{
$define = true;
$continuation = false;
$name = $matches[1];
$value = $matches[2];
}
// #define strings
if (!$define &&
1 == preg_match($stringPattern,
$line, $matches))
{
$define = true;
$continuation = ('\\' == $matches[4]);
$name = $matches[1];
$value = $matches[2];
}
// For #define strings that continue over more than one line
if ($continuation &&
1 == preg_match($continuationPattern,
$line, $matches))
{
$value .= $matches[1];
$continuation = ('\\' == $matches[3]);
}
// Have a complete #define, add to the array
if ($define && !$continuation)
{
$define = $continuation = false;
$newValues[$name]=$value;
}
elseif (!$define)
{
// Do any replacements
$line = preg_replace('/(^| |\()([A-Z_][A-Z0-9_]*)(\)| |$)/e',
$evalStr, $line);
echo $line; // In case we need to have pure SQL.
// Send it to be processed.
fwrite($mysql, $line) or die("MySql has failed!");
}
}
pclose($mysql);
?>

Related

How does one insert fortran code from an external file into a separate code?

I'd like to have my code take code written in another document, read it, and then use it as though it was written in the code. Say we have the following:
MODULE samplemod
CONTAINS
FUNCTION sillysum(boudary,function) RESULT(counter)
IMPLICIT NONE
REAL(KIND=8) :: boundary, counter
REAL(KIND=8), DIMENSION(:) :: function
INTEGER :: m
counter = 0.d0
DO m = 1, my_mesh%me
counter = function(m) + externalfunction
END DO
END FUNCTION sillysum
END MODULE samplemod
PROGRAM sampleprogram
USE samplemod
REAL(KIND=8), DIMENSION(:) :: function1
ALLOCATE(function1(100))
DO m=1, 100
function1(i) = i
END DO
WRITE(*,*) sillysum(100,function1)
END PROGRAM sampleprogram
Where in some external file (say 'externfunct.txt') one has written m**2. How can the Fortran code read the external function m**2, SIN(m), or even 0 and have that replace externalfunction. Here's a simpler example:
REAL(KIND=8) :: x = 2
CHARACTER(LEN=*) :: strng = "external"
WRITE(*,*) "Hello world, 2 + ", strng, " = ", 2 + external
Where in a txt file I have written I have written SIN(x).
I think there are two different approaches for this (* in fact, there seems a "third" approach also, see EDIT); one is to use a shared library, and the other is to use a parser for math expressions. The first approach is described in a Rossetastone page (Call a function in a shared library) and an SO page (Fortran dynamic libraries, load at runtime?), for example. For the second approach, you can find 3rd-party libraries by searching with "math parser" or "Fortran math parser" etc. Here, I have tried this one because it seems pretty simple (only one module and no installation). If we write a simple test program like this
program test
use interpreter, only: init, evaluate, dp => realkind
implicit none
integer, parameter :: mxvars = 10 !! consider 10 variables at most here
character(10) :: symbols( mxvars )
real(dp) :: values( mxvars ), answer
character(1000) :: funcstr !! a user-defined math expression
character(5) :: stat
!> Define variable names.
symbols( 1 ) = "x"
symbols( 2 ) = "a"
symbols( 3 ) = "b"
symbols( 4 ) = "c"
symbols( 5 ) = "foo"
!> Get a math expression.
print *, "Please input a math expression:"
read(*, "(a)") funcstr !! e.g., a * x + b
!> Init the evaluator.
call init( funcstr, symbols, stat )
if ( stat /= "ok" ) stop "stat /= ok"
!> Set values for the variables.
values( : ) = 0
values( 1 ) = 2.0_dp ! x
values( 2 ) = 10.0_dp ! a
values( 3 ) = 7.0_dp ! b
!> Evaluate.
answer = evaluate( values )
print *, "function value = ", answer
end program
and compile it as (*1)
$ gfortran -ffree-line-length-none interpreter.f90 mytest.f90
we can test various expressions as follows:
$ ./a.out
Please input a math expression:
a * x + b
function value = 27.000000000000000
$ ./a.out
Please input a math expression:
sin( a * x ) + cos( b ) + foo
function value = 1.6668475050709324
The usage of other libraries also seems very similar. Because the performance of each library may be rather different, it may be useful to try several different libraries for comparison.
(*1) The module has some lines with sind, cosd, and tand, but they are not supported by gfortran. So, to compile, I have commented them out and replaced them by stop, i.e.,
stop "sind not supported..."
! pdata(st) = sind(pdata(st))
(I guess sind(x) means sin( x * pi / 180 ), so it may be OK to define it as such.)
[EDIT]
A "third" approach may be to call the built-in eval() function in interpreted languages like Python or Julia via system(), e.g., this SO page. Although this also has a lot of weak points (and it is probably much easier to use such languages directly), calling eval() from Fortran might be useful for some specific purposes.

Compress JSON file by eliminating whitespace

I am working with a large json file (~100,000 lines) and need to compress it down to make a program run faster. I wish to delete all the horizontal tabs, returns, etc. to minimize the size of the file.
For example if a line was originally:
"name_id": "Richard Feynman",
"occupation": "Professional Bongos Player"
it should be compressed to:
"name_id":"Richard Feynman","occupation":"Professional Bongos Player"`
I have scoured the Internet (forgive me if it is a simple answer, I am a beginner) and can't seem to find a command for the terminal that will help me do this. Any help would be much appreciated
Looks like you're looking for a JSON minifier.
There are some around, both online and standalone.
Try googling these terms + your favorite language, I'm sure you'll find something that suits your needs.
There are other tools that modify your JSON to make it smaller, but you'll end up with a different JSON, I guess. Haven't tried those.
Using GNU awk for RT:
$ awk 'BEGIN{RS="\""} NR%2{gsub(/[[:space:]]/,"")} {ORS=RT;print} END{printf "\n"}' file
"name_id":"Richard Feynman","occupation":"Professional Bongos Player"
The following flex(1) program will do the work. It makes a lexical analisys of json source and eliminates comments and spaces between tokens, respecting the in-string spaces. It also recognizes unquoted identifiers, and quotes them.
To compile it, just do
make json
Use it with the following command:
json [ file ... ]
if you don't specify a file, the program will read from stdin.
Here's the source:
%{
/* json-min. JSON minimizer.
* Author: Luis Colorado <lc#luiscoloradosistemas.com>
* Date: Wed Aug 13 07:35:23 EEST 2014
* Disclaimer: This program is GPL, as of GPL version 3, you
* may have received a copy of that document, or you can
* instead look at http://www.gnu.org/licenses/gpl.txt to read
* it. There's no warranty, nor assumed nor implicit on the
* use of this program, you receive it `as is' so whatever you
* do with it is only your responsibility. Luis Colorado
* won't assume any responsibility of the use or misuse of
* this program. You are warned.
*/
%}
dec ([1-9][0-9]*)
oct (0[0-7]*)
hex (0[xX][0-9a-fA-F]*)
doub ({dec}"."([0-9]*)?|{dec}?"."[0-9]+)
strd (\"([^\"]|\\.)*\")
t "true"
f "false"
n "null"
com1 "//".*
com2b "/*"
endc "*/"
ident ([a-zA-Z_][a-zA-Z0-9_]*)
%x INCOMMENT
%option noyywrap
%%
{dec} |
{oct} |
{hex} |
{doub} |
{strd} |
{t} |
{f} |
{n} |
"{" |
":" |
";" |
"}" |
"[" |
"]" |
"," ECHO;
[\ \t\n] |
{com1} ;
{com2b} BEGIN(INCOMMENT);
<INCOMMENT>. ;
<INCOMMENT>{endc} BEGIN(INITIAL);
{ident} { fprintf(stderr, "WARNING:"
"unquoted identifier %s "
"in source. Quoting.\n",
yytext);
printf("\"%s\"", yytext);
}
. { fprintf(stderr,
"WARNING: unknown symbol %s "
"in source, copied to output\n",
yytext);
ECHO;
}
%%
void process(const char *fn);
int main(int argc, const char **argv)
{
int i;
if (argc > 1) for (i = 1; i < argc; i++)
process(argv[i]);
else process(NULL); /* <-- stdin */
} /* main */
void process(const char *fn)
{
FILE *f = stdin;
if (fn) {
f = fopen(fn, "r");
if (!f) {
fprintf(stderr,
"ERROR:fopen:%s:%s(errno=%d)\n",
fn, strerror(errno), errno);
exit(EXIT_FAILURE);
} /* if */
} /* if */
yyin = f;
yylex();
if (fn) /* only close if we opened, don't close stdin. */
fclose(f);
printf("\n");
}
I have just written it, so there's little testing on it. Use it with care (conserve a backup of your original file) It doesn't overwrite the original file, just outputs to stdout, so you don't overwrite your data using it.
BR,
Luis

number(X) as function parameter or return value generates PLS 00103 error

I have the following function definition:
create or replace FUNCTION checkXML
(idx in number(19))
return number(19)
is
...
But when I compile it, am getting the following errors,
Error(2,16): PLS-00103: Encountered the symbol "(" when expecting one of the
following: := . ) , # % default character The symbol ":=" was substituted for
"(" to continue.
Error(3,15): PLS-00103: Encountered the symbol "(" when expecting one of the
following: . # % ; is authid as cluster order using external character
deterministic parallel_enable pipelined aggregate result_cache
Change the function declaraction to be
create or replace FUNCTION checkXML
(idx in number)
return number
PL/SQL doesn't accept length or precision specifiers on parameters or return types.
Share and enjoy.

Tcl Expect "interact" command echos previous output from spawned rlwrap process

I have a feeling there is something obvious I'm missing, but my searches are coming up fruitless thus far.
I am trying to use a tcl/expect script to start up a tclsh interactive shell, add a procedure for easily reloading utilities for testing, and then return normal control to me.
So far, the one way I've discovered to make a tcl interactive shell "usable" is to start it with "rlwrap" so that I can use arrow keys, etc.
So I tried the following script and something about rlwrap is causing previous output to be dumped to stdout when the interact command is hit.
Is there something I can do to make this not happen?
Code:
package require Expect
puts "Tcl version : [info tclversion]"
puts "Expect version: [exp_version]"
log_user 0
spawn -noecho rlwrap tclsh
# Create procedure to easily reload utilites after changes have been made
expect "% "
send {
proc reload {} {
# Procedure to reload utility source easily for testing
}
}
# Source utilities
expect "% "
send "reload\r"
send_user "\nUse 'reload' procedure to re-source utility files\n\n"
log_user 1
interact
Output:
Tcl version : 8.4
Expect version: 5.43.0
Use 'reload' procedure to re-source utility files
proc reload {} {
# Procedure to reload utility source easily for testing
}
% reload
%
You can that for some reason it's echoing the proc definition and the entering of the reload command. This occurs as soon as interact occurs. If I replace interact with "exit" I do not see any of this output.
Of course the output I'm hope to see would be this:
Tcl version : 8.4
Expect version: 5.43.0
Use 'reload' procedure to re-source utility files
%
If you don't mind to compile a small C program yourself, you could use this:
#include <tcl.h>
#ifdef WIN32
#ifdef UNICODE
#define WIN32_UNICODE
#endif
#endif
int TclSHI_Main(Tcl_Interp*);
static int g_argc;
#ifdef WIN32_UNICODE
#define Tcl_NewStringObj Tcl_NewUnicodeObj
static wchar_t*** g_argv;
void wmain(int argc, wchar_t **argv) {
#else
static char*** g_argv;
void main(int argc, char **argv) {
#endif
g_argc = argc;
g_argv = &argv;
Tcl_FindExecutable(argv[0]);
Tcl_Main(1, argv, TclSHI_Main);
}
int TclSHI_Main(Tcl_Interp* interp) {
Tcl_Obj* lobj;
int i;
if (g_argc > 1) {
Tcl_SetVar2Ex(interp, "argv0", NULL, Tcl_NewStringObj((*g_argv)[1], -1), TCL_GLOBAL_ONLY);
}
lobj = Tcl_NewObj();
Tcl_IncrRefCount(lobj);
for (i = 2; i < g_argc; i++) {
Tcl_ListObjAppendElement(interp, lobj, Tcl_NewStringObj((*g_argv)[i], -1));
}
Tcl_SetVar2Ex(interp, "argv", NULL, lobj, TCL_GLOBAL_ONLY);
Tcl_DecrRefCount(lobj);
Tcl_SetVar2Ex(interp, "argc", NULL, Tcl_NewIntObj(g_argc - 2), TCL_GLOBAL_ONLY);
if (g_argc > 1) {
Tcl_Eval(interp, "source $argv0");
}
return TCL_OK;
}
I tested it on windows (CL) and linux (GCC).
To compile it with gcc I used gcc TclSH.c -o TclSHI -ltcl8.6
On windows I used Visual Studio.
It tells Tcl that it did not receive any arguments (Tcl_Main(1,...)), but populates the new interp with this arguments and sources the file. After this step it will always show the prompt (it never received any arguments, right?).
There is a small problem with your expect solution, if you specify any arguments, Tcl would execute that script, and never show the prompt.
Also note that I'm a novice C programmer, so this solution might not be bullet proof.
What you want to do is to wait for an unambiguous marker that indicates that the subordinate process is ready.
# ... your script as above ...
expect "% "
#### NEW STUFF STARTS ####
send "reload;puts READY\r"
expect "READY\r"
# Note that we need to fake the prompt; c'est la vie
send_user "\nUse 'reload' procedure to re-source utility files\n\n% "
# Now start doing things!
log_user 1
interact
Or at least that works when I try with a subordinate process, but I wasn't using rlwrap in the mix so that might change thingsā€¦

Tcl C API: redirect stdout of embedded Tcl interp to a file without affecting the whole program

#include <tcl.h>
int main(int argc, char** argv)
{
Tcl_Interp *interp = Tcl_CreateInterp();
Tcl_Channel stdoutChannel = Tcl_GetChannel(interp, "stdout", NULL);
Tcl_UnregisterChannel(interp, stdoutChannel);
Tcl_Channel myChannel = Tcl_OpenFileChannel(interp, "/home/aminasya/nlb_rundir/imfile", "w", 0744);
Tcl_RegisterChannel(interp, myChannel);
Tcl_Eval(interp, "puts hello");
}
In this code I have tried to close stdout channel and redirect it to file. (As described Get the output from Tcl C Procedures). After running, "imfile" is created but empty. What am doing wrong?
I have seen How can I redirect stdout into a file in tcl too, but I need to do it using Tcl C API.
I have also tried this way, but again no result.
FILE *myfile = fopen("myfile", "W+");
Tcl_Interp *interp = Tcl_CreateInterp();
Tcl_Channel myChannel = Tcl_MakeFileChannel(myfile, TCL_WRITABLE);
Tcl_SetStdChannel(myChannel, TCL_STDOUT);
The difficulty in your case is the interaction between the standard channels of a Tcl interpreter and the file descriptors (FDs) of the standard streams as seen by the main program (and the C runtime), coupled with the semantics of open(2) in Unix.
The process which makes all your output redirected rolls like this:
The OS makes sure the three standard file descriptors (FDs) are open (and numbered 0, 1 and 2, with 1 being the standard output) by the time the program starts executing.
As soon as the Tcl interpreter you create initializes its three standard channels (this happens when you call Tcl_GetChannel() for "stdout", as described here), they get associated with those already existing three FDs in the main program.
Note that the underlying FDs are not cloned, instead, they are just "borrowed" from the enclosing program. In fact, I think in 99% of cases this is a sensible thing to do.
When you close (which happend when unregisteting) the standard channel stdout in your Tcl interpreter, the underlying FD (1) is closed as well.
The call to fopen(3) internally calls open(2) which picks up the lowest free FD, which is 1, and thus the standard output stream as understood by the main program (and the C runtime) is now connected to that opened file.
You then create a Tcl channel out of your file and register it with the interpreter. The channel indeed becomes stdout for the interpreter.
In the end, both writes to the standard output stream in your main program and writes to the standard output channel in your Tcl interpreter are sent do the same underlying FD and hence end up in the same file.
I can see two ways to deal with this behaviour:
Play a neat trick to "reconnect" the FD 1 to the same stream it was initially opened to and make the file opened for the Tcl interpreter's stdout use an FD greater than 2.
Instead of first letting the Tcl interpreter initialize its standard channels and then reinitializing one of them, initialize them all manually before letting that auto-vivification machinery kick in.
Both approaches have their pros and cons:
"Preserving FD 1" is generally simpler to implement, and if you want to redirect only stdout in your Tcl interpreter, and leave the two other standard channels to be connected to the same standard streams used by the enclosing program, this approach seems to be sensible to employ. The possible downsides are:
Too much magic involved (extensive commenting the code is advised).
Not sure how this would work on Windows: there's no dup(2) there (see below) and some other approach might be needed.
Not using the standard streams for stdin and stderr from the enclosing program might be useful.
Initializing the standard channels in the Tcl interpreter by hand requires more code and supposedly warrants the correct ordering (stdin, stdout, stderr, in that order). If you want the remaining two standard channels in your Tcl interpreter to be connected to the matching streams of the enclosing program, this approach is more work; the first approach does this for free.
Here's how to preserve FD 1 to make only stdout in the Tcl interpreter be connected to a file; for the enclosing program FD 1 is still connected to the same stream as set up by the OS.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <tcl.h>
int redirect(Tcl_Interp *interp)
{
Tcl_Channel chan;
int rc;
int fd;
/* Get the channel bound to stdout.
* Initialize the standard channels as a byproduct
* if this wasn't already done. */
chan = Tcl_GetChannel(interp, "stdout", NULL);
if (chan == NULL) {
return TCL_ERROR;
}
/* Duplicate the descriptor used for stdout. */
fd = dup(1);
if (fd == -1) {
perror("Failed to duplicate stdout");
return TCL_ERROR;
}
/* Close stdout channel.
* As a byproduct, this closes the FD 1, we've just cloned. */
rc = Tcl_UnregisterChannel(interp, chan);
if (rc != TCL_OK)
return rc;
/* Duplicate our saved stdout descriptor back.
* dup() semantics are such that if it doesn't fail,
* we get FD 1 back. */
rc = dup(fd);
if (rc == -1) {
perror("Failed to reopen stdout");
return TCL_ERROR;
}
/* Get rid of the cloned FD. */
rc = close(fd);
if (rc == -1) {
perror("Failed to close the cloned FD");
return TCL_ERROR;
}
/* Open a file for writing and create a channel
* out of it. As FD 1 is occupied, this FD won't become
* stdout for the C code. */
chan = Tcl_OpenFileChannel(interp, "aaa.txt", "w", 0666);
if (chan == NULL)
return TCL_ERROR;
/* Since stdout channel does not exist in the interp,
* this call will make our file channel the new stdout. */
Tcl_RegisterChannel(interp, chan);
return TCL_OK;
}
int main(void)
{
Tcl_Interp *interp;
int rc;
interp = Tcl_CreateInterp();
rc = redirect(interp);
if (rc != TCL_OK) {
fputs("Failed to redirect stdout", stderr);
return 1;
}
puts("before");
rc = Tcl_Eval(interp, "puts stdout test");
if (rc != TCL_OK) {
fputs("Failed to eval", stderr);
return 2;
}
puts("after");
Tcl_Finalize();
return 0;
}
Building and running (done in Debian Wheezy):
$ gcc -W -Wall -I/usr/include/tcl8.5 -L/usr/lib/tcl8.5 -ltcl main.c
$ ./a.out
before
after
$ cat aaa.txt
test
As you can see, the string "test" output by puts goes to the file while the strings "before" and "after", which are write(2)n to FD 1 in the enclosing program (this is what puts(3) does in the end) go to the terminal.
The hand-initialization approach would be something like this (sort of pseudocode):
Tcl_Channel stdin, stdout, stderr;
stdin = Tcl_OpenFileChannel(interp, "/dev/null", "r", 0666);
stdout = Tcl_OpenFileChannel(interp, "aaa.txt", "w", 0666);
stderr = Tcl_OpenFileChannel(interp, "/dev/null", "w", 0666);
Tcl_RegisterChannel(interp, stdin);
Tcl_RegisterChannel(interp, stdout);
Tcl_RegisterChannel(interp, stderr);
I have not tested this approach though.
At the level of the C API, and assuming that you are on a Unix-based OS (i.e., not Windows), you can do this far more simply by using the right OS calls:
#include <fcntl.h>
#include <unistd.h>
// ... now inside a function
int fd = open("/home/aminasya/nlb_rundir/imfile", O_WRONLY|O_CREAT, 0744);
// Important: deal with errors here!
dup2(fd, STDOUT_FILENO);
close(fd);
You could also use dup() to save the old stdout (to an arbitrary number that Tcl will just ignore) so that you can restore it later, if desired.
Try this:
FILE *myfile = fopen("myfile", "W+");
Tcl_Interp *interp = Tcl_CreateInterp();
Tcl_Channel myChannel = Tcl_MakeFileChannel(myfile, TCL_WRITABLE);
Tcl_RegisterChannel(myChannel);
Tcl_SetStdChannel(myChannel, TCL_STDOUT);
You need to register the channel with the interpreter before you can reset the std channel to use it.