exec quotes its arguments automatically which can lead to problems when unescaped quotes need to be inserted in a commandline. ''This sounds like a misinterpretation of what [exec] does. There is no quoting going on, because [exec] is directly constructing the [argv] array of the program being called!'' ---- If arguments contain spaces exec will quote the argument before passing it on (some testing shows that it is actually a bit more involved than this). So {c:\Documents and Settings} will be changed to "c:\Documents and Settings". However, sometimes you want to force this quoting of an argument. One example I have encountered is when passing parameters to a batch file that contain comma's: test.bat a,b will call test.bat with two parameters a and b. This can be very useful, but leads to problems when you want to send a,b as one parameter. The solution is to quote the a,b test.bat "a,b" If we want to call this batchfile from Tcl, the first idea would be to use exec test.bat {"a,b"} but here exec will quote this to: test.bat \"a,b\" Instead the << argument to exec can be used in combination with cmd.exe. << will pipe the remaining args to the stdin of the program. So exec cmd << "test.bat \"a,b\"\n" ; # note the trailing \n will lead to the desired effect. -- [MJ] [RS] Very cool - I had exactly that problem yesterday! To prevent forgetting the \n, one might wrap it like this: proc do cmd {exec cmd << $cmd\n} [MJ] I have noticed before that there seems to be some sort of problem synchronization on the Tcl chat. Yesterday (2006-08-26) was apparently exec day ;-) [MJ] The main issue with exec quoting seems to be that the automatic quoting exec applies (even though it is correct and convenient most times) sometimes gets in the way. It would therefore be nice if exec allowed a flag that disables all automatic quoting to allow the programmer to specify the exact quoting in case exec gets it wrong. This would allow a solution like: exec -noquoting {test.bat "a,b"} This will also make use of auto_execok without eval possible (although in 8.5 [{expand}] solves this in different way): exec -noquoting "[auto_execok start] http://www.tcl.tk" ; # 8.4.13 exec {expand}[auto_execok start] "http://www.tcl.tk" ; # 8.5 This new flag will only lead to problems with older scripts if a command named -noquoting exists which seems unlikely. [RS] Another point that ''exec -noquoting'' should handle is to take < and > literal, not as redirections, e.g. exec echo "" [MJ] Idealiter -noquoting would refrain from interpreting any special characters such as " < << 2> etc. So the tag case should be handled correctly eg: % exec echo "" couldn't read file "starttag>": no such file or directory % exec -noquoting echo "" % [MJ] Below a patch on 8.5a3 that adds a -noquote option to exec. Only quoting of " is disabled. Disabling handling of < | etc. is more complicated: diff -ur tcl8.5a4\generic\tcl.h tcl8.5a4-exec\generic\tcl.h --- tcl8.5a4\generic\tcl.h Mon Mar 27 13:08:50 2006 +++ tcl8.5a4-exec\generic\tcl.h Tue Aug 29 16:13:12 2006 @@ -1492,6 +1492,7 @@ #define TCL_STDOUT (1<<2) #define TCL_STDERR (1<<3) #define TCL_ENFORCE_MODE (1<<4) +#define TCL_NOQUOTE (1<<5) /* * Bits passed to Tcl_DriverClose2Proc to indicate which side of a channel diff -ur tcl8.5a4\generic\tclIntDecls.h tcl8.5a4-exec\generic\tclIntDecls.h --- tcl8.5a4\generic\tclIntDecls.h Wed Apr 26 11:42:54 2006 +++ tcl8.5a4-exec\generic\tclIntDecls.h Tue Aug 29 16:23:43 2006 @@ -104,7 +104,7 @@ EXTERN int TclCreatePipeline _ANSI_ARGS_((Tcl_Interp * interp, int argc, CONST char ** argv, Tcl_Pid ** pidArrayPtr, TclFile * inPipePtr, - TclFile * outPipePtr, TclFile * errFilePtr)); + TclFile * outPipePtr, TclFile * errFilePtr, int noQuote)); #endif #endif /* UNIX */ #ifdef __WIN32__ @@ -114,7 +114,7 @@ EXTERN int TclCreatePipeline _ANSI_ARGS_((Tcl_Interp * interp, int argc, CONST char ** argv, Tcl_Pid ** pidArrayPtr, TclFile * inPipePtr, - TclFile * outPipePtr, TclFile * errFilePtr)); + TclFile * outPipePtr, TclFile * errFilePtr, int noQuote)); #endif #endif /* __WIN32__ */ #ifndef TclCreateProc_TCL_DECLARED @@ -1068,10 +1068,10 @@ int (*tclCopyAndCollapse) _ANSI_ARGS_((int count, CONST char * src, char * dst)); /* 7 */ int (*tclCopyChannel) _ANSI_ARGS_((Tcl_Interp * interp, Tcl_Channel inChan, Tcl_Channel outChan, int toRead, Tcl_Obj * cmdPtr)); /* 8 */ #if !defined(__WIN32__) /* UNIX */ - int (*tclCreatePipeline) _ANSI_ARGS_((Tcl_Interp * interp, int argc, CONST char ** argv, Tcl_Pid ** pidArrayPtr, TclFile * inPipePtr, TclFile * outPipePtr, TclFile * errFilePtr)); /* 9 */ + int (*tclCreatePipeline) _ANSI_ARGS_((Tcl_Interp * interp, int argc, CONST char ** argv, Tcl_Pid ** pidArrayPtr, TclFile * inPipePtr, TclFile * outPipePtr, TclFile * errFilePtr, int noQuote)); /* 9 */ #endif /* UNIX */ #ifdef __WIN32__ - int (*tclCreatePipeline) _ANSI_ARGS_((Tcl_Interp * interp, int argc, CONST char ** argv, Tcl_Pid ** pidArrayPtr, TclFile * inPipePtr, TclFile * outPipePtr, TclFile * errFilePtr)); /* 9 */ + int (*tclCreatePipeline) _ANSI_ARGS_((Tcl_Interp * interp, int argc, CONST char ** argv, Tcl_Pid ** pidArrayPtr, TclFile * inPipePtr, TclFile * outPipePtr, TclFile * errFilePtr, int noQuote)); /* 9 */ #endif /* __WIN32__ */ int (*tclCreateProc) _ANSI_ARGS_((Tcl_Interp * interp, Namespace * nsPtr, CONST char * procName, Tcl_Obj * argsPtr, Tcl_Obj * bodyPtr, Proc ** procPtrPtr)); /* 10 */ void (*tclDeleteCompiledLocalVars) _ANSI_ARGS_((Interp * iPtr, CallFrame * framePtr)); /* 11 */ diff -ur tcl8.5a4\generic\tclIntPlatDecls.h tcl8.5a4-exec\generic\tclIntPlatDecls.h --- tcl8.5a4\generic\tclIntPlatDecls.h Wed Apr 26 11:42:56 2006 +++ tcl8.5a4-exec\generic\tclIntPlatDecls.h Tue Aug 29 16:34:29 2006 @@ -70,7 +70,7 @@ EXTERN int TclpCreateProcess _ANSI_ARGS_((Tcl_Interp * interp, int argc, CONST char ** argv, TclFile inputFile, TclFile outputFile, - TclFile errorFile, Tcl_Pid * pidPtr)); + TclFile errorFile, Tcl_Pid * pidPtr, int noQuote)); #endif /* Slot 5 is reserved */ #ifndef TclpMakeFile_TCL_DECLARED @@ -210,7 +210,7 @@ EXTERN int TclpCreateProcess _ANSI_ARGS_((Tcl_Interp * interp, int argc, CONST char ** argv, TclFile inputFile, TclFile outputFile, - TclFile errorFile, Tcl_Pid * pidPtr)); + TclFile errorFile, Tcl_Pid * pidPtr, int noQuote)); #endif /* Slot 16 is reserved */ /* Slot 17 is reserved */ @@ -314,7 +314,7 @@ int (*tclpCloseFile) _ANSI_ARGS_((TclFile file)); /* 1 */ Tcl_Channel (*tclpCreateCommandChannel) _ANSI_ARGS_((TclFile readFile, TclFile writeFile, TclFile errorFile, int numPids, Tcl_Pid * pidPtr)); /* 2 */ int (*tclpCreatePipe) _ANSI_ARGS_((TclFile * readPipe, TclFile * writePipe)); /* 3 */ - int (*tclpCreateProcess) _ANSI_ARGS_((Tcl_Interp * interp, int argc, CONST char ** argv, TclFile inputFile, TclFile outputFile, TclFile errorFile, Tcl_Pid * pidPtr)); /* 4 */ + int (*tclpCreateProcess) _ANSI_ARGS_((Tcl_Interp * interp, int argc, CONST char ** argv, TclFile inputFile, TclFile outputFile, TclFile errorFile, Tcl_Pid * pidPtr, int noQuote)); /* 4 */ void *reserved5; TclFile (*tclpMakeFile) _ANSI_ARGS_((Tcl_Channel channel, int direction)); /* 6 */ TclFile (*tclpOpenFile) _ANSI_ARGS_((CONST char * fname, int mode)); /* 7 */ @@ -342,7 +342,7 @@ int (*tclpCloseFile) _ANSI_ARGS_((TclFile file)); /* 12 */ Tcl_Channel (*tclpCreateCommandChannel) _ANSI_ARGS_((TclFile readFile, TclFile writeFile, TclFile errorFile, int numPids, Tcl_Pid * pidPtr)); /* 13 */ int (*tclpCreatePipe) _ANSI_ARGS_((TclFile * readPipe, TclFile * writePipe)); /* 14 */ - int (*tclpCreateProcess) _ANSI_ARGS_((Tcl_Interp * interp, int argc, CONST char ** argv, TclFile inputFile, TclFile outputFile, TclFile errorFile, Tcl_Pid * pidPtr)); /* 15 */ + int (*tclpCreateProcess) _ANSI_ARGS_((Tcl_Interp * interp, int argc, CONST char ** argv, TclFile inputFile, TclFile outputFile, TclFile errorFile, Tcl_Pid * pidPtr, int noQuote)); /* 15 */ void *reserved16; void *reserved17; TclFile (*tclpMakeFile) _ANSI_ARGS_((Tcl_Channel channel, int direction)); /* 18 */ diff -ur tcl8.5a4\generic\tclIOCmd.c tcl8.5a4-exec\generic\tclIOCmd.c --- tcl8.5a4\generic\tclIOCmd.c Fri Nov 04 17:38:38 2005 +++ tcl8.5a4-exec\generic\tclIOCmd.c Tue Aug 29 16:26:48 2006 @@ -800,19 +800,20 @@ char *string; Tcl_Channel chan; CONST char *argStorage[NUM_ARGS]; - int argc, background, i, index, keepNewline, result, skip, length; + int argc, background, i, index, keepNewline, noQuote, result, skip, length; static CONST char *options[] = { - "-keepnewline", "--", NULL + "-keepnewline", "-noquote", "--", NULL }; enum options { - EXEC_KEEPNEWLINE, EXEC_LAST + EXEC_KEEPNEWLINE, EXEC_NOQUOTE, EXEC_LAST }; /* - * Check for a leading "-keepnewline" argument. + * Check for a leading "-keepnewline" and "-noquote" arguments. */ keepNewline = 0; + noQuote = 0; for (skip = 1; skip < objc; skip++) { string = Tcl_GetString(objv[skip]); if (string[0] != '-') { @@ -824,6 +825,8 @@ } if (index == EXEC_KEEPNEWLINE) { keepNewline = 1; + } else if (index == EXEC_NOQUOTE) { + noQuote = TCL_NOQUOTE; } else { skip++; break; @@ -866,7 +869,7 @@ } argv[argc] = NULL; chan = Tcl_OpenCommandChannel(interp, argc, argv, - (background ? 0 : TCL_STDOUT | TCL_STDERR)); + (background ? 0 : TCL_STDOUT | TCL_STDERR) | noQuote); /* * Free the argv array if malloc'ed storage was used. diff -ur tcl8.5a4\generic\tclPipe.c tcl8.5a4-exec\generic\tclPipe.c --- tcl8.5a4\generic\tclPipe.c Wed Mar 15 19:38:50 2006 +++ tcl8.5a4-exec\generic\tclPipe.c Tue Aug 29 16:31:13 2006 @@ -443,7 +443,7 @@ * frome this pipe is stored at *outPipePtr. * NULL means command specified its own output * sink. */ - TclFile *errFilePtr) /* If non-NULL, all stderr output from the + TclFile *errFilePtr, /* If non-NULL, all stderr output from the * pipeline will go to a temporary file * created here, and a descriptor to read the * file will be left at *errFilePtr. The file @@ -453,6 +453,7 @@ * to our stderr. If the pipeline specifies * redirection then the file will still be * created but it will never get any data. */ + int noQuote) /* If non-NULL Tcl will refrain from quoting the arguments */ { Tcl_Pid *pidPtr = NULL; /* Points to malloc-ed array holding all the * pids of child processes. */ @@ -895,7 +896,7 @@ oldName = argv[i]; argv[i] = Tcl_DStringValue(&execBuffer); result = TclpCreateProcess(interp, lastArg - i, argv + i, - curInFile, curOutFile, curErrFile, &pid); + curInFile, curOutFile, curErrFile, &pid, noQuote); argv[i] = oldName; if (result != TCL_OK) { goto error; @@ -1027,11 +1028,12 @@ int argc, /* How many arguments. */ CONST char **argv, /* Array of arguments for command pipe. */ int flags) /* Or'ed combination of TCL_STDIN, TCL_STDOUT, - * TCL_STDERR, and TCL_ENFORCE_MODE. */ + * TCL_STDERR, TCL_ENFORCE_MODE and TCL_NOQUOTE. */ { TclFile *inPipePtr, *outPipePtr, *errFilePtr; TclFile inPipe, outPipe, errFile; int numPids; + int noQuote; Tcl_Pid *pidPtr; Tcl_Channel channel; @@ -1040,9 +1042,11 @@ inPipePtr = (flags & TCL_STDIN) ? &inPipe : NULL; outPipePtr = (flags & TCL_STDOUT) ? &outPipe : NULL; errFilePtr = (flags & TCL_STDERR) ? &errFile : NULL; + + noQuote = (flags & TCL_NOQUOTE); numPids = TclCreatePipeline(interp, argc, argv, &pidPtr, inPipePtr, - outPipePtr, errFilePtr); + outPipePtr, errFilePtr, noQuote); if (numPids < 0) { goto error; diff -ur tcl8.5a4\unix\tclUnixPipe.c tcl8.5a4-exec\unix\tclUnixPipe.c --- tcl8.5a4\unix\tclUnixPipe.c Mon Mar 27 13:08:52 2006 +++ tcl8.5a4-exec\unix\tclUnixPipe.c Tue Aug 29 16:33:02 2006 @@ -390,9 +390,10 @@ * file is not writeable or is NULL, errors * from the child will be discarded. errorFile * may be the same as outputFile. */ - Tcl_Pid *pidPtr) /* If this function is successful, pidPtr is + Tcl_Pid *pidPtr, /* If this function is successful, pidPtr is * filled with the process id of the child * process. */ + int noQuote) /* If set arguments will not be quoted */ { TclFile errPipeIn, errPipeOut; int joinThisError, count, status, fd; diff -ur tcl8.5a4\win\tclWinPipe.c tcl8.5a4-exec\win\tclWinPipe.c --- tcl8.5a4\win\tclWinPipe.c Tue Mar 28 20:22:50 2006 +++ tcl8.5a4-exec\win\tclWinPipe.c Tue Aug 29 16:52:21 2006 @@ -180,7 +180,7 @@ static int ApplicationType(Tcl_Interp *interp, const char *fileName, char *fullName); static void BuildCommandLine(const char *executable, int argc, - CONST char **argv, Tcl_DString *linePtr); + CONST char **argv, Tcl_DString *linePtr, int noQuote); static BOOL HasConsole(void); static int PipeBlockModeProc(ClientData instanceData, int mode); static void PipeCheckProc(ClientData clientData, int flags); @@ -951,9 +951,10 @@ * file is not writeable or is NULL, errors * from the child will be discarded. errorFile * may be the same as outputFile. */ - Tcl_Pid *pidPtr) /* If this function is successful, pidPtr is + Tcl_Pid *pidPtr, /* If this function is successful, pidPtr is * filled with the process id of the child * process. */ + int noQuote) /* If set arguments will not be quoted */ { int result, applType, createFlags; Tcl_DString cmdLine; /* Complete command line (TCHAR). */ @@ -1243,7 +1244,7 @@ * ab~1.def instead of "a b.default"). */ - BuildCommandLine(execPath, argc, argv, &cmdLine); + BuildCommandLine(execPath, argc, argv, &cmdLine, noQuote); if ((*tclWinProcs->createProcessProc)(NULL, (TCHAR *) Tcl_DStringValue(&cmdLine), NULL, NULL, TRUE, @@ -1542,8 +1543,9 @@ * extension). Replacement for argv[0]. */ int argc, /* Number of arguments. */ CONST char **argv, /* Argument strings in UTF. */ - Tcl_DString *linePtr) /* Initialized Tcl_DString that receives the + Tcl_DString *linePtr, /* Initialized Tcl_DString that receives the * command line (TCHAR). */ + int noQuote) /* If set no quoting will be applied to arguments */ { CONST char *arg, *start, *special; int quote, i; @@ -1567,7 +1569,7 @@ arg = argv[i]; Tcl_DStringAppend(&ds, " ", 1); } - + if (!noQuote) { quote = 0; if (arg[0] == '\0') { quote = 1; @@ -1624,7 +1626,11 @@ if (quote) { Tcl_DStringAppend(&ds, "\"", 1); } - } + } else { + //no quoting applies + Tcl_DStringAppend(&ds,arg,-1); + } + } Tcl_DStringFree(linePtr); Tcl_WinUtfToTChar(Tcl_DStringValue(&ds), Tcl_DStringLength(&ds), linePtr); Tcl_DStringFree(&ds);