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 hardcoded strings in 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 SIZE_T AllocLen
= 1000;
99 LPTSTR Contents
= cmd_alloc(AllocLen
* sizeof(TCHAR
));
103 while (_fgetts(Buffer
, CMDLINE_LENGTH
, InputFile
))
105 ULONG_PTR CharsRead
= _tcslen(Buffer
);
106 while (Len
+ CharsRead
>= AllocLen
)
108 LPTSTR OldContents
= Contents
;
109 Contents
= cmd_realloc(Contents
, (AllocLen
*= 2) * sizeof(TCHAR
));
112 cmd_free(OldContents
);
116 _tcscpy(&Contents
[Len
], Buffer
);
120 Contents
[Len
] = _T('\0');
124 static INT
ForF(PARSED_COMMAND
*Cmd
, LPTSTR List
, TCHAR
*Buffer
)
126 LPTSTR Delims
= _T(" \t");
129 DWORD Tokens
= (1 << 1);
130 BOOL RemainderVar
= FALSE
;
131 TCHAR StringQuote
= _T('"');
132 TCHAR CommandQuote
= _T('\'');
133 LPTSTR Variables
[32];
141 TCHAR
*Param
= Cmd
->For
.Params
;
142 if (*Param
== _T('"') || *Param
== _T('\''))
145 while (*Param
&& *Param
!= Quote
)
147 if (*Param
<= _T(' '))
151 else if (_tcsnicmp(Param
, _T("delims="), 7) == 0)
154 /* delims=xxx: Specifies the list of characters that separate tokens */
156 while (*Param
&& *Param
!= Quote
)
158 if (*Param
== _T(' '))
160 TCHAR
*FirstSpace
= Param
;
161 Param
+= _tcsspn(Param
, _T(" "));
162 /* Exclude trailing spaces if this is not the last parameter */
163 if (*Param
&& *Param
!= Quote
)
164 *FirstSpace
= _T('\0');
172 else if (_tcsnicmp(Param
, _T("eol="), 4) == 0)
175 /* eol=c: Lines starting with this character (may be
176 * preceded by delimiters) are skipped. */
181 else if (_tcsnicmp(Param
, _T("skip="), 5) == 0)
183 /* skip=n: Number of lines to skip at the beginning of each file */
184 SkipLines
= _tcstol(Param
+ 5, &Param
, 0);
188 else if (_tcsnicmp(Param
, _T("tokens="), 7) == 0)
191 /* tokens=x,y,m-n: List of token numbers (must be between
192 * 1 and 31) that will be assigned into variables. */
194 while (*Param
&& *Param
!= Quote
&& *Param
!= _T('*'))
196 INT First
= _tcstol(Param
, &Param
, 0);
200 if (*Param
== _T('-'))
202 /* It's a range of tokens */
203 Last
= _tcstol(Param
+ 1, &Param
, 0);
204 if (Last
< First
|| Last
> 31)
207 Tokens
|= (2 << Last
) - (1 << First
);
209 if (*Param
!= _T(','))
213 /* With an asterisk at the end, an additional variable
214 * will be created to hold the remainder of the line
215 * (after the last token specified). */
216 if (*Param
== _T('*'))
222 else if (_tcsnicmp(Param
, _T("useback"), 7) == 0)
225 /* usebackq: Use alternate quote characters */
226 StringQuote
= _T('\'');
227 CommandQuote
= _T('`');
228 /* Can be written as either "useback" or "usebackq" */
229 if (_totlower(*Param
) == _T('q'))
241 /* Count how many variables will be set: one for each token,
242 * plus maybe one for the remainder */
243 fc
->varcount
= RemainderVar
;
244 for (i
= 1; i
< 32; i
++)
245 fc
->varcount
+= (Tokens
>> i
& 1);
246 fc
->values
= Variables
;
248 if (*List
== StringQuote
|| *List
== CommandQuote
)
250 /* Treat the entire "list" as one single element */
252 End
= &List
[_tcslen(List
)];
257 while (GetNextElement(&Start
, &End
))
260 LPTSTR FullInput
, In
, NextLine
;
264 if (*Start
== StringQuote
&& End
[-1] == StringQuote
)
266 /* Input given directly as a string */
268 FullInput
= cmd_dup(Start
+ 1);
270 else if (*Start
== CommandQuote
&& End
[-1] == CommandQuote
)
272 /* Read input from a command */
274 InputFile
= _tpopen(Start
+ 1, _T("r"));
277 error_bad_command(Start
+ 1);
280 FullInput
= ReadFileContents(InputFile
, Buffer
);
285 /* Read input from a file */
289 InputFile
= _tfopen(Start
, _T("r"));
293 error_sfile_not_found(Start
);
296 FullInput
= ReadFileContents(InputFile
, Buffer
);
302 error_out_of_memory();
306 /* Loop over the input line by line */
311 DWORD RemainingTokens
= Tokens
;
312 LPTSTR
*CurVar
= Variables
;
314 NextLine
= _tcschr(In
, _T('\n'));
316 *NextLine
++ = _T('\0');
321 /* Ignore lines where the first token starts with the eol character */
322 In
+= _tcsspn(In
, Delims
);
326 while ((RemainingTokens
>>= 1) != 0)
328 /* Save pointer to this token in a variable if requested */
329 if (RemainingTokens
& 1)
331 /* Find end of token */
332 In
+= _tcscspn(In
, Delims
);
333 /* Nul-terminate it and advance to next token */
337 In
+= _tcsspn(In
, Delims
);
340 /* Save pointer to remainder of line */
343 /* Don't run unless the line had enough tokens to fill at least one variable */
345 Ret
= RunInstance(Cmd
);
346 } while (!Exiting(Cmd
) && (In
= NextLine
) != NULL
);
353 /* FOR /L: Do a numeric loop */
354 static INT
ForLoop(PARSED_COMMAND
*Cmd
, LPTSTR List
, TCHAR
*Buffer
)
356 enum { START
, STEP
, END
};
357 INT params
[3] = { 0, 0, 0 };
361 TCHAR
*Start
, *End
= List
;
362 for (i
= 0; i
< 3 && GetNextElement(&Start
, &End
); i
++)
363 params
[i
] = _tcstol(Start
, NULL
, 0);
366 while (!Exiting(Cmd
) &&
367 (params
[STEP
] >= 0 ? (i
<= params
[END
]) : (i
>= params
[END
])))
369 _itot(i
, Buffer
, 10);
370 Ret
= RunInstance(Cmd
);
376 /* Process a FOR in one directory. Stored in Buffer (up to BufPos) is a
377 * string which is prefixed to each element of the list. In a normal FOR
378 * it will be empty, but in FOR /R it will be the directory name. */
379 static INT
ForDir(PARSED_COMMAND
*Cmd
, LPTSTR List
, TCHAR
*Buffer
, TCHAR
*BufPos
)
381 TCHAR
*Start
, *End
= List
;
383 while (!Exiting(Cmd
) && GetNextElement(&Start
, &End
))
385 if (BufPos
+ (End
- Start
) > &Buffer
[CMDLINE_LENGTH
])
387 memcpy(BufPos
, Start
, (End
- Start
) * sizeof(TCHAR
));
388 BufPos
[End
- Start
] = _T('\0');
390 if (_tcschr(BufPos
, _T('?')) || _tcschr(BufPos
, _T('*')))
392 WIN32_FIND_DATA w32fd
;
397 hFind
= FindFirstFile(Buffer
, &w32fd
);
398 if (hFind
== INVALID_HANDLE_VALUE
)
400 FilePart
= _tcsrchr(BufPos
, _T('\\'));
401 FilePart
= FilePart
? FilePart
+ 1 : BufPos
;
404 if (w32fd
.dwFileAttributes
& FILE_ATTRIBUTE_HIDDEN
)
406 if (!(w32fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
407 != !(Cmd
->For
.Switches
& FOR_DIRS
))
409 if (_tcscmp(w32fd
.cFileName
, _T(".")) == 0 ||
410 _tcscmp(w32fd
.cFileName
, _T("..")) == 0)
412 _tcscpy(FilePart
, w32fd
.cFileName
);
413 Ret
= RunInstance(Cmd
);
414 } while (!Exiting(Cmd
) && FindNextFile(hFind
, &w32fd
));
419 Ret
= RunInstance(Cmd
);
425 /* FOR /R: Process a FOR in each directory of a tree, recursively. */
426 static INT
ForRecursive(PARSED_COMMAND
*Cmd
, LPTSTR List
, TCHAR
*Buffer
, TCHAR
*BufPos
)
429 WIN32_FIND_DATA w32fd
;
432 if (BufPos
[-1] != _T('\\'))
434 *BufPos
++ = _T('\\');
438 Ret
= ForDir(Cmd
, List
, Buffer
, BufPos
);
440 _tcscpy(BufPos
, _T("*"));
441 hFind
= FindFirstFile(Buffer
, &w32fd
);
442 if (hFind
== INVALID_HANDLE_VALUE
)
446 if (!(w32fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
))
448 if (_tcscmp(w32fd
.cFileName
, _T(".")) == 0 ||
449 _tcscmp(w32fd
.cFileName
, _T("..")) == 0)
451 Ret
= ForRecursive(Cmd
, List
, Buffer
, _stpcpy(BufPos
, w32fd
.cFileName
));
452 } while (!Exiting(Cmd
) && FindNextFile(hFind
, &w32fd
));
458 ExecuteFor(PARSED_COMMAND
*Cmd
)
460 TCHAR Buffer
[CMDLINE_LENGTH
]; /* Buffer to hold the variable value */
461 LPTSTR BufferPtr
= Buffer
;
464 LPTSTR List
= DoDelayedExpansion(Cmd
->For
.List
);
469 /* Create our FOR context */
470 lpNew
= cmd_alloc(sizeof(FOR_CONTEXT
));
477 lpNew
->firstvar
= Cmd
->For
.Variable
;
479 lpNew
->values
= &BufferPtr
;
481 Cmd
->For
.Context
= lpNew
;
484 if (Cmd
->For
.Switches
& FOR_F
)
486 Ret
= ForF(Cmd
, List
, Buffer
);
488 else if (Cmd
->For
.Switches
& FOR_LOOP
)
490 Ret
= ForLoop(Cmd
, List
, Buffer
);
492 else if (Cmd
->For
.Switches
& FOR_RECURSIVE
)
494 DWORD Len
= GetFullPathName(Cmd
->For
.Params
? Cmd
->For
.Params
: _T("."),
495 MAX_PATH
, Buffer
, NULL
);
496 Ret
= ForRecursive(Cmd
, List
, Buffer
, &Buffer
[Len
]);
500 Ret
= ForDir(Cmd
, List
, Buffer
, Buffer
);
503 /* Remove our context, unless someone already did that */