2 * FOR.C - for internal batch command.
7 * 16-Jul-1998 (Hans B Pufal)
10 * 16-Jul-1998 (John P Price)
11 * Seperated commands into individual files.
13 * 19-Jul-1998 (Hans B Pufal)
14 * Implementation of FOR.
16 * 27-Jul-1998 (John P Price <linux-guru@gcfl.net>)
17 * Added config.h include.
19 * 20-Jan-1999 (Eric Kohl)
20 * Unicode and redirection safe!
22 * 01-Sep-1999 (Eric Kohl)
25 * 23-Feb-2001 (Carl Nettelblad <cnettel@hem.passagen.se>)
26 * Implemented preservation of echo flag. Some other for related
27 * code in other files fixed, too.
29 * 28-Apr-2005 (Magnus Olsen) <magnus@greatlord.com>)
30 * Remove all hardcode string to En.rc
36 /* FOR is a special command, so this function is only used for showing help now */
37 INT
cmd_for (LPTSTR param
)
39 TRACE ("cmd_for (\'%s\')\n", debugstr_aw(param
));
41 if (!_tcsncmp (param
, _T("/?"), 2))
43 ConOutResPaging(TRUE
,STRING_FOR_HELP1
);
51 /* The stack of current FOR contexts.
52 * NULL when no FOR command is active */
53 LPFOR_CONTEXT fc
= NULL
;
55 /* Get the next element of the FOR's list */
56 static BOOL
GetNextElement(TCHAR
**pStart
, TCHAR
**pEnd
)
59 BOOL InQuotes
= FALSE
;
65 while (*p
&& (InQuotes
|| !_istspace(*p
)))
66 InQuotes
^= (*p
++ == _T('"'));
71 /* Execute a single instance of a FOR command */
72 static INT
RunInstance(PARSED_COMMAND
*Cmd
)
74 if (bEcho
&& !bDisableBatchEcho
&& Cmd
->Subcommands
->Type
!= C_QUIET
)
79 EchoCommand(Cmd
->Subcommands
);
82 /* Just run the command (variable expansion is done in DoDelayedExpansion) */
83 return ExecuteCommand(Cmd
->Subcommands
);
86 /* Check if this FOR should be terminated early */
87 static BOOL
Exiting(PARSED_COMMAND
*Cmd
)
89 /* Someone might have removed our context */
90 return bCtrlBreak
|| fc
!= Cmd
->For
.Context
;
93 /* Read the contents of a text file into memory,
94 * dynamically allocating enough space to hold it all */
95 static LPTSTR
ReadFileContents(FILE *InputFile
, TCHAR
*Buffer
)
98 DWORD AllocLen
= 1000;
99 LPTSTR Contents
= cmd_alloc(AllocLen
* sizeof(TCHAR
));
103 while (_fgetts(Buffer
, CMDLINE_LENGTH
, InputFile
))
105 DWORD CharsRead
= _tcslen(Buffer
);
106 while (Len
+ CharsRead
>= AllocLen
)
108 Contents
= cmd_realloc(Contents
, (AllocLen
*= 2) * sizeof(TCHAR
));
112 _tcscpy(&Contents
[Len
], Buffer
);
116 Contents
[Len
] = _T('\0');
120 static INT
ForF(PARSED_COMMAND
*Cmd
, LPTSTR List
, TCHAR
*Buffer
)
122 LPTSTR Delims
= _T(" \t");
125 DWORD Tokens
= (1 << 1);
126 BOOL RemainderVar
= FALSE
;
127 TCHAR StringQuote
= _T('"');
128 TCHAR CommandQuote
= _T('\'');
129 LPTSTR Variables
[32];
137 TCHAR
*Param
= Cmd
->For
.Params
;
138 if (*Param
== _T('"') || *Param
== _T('\''))
141 while (*Param
&& *Param
!= Quote
)
143 if (*Param
<= _T(' '))
147 else if (_tcsnicmp(Param
, _T("delims="), 7) == 0)
150 /* delims=xxx: Specifies the list of characters that separate tokens */
152 while (*Param
&& *Param
!= Quote
)
154 if (*Param
== _T(' '))
156 TCHAR
*FirstSpace
= Param
;
157 Param
+= _tcsspn(Param
, _T(" "));
158 /* Exclude trailing spaces if this is not the last parameter */
159 if (*Param
&& *Param
!= Quote
)
160 *FirstSpace
= _T('\0');
168 else if (_tcsnicmp(Param
, _T("eol="), 4) == 0)
171 /* eol=c: Lines starting with this character (may be
172 * preceded by delimiters) are skipped. */
177 else if (_tcsnicmp(Param
, _T("skip="), 5) == 0)
179 /* skip=n: Number of lines to skip at the beginning of each file */
180 SkipLines
= _tcstol(Param
+ 5, &Param
, 0);
184 else if (_tcsnicmp(Param
, _T("tokens="), 7) == 0)
187 /* tokens=x,y,m-n: List of token numbers (must be between
188 * 1 and 31) that will be assigned into variables. */
190 while (*Param
&& *Param
!= Quote
&& *Param
!= _T('*'))
192 INT First
= _tcstol(Param
, &Param
, 0);
196 if (*Param
== _T('-'))
198 /* It's a range of tokens */
199 Last
= _tcstol(Param
+ 1, &Param
, 0);
200 if (Last
< First
|| Last
> 31)
203 Tokens
|= (2 << Last
) - (1 << First
);
205 if (*Param
!= _T(','))
209 /* With an asterisk at the end, an additional variable
210 * will be created to hold the remainder of the line
211 * (after the last token specified). */
212 if (*Param
== _T('*'))
218 else if (_tcsnicmp(Param
, _T("useback"), 7) == 0)
221 /* usebackq: Use alternate quote characters */
222 StringQuote
= _T('\'');
223 CommandQuote
= _T('`');
224 /* Can be written as either "useback" or "usebackq" */
225 if (_totlower(*Param
) == _T('q'))
237 /* Count how many variables will be set: one for each token,
238 * plus maybe one for the remainder */
239 fc
->varcount
= RemainderVar
;
240 for (i
= 1; i
< 32; i
++)
241 fc
->varcount
+= (Tokens
>> i
& 1);
242 fc
->values
= Variables
;
244 if (*List
== StringQuote
|| *List
== CommandQuote
)
246 /* Treat the entire "list" as one single element */
248 End
= &List
[_tcslen(List
)];
253 while (GetNextElement(&Start
, &End
))
256 LPTSTR FullInput
, In
, NextLine
;
260 if (*Start
== StringQuote
&& End
[-1] == StringQuote
)
262 /* Input given directly as a string */
264 FullInput
= cmd_dup(Start
+ 1);
266 else if (*Start
== CommandQuote
&& End
[-1] == CommandQuote
)
268 /* Read input from a command */
270 InputFile
= _tpopen(Start
+ 1, _T("r"));
273 error_bad_command(Start
+ 1);
276 FullInput
= ReadFileContents(InputFile
, Buffer
);
281 /* Read input from a file */
285 InputFile
= _tfopen(Start
, _T("r"));
289 error_sfile_not_found(Start
);
292 FullInput
= ReadFileContents(InputFile
, Buffer
);
298 error_out_of_memory();
302 /* Loop over the input line by line */
307 DWORD RemainingTokens
= Tokens
;
308 LPTSTR
*CurVar
= Variables
;
310 NextLine
= _tcschr(In
, _T('\n'));
312 *NextLine
++ = _T('\0');
317 /* Ignore lines where the first token starts with the eol character */
318 In
+= _tcsspn(In
, Delims
);
322 while ((RemainingTokens
>>= 1) != 0)
324 /* Save pointer to this token in a variable if requested */
325 if (RemainingTokens
& 1)
327 /* Find end of token */
328 In
+= _tcscspn(In
, Delims
);
329 /* Nul-terminate it and advance to next token */
333 In
+= _tcsspn(In
, Delims
);
336 /* Save pointer to remainder of line */
339 /* Don't run unless the line had enough tokens to fill at least one variable */
341 Ret
= RunInstance(Cmd
);
342 } while (!Exiting(Cmd
) && (In
= NextLine
) != NULL
);
349 /* FOR /L: Do a numeric loop */
350 static INT
ForLoop(PARSED_COMMAND
*Cmd
, LPTSTR List
, TCHAR
*Buffer
)
352 enum { START
, STEP
, END
};
353 INT params
[3] = { 0, 0, 0 };
357 TCHAR
*Start
, *End
= List
;
358 for (i
= 0; i
< 3 && GetNextElement(&Start
, &End
); i
++)
359 params
[i
] = _tcstol(Start
, NULL
, 0);
362 while (!Exiting(Cmd
) &&
363 (params
[STEP
] >= 0 ? (i
<= params
[END
]) : (i
>= params
[END
])))
365 _itot(i
, Buffer
, 10);
366 Ret
= RunInstance(Cmd
);
372 /* Process a FOR in one directory. Stored in Buffer (up to BufPos) is a
373 * string which is prefixed to each element of the list. In a normal FOR
374 * it will be empty, but in FOR /R it will be the directory name. */
375 static INT
ForDir(PARSED_COMMAND
*Cmd
, LPTSTR List
, TCHAR
*Buffer
, TCHAR
*BufPos
)
377 TCHAR
*Start
, *End
= List
;
379 while (!Exiting(Cmd
) && GetNextElement(&Start
, &End
))
381 if (BufPos
+ (End
- Start
) > &Buffer
[CMDLINE_LENGTH
])
383 memcpy(BufPos
, Start
, (End
- Start
) * sizeof(TCHAR
));
384 BufPos
[End
- Start
] = _T('\0');
386 if (_tcschr(BufPos
, _T('?')) || _tcschr(BufPos
, _T('*')))
388 WIN32_FIND_DATA w32fd
;
393 hFind
= FindFirstFile(Buffer
, &w32fd
);
394 if (hFind
== INVALID_HANDLE_VALUE
)
396 FilePart
= _tcsrchr(BufPos
, _T('\\'));
397 FilePart
= FilePart
? FilePart
+ 1 : BufPos
;
400 if (w32fd
.dwFileAttributes
& FILE_ATTRIBUTE_HIDDEN
)
402 if (!(w32fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
403 != !(Cmd
->For
.Switches
& FOR_DIRS
))
405 if (_tcscmp(w32fd
.cFileName
, _T(".")) == 0 ||
406 _tcscmp(w32fd
.cFileName
, _T("..")) == 0)
408 _tcscpy(FilePart
, w32fd
.cFileName
);
409 Ret
= RunInstance(Cmd
);
410 } while (!Exiting(Cmd
) && FindNextFile(hFind
, &w32fd
));
415 Ret
= RunInstance(Cmd
);
421 /* FOR /R: Process a FOR in each directory of a tree, recursively. */
422 static INT
ForRecursive(PARSED_COMMAND
*Cmd
, LPTSTR List
, TCHAR
*Buffer
, TCHAR
*BufPos
)
425 WIN32_FIND_DATA w32fd
;
428 if (BufPos
[-1] != _T('\\'))
430 *BufPos
++ = _T('\\');
434 Ret
= ForDir(Cmd
, List
, Buffer
, BufPos
);
436 _tcscpy(BufPos
, _T("*"));
437 hFind
= FindFirstFile(Buffer
, &w32fd
);
438 if (hFind
== INVALID_HANDLE_VALUE
)
442 if (!(w32fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
))
444 if (_tcscmp(w32fd
.cFileName
, _T(".")) == 0 ||
445 _tcscmp(w32fd
.cFileName
, _T("..")) == 0)
447 Ret
= ForRecursive(Cmd
, List
, Buffer
, _stpcpy(BufPos
, w32fd
.cFileName
));
448 } while (!Exiting(Cmd
) && FindNextFile(hFind
, &w32fd
));
454 ExecuteFor(PARSED_COMMAND
*Cmd
)
456 TCHAR Buffer
[CMDLINE_LENGTH
]; /* Buffer to hold the variable value */
457 LPTSTR BufferPtr
= Buffer
;
460 LPTSTR List
= DoDelayedExpansion(Cmd
->For
.List
);
465 /* Create our FOR context */
466 lpNew
= cmd_alloc(sizeof(FOR_CONTEXT
));
473 lpNew
->firstvar
= Cmd
->For
.Variable
;
475 lpNew
->values
= &BufferPtr
;
477 Cmd
->For
.Context
= lpNew
;
480 if (Cmd
->For
.Switches
& FOR_F
)
482 Ret
= ForF(Cmd
, List
, Buffer
);
484 else if (Cmd
->For
.Switches
& FOR_LOOP
)
486 Ret
= ForLoop(Cmd
, List
, Buffer
);
488 else if (Cmd
->For
.Switches
& FOR_RECURSIVE
)
490 DWORD Len
= GetFullPathName(Cmd
->For
.Params
? Cmd
->For
.Params
: _T("."),
491 MAX_PATH
, Buffer
, NULL
);
492 Ret
= ForRecursive(Cmd
, List
, Buffer
, &Buffer
[Len
]);
496 Ret
= ForDir(Cmd
, List
, Buffer
, Buffer
);
499 /* Remove our context, unless someone already did that */