Batch: Why does appending a textfile write "ECHO is off" instead? - batch-file

So I wrote a batch that has some code to check how many times it has been run by reading a textfile and then writing back into that textfile the new, increased number.
#ECHO OFF
for /f "delims=" %%x in (TimesRun.txt) do set Build=%%x
set Build=%Build%+1
#echo Build value : %Build%
echo %Build%>>TimesRun.txt
Pause
That does append the textfile allright, but it adds "1+1" to it. Silly me! I forgot to use the /a switch to enable arithmetic operations! But when I change the code accordingly...
#ECHO OFF
for /f "delims=" %%x in (TimesRun.txt) do set Build=%%x
set /a Build=%Build%+1
#echo Build value : %Build%
echo %Build%>>TimesRun.txt
Pause
... something funny happens: Instead of appending my file, ECHO is off. gets written on the console. Now, I know that this usually happens when ECHO is used without text or with an empty variable. I have added the first #echo Build value : %Build% specifically to see whether the variable Build is empty or not; it is not, and the calculation was carried out correctly.
I already figured out that
>>TimesRun.txt (echo %Build%)
does bring the desired result. I still do not understand why
echo %Build%>>TimesRun.txt
does not, however. What am I missing?

You are unintentionally specifying a redirection handle.
Redirection allows you to specify a certain handle that defines what is to be redirected:
0 = STDIN (keyboard input)
1 = STDOUT (text output)
2 = STDERR (error text output)
3 ~ 9 = undefined
For the input redirection operator <, handle 0 is used by default; for the output redirection operators > and >>, the default handle is 1.
You can explicitly specify a handle by putting a single numeric figure in front of the redirection operator; for instance 2> defines to redirect the error text output.
In your echo command line you are doing exactly this unintentionally, when %Build% is a single numberic digit, like 1 for example:
echo 1>>TimesRun.txt
To avoid that, you have the following options:
To reverse the statement so that the redirection definition comes first:
>>TimesRun.txt echo %Build%
This is the most general and secure way of doing redirections.
To enclose the redirected command in parentheses:
(echo %Build%)>>TimesRun.txt
This also works safely.
To put a SPACE in front of the redirection operator:
echo %Build% >>TimesRun.txt
This works too, but the additional SPACE is included in the output of echo.
See also this great post: cmd.exe redirection operators order and position.

Batch file redirection can be customized to specify where you're outputting to.
command 1>file.txt redirects the output of STDOUT to file.txt
command 2>file.txt redirects the output of STDERR to file.txt
Your build value was 1, so you inadvertently told CMD to send the output of echo to TimesRun.txt - when you run echo by itself, it prints it's status (ON or OFF).
You also could have said echo %Build% >>TimesRun.txt and the space would prevent the value of Build from being treated as a redirection command.

The Microsoft article Using command redirection operators explains the 3 standard handles and how to redirect them to another handle, command, device, file or console application.
Redirection of output written to handle 1 - STDOUT - to a file should be done with just
using > ... create file if not already existing or overwrite existing file, or
using >> ... create file if not already existing or append to existing file.
The redirection operators are usually appended at end of a command line. But this is problematic in case of using command ECHO and the string output to STDOUT ends with 1 to 9.
One of several solutions is to specify in this case the redirection at beginning of the command line:
#for /F "delims=" %%x in (TimesRun.txt) do #set Build=%%x
#set /A Build+=1
#echo Build value : %Build%
>>TimesRun.txt echo %Build%
Executing this small batch file without #echo off at top from within a command prompt window shows what Windows command processor executes after preprocessing each line with text file TimesRun.txt containing currently the value 0 or does not exist at all.
echo 1 1>>TimesRun.txt
It can be seen that Windows command interpreter moved the redirection to end of line with inserting a space and 1 left to >>.
With above batch code the line with >> really executed after preprocessing is:
echo 2 1>>TimesRun.txt
Specifying the redirection at end with 1>>, i.e. use in the batch file
echo %Build%1>>TimesRun.txt
is also no good idea as this would result on first run in executing the line:
echo 11 1>>TimesRun.txt
So 11 is written into the file instead of 1. This wrong output could be avoided by inserting a space before >> or 1>>, i.e. use one of those two:
echo %Build% >>TimesRun.txt
echo %Build% 1>>TimesRun.txt
But then the space after %Build% is also written into the file as really executed is:
echo 1 1>>TimesRun.txt
The trailing space would be no problem here, but should be nevertheless avoided.
Note: On using arithmetic operations, i.e. set /A ... any string not being a number or operator is automatically interpreted as variable name and the current value of this variable is used on evaluating the arithmetic expression. Therefore after set /A with environment variable names consisting only of word characters and starting with an alphabetic character as usually used for environment variables no %...% or !...! must be used inside the arithmetic expression. This is explained in help of command SET output into console window on running set /? within a command prompt window.

Related

CMD: How to echo special command symbol to clipboard?

I need to put this string >>> (it's just some handy copypaste) to clipboard.
Since > is a special cmd-character, I'm using ^ before it to mean all special characters literally.
So far, my Batch code looks like this
(&& pause here is used to see debug messages):
echo ^>^>^> && pause
echo ^>^>^>>"%~dp0foo.txt" && pause
echo foo|clip && pause
echo ^>^>^>|clip && pause
1st line works perfectly (not affecting clipboard though).
2nd line works perfectly (not affecting clipboard either though).
3rd line works perfectly (not using the symbols I need though).
4th line returns >> was unexpected at this time error.
Obviously, I need some syntax tips.
It's a bit tricky, because the pipe creates two new cmd instances (like #aschipf mentioned).
You could use a variable and delayed expansion
set "var=>>>"
cmd /v:on /c "echo(!var!"| clip
Or you can use FOR-variable expansion
set "var=>>>"
( FOR %%X in ("%%var%%") DO #(echo(%%~X^) ) | clip
Okay, I figured an almost decent workaround:
echo ^>^>^>>"%~dp0foo.txt"
type "%~dp0foo.txt"|clip
del "%~dp0foo.txt"
puts >>> into foo.txt right next to your Batch
(it also accounts for spaces in path to the file via ").
returns >>> as a content from foo.txt and puts it into clipboard.
deletes foo.txt right away.
Still hoping to meet a proper-syntax-based solution.

Batch - Remove Range/specific words

In perforce I print the list of shelved/changes to the file with the following batch script :
for %%A IN (%ShelvedCHL%) DO (
echo Change List: %%A
p4 -p %PPort% -c %PClient% unshelve -s %%A && if NOT %ERRORLEVEL%==0 exit -1
)>> list.txt
Here is my list.txt
Change List: 24536
//GSA/TOC.h#1 - unshelved, opened for edit
... /GSA/TOC.h - also opened by test#dev
... /GSA/TOC.h - also opened by test#dev
//odata/oenums.h#6 - unshelved, opened for edit
... //odata/oenums.h#6 - also opened by test#dev
I want to have the following output, basically remove sentence after - with dash as well : or even any perforce command to have less information and just list of files :
//GSA/TOC.h#1
... /GSA/TOC.h
... /GSA/TOC.h
//odata/oenums.h#6
... //odata/oenums.h#6
I would appreciate any help, thanks in advance!
You can do this in p4 natively without doing hardly any scripting at all. Check out this blog post:
https://www.perforce.com/blog/fun-formatting
You can see the fields in a Perforce message by running it with the -e global opt*, like this:
p4 -e files ...
Then you can use any or all of those fields when reformatting the output, like this:
p4 -F "just the filename please: %depotFile%" files ...
*see also -Ztag which gives you an alternate dictionary of output fields
At first, let us take a look at your code:
I do not know the program p4, but I assume it is setting the ErrorLevel. So since this value is updated in the same block of code as you want to read it, you need to use delayed expansion, so place setlocal EnableDelayedExpansion on top of your script and use !ErrorLevel! instead of %ErrorLevel%. Another way is to replace if not !ErrorLevel!==0 by ifnot ErrorLevel 1, meaning if ErrorLevel is not greater than and not equal to 1, or expressed in a simpler way, if ErrorLevel is less than 1, but this works only if the program does not set a negative value.
Even if you corrected the ErrorLevel issue, the if query would never eb executed because of the conditional command concatenation operator %%, because this lets the following command only execute in case the preceding one succeeded, meaning that its exit code1 equals zero. Therefore to execute the if statement, use the unconditional operator &. Anyway, there is also another conditional operator ||, which lets the following command only execute in case the exit code is a non-zero value; this one could replace your if condition completely.
The exit command does not only quit the batch file, it also terminates the command prompt (cmd) instance which the batch script ran in. To quit the batch file only use exit /B instead.
You are setting the ErrorLevel to -1 by exit -1. You can do this, of course, but usually negative values are avoided; hence let me suggest a positive value like 1 (by exit /B 1).
You are opening and closing the file list.txt for every single iteration of the for loop. This reduces overall performance. Furthermore, if list.txt already exists, the data becomes appended; if you do not want that you need to place del "list.txt" 2> nul before the for loop to initially delete the file. Anyway, to write the entire file at once, put another pair of parentheses around the for loop. You can then chose whether to append to an already existing file using the redirection operator >>, or to overwrite it using operator > (without any need to delete it first).
All this results in the following improved script:
(for %%A in (%ShelvedCHL%) do (
echo Change List: %%A
p4 -p %PPort% -c %PClient% unshelve -s %%A || exit /B 1
)) > "list.txt"
Depending on what %ShelvedCHL% contains (it seems to be 24536 in your sample data, so not a file path/name/mask), the for loop might even be superfluous, although I cannot know at this point...
Anyway, all of the above does still not yet account for removal of the partial string beginning with SPACE + - + SPACE, so let us implement this now:
To keep it simple, we could just modify the file list.txt after the above code, using this code (see all the explanatory rem remarks; the mentioned string manipulation is called sub-string substitution):
rem // Read file `list.txt` line by line:
(for /F "usebackq delims= eol=|" %%L in ("list.txt") do (
rem // Assign line string to variable:
set "LINE=%%L"
rem // Enable delayed expansion to be able to do string manipulation:
setlocal EnableDelayedExpansion
rem /* Replace every occurrence of ` - ` by a single character `|`, then use this one
rem as a delimiter to split the line string as `for /F` requires single-character
rem delimiters; just using `-` is not good as they might occur in the partial
rem strings that need to be kept, I suppose; the `|` must not occur in them: */
for /F "tokens=1 delims=| eol=|" %%K in ("!LINE: - =|!") do (
rem // Disable delayed expansion to not lose `!`-marks:
endlocal
rem // Return the split string, that is the part before the (first) ` - `:
echo %%K
)
)) > "list_NEW.txt"
The resulting data is contained in the file list_NEW.txt. To have it in the original file, append the following line to the code:
move /Y "list_NEW.txt" "list.txt" > nul
1... Usually the exit code and ErrorLevel are the same, but there are in fact some rare cases where they may differ.

Adding an iteration to a text file in a batch file

I am trying to add an iteration to a text file to save the output number instead of it being overwritten to keep the iterations.
I am able to write the first iteration number but it will not add the number after.
I have a text file with just the iteration number as 1.
iteration.txt
1
My batch code looks like this so far.
REM Run model outputs
set "iter="
for /F "delims=" %%i in (iteration.txt) do if not defined iter set "iter=%%i"
copy .\run1_Diagnostics.csv storefilesfolder\%itera%_Diagnostics.csv
echo %iter%+1 >iteration.txt
The problem is it either just overwrite the text file to +1 or +1 +1 +1 +1 and doesn't add the number.
I suggest to use this code:
REM Run model outputs
if not exist iteration.txt set "iter=1" & goto CopyFile
set "iter="
for /F "delims=" %%i in (iteration.txt) do if not defined iter set "iter=%%i"
set /A iter+=1
:CopyFile
copy run1_Diagnostics.csv storefilesfolder\%iter%_Diagnostics.csv
>iteration.txt echo %iter%
There is also another method to read the first line of a text file and assign it to an environment variable:
REM Run model outputs
if not exist iteration.txt set "iter=1" & goto CopyFile
set "iter="
set /P iter=<iteration.txt
set /A iter+=1
:CopyFile
echo copy run1_Diagnostics.csv storefilesfolder\%iter%_Diagnostics.csv
>iteration.txt echo %iter%
Both batch files first check if the file iteration.txt exists at all in current directory.
The environment variable iter is defined with value 1 if the file does not exist.
Otherwise the first line is read from the file and assigned to environment variable iter using command FOR or an input redirection from file on prompting for value of the environment variable using command SET with option /P.
The value read from file is incremented by one using command SET with option /A which means the new value of environment variable iter is the result of an arithmetic expression. set /A is the only method supported by Windows command line interpreter to do simple mathematical or binary operations on 32-bit signed integer values. The value of iter is 1 if the string read from file is not a valid 32-bit signed integer.
Next the file is copied with using initial value 1 or the value read from file iteration.txt incremented by one.
.\ is not really necessary to specify the current directory. It is safe to omit those two characters if the file or folder is in current directory.
The last command line outputs the current iteration value to handle STDOUT which is redirected to file being created or overwritten in case of existing already.
The redirection operator and the file name is specified here left to command ECHO which outputs the number because of echo %iter%>iteration.txt would not work for the numbers 1 to 9. And using echo %iter% >iteration.txt writes the space character between number and redirection operator > also into the file as trailing space which should be avoided here.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
copy /?
echo /?
for /?
goto /?
if /?
rem /?
set /?
Read also the Microsoft article about Using Command Redirection Operators for an explanation of redirection operators < and > and single line with multiple commands using Windows batch file for an explanation of operator &.

cmd, write and read from txt

i was toying around with cmd a bit and wanted to write a little application which involves a simple feature to read a counter from a txt file, then work with it and at the end raise the counter by one.
set /p x=<file.txt
...
set /a y=x+1
echo %y%>file.txt
Problem is it always returns "ECHO ist eingeschaltet (ON)." which translates to ECHO is turned on (ON) for some reason. Could somebody please explain where it comes from and how to fix it? I dont need anything fancy. I just want it to work and know where my mistake is.
At first, I want to show you how your echo command line should look like:
> "file.txt" echo(%y%
Here is your original line of code again:
echo %y%>file.txt
The reason for the unexpected output ECHO is on./ECHO is off. is because the echo command does not receive anything to echo (type echo /? and read the help text to learn what on/off means). Supposing y carries the value 2, the line expands to:
echo 2>file.txt
The number 2 here is not taken to be echoed here, it is consumed by the redirection instead; according to the article Redirection, 2> constitutes a redirection operator, telling to redirect the stream with the handle 2 (STDERR) to the given file. Such a handle can reach from 0 to 9.
There are some options to overcome that problem:
inserting a SPACE in between the echoed text and the redirection operator:
echo %y% >file.txt
the disadvantage is that the SPACE becomes part of the echoed text;
placing parentheses around the echo command:
(echo %y%)>file.txt
placing the redirection part at the beginning of the command line:
>file.txt echo %y%
I prefer the last option as this is the most general and secure solution. In addition, there is still room for improvement:
quote the file path/name to avoid trouble in case it contains white-spaces or other special characters;
use the odd syntax echo( to be able to output everything, even an empty string or literal strings like on, off and /?;
> "file.txt" echo(%y%
Hint:
To see what is actually going on, do not run a batch file by double-clicking on its icon; open a command prompt window and type its (quoted) path, so the window will remain open, showing any command echoes and error messages. In addition, for debugging a batch file, do not put #echo off on top (or comment it out by preceding rem, or use #echo on) in order to see command echoes.
Echo on means that everything that is executed in the batch is also shown in the console. So you see the command and on the following line the result.
You can turn this off with the echo off command or by preceding a # sign before the command you want to hide.
so
::turns of the echo for the remainder of the batch or untill put back on
::the command itself is not shwn because off the #
#echo off
set /p x=<file.txt
...
::the following won't be shown regardless the setting of echo
#set /a y = x+1
echo %y% > file.txt
EDIT after first comment
because your command echo %y%>file.txt doesn't work, you need a space before the > symbol, now you get the result of echo which gives you the current setting of echo
here a working sample, I put everything in one variable for sake of simplicity.
echo off
set /p x =< file.txt
set /a x += 1
echo %x% > file.txt

How to avoid cmd.exe interpreting shell special characters like < > ^

I have a Windows CMD script that accepts a number of parameters and executes an EXE, passing first some hard-coded arguments and then all of the parameters from the user. The CMD script looks like this:
launcher.exe paramX paramY %*
The user would execute the CMD script from the Windows shell as follows:
launcher.cmd param1 param2 param3 [...]
The problem I have is that if the parameters to the CMD script contain shell special characters like < > and ^, the user is forced to escape these by preceding each with 3 caret ^ shell escape characters.
Two Examples
1) To pass the argument ten>one to the EXE, the user must launch the CMD as follows:
launcher.cmd ten^^^>one
The reason for this is that the shell special characters ^ and > are interpreted by the command shell at two levels, first on the command line and second inside the CMD script. So, the shell escaping with the caret ^ shell escape character must be applied twice. The problem is that this is non-obvious to the user and looks ugly.
For this example, a nicer solution is to surround the argument with double quotes. However, this breaks down for more complex examples that include a literal double quote in the argument.
2) To pass the argument "^ to the EXE, the user must launch the CMD as follows:
launcher.cmd "\"^^^^"
In my case I want to support arguments that contain any sequence of low ASCII characters, excluding control characters, i.e. code points 0x20 to 0x7E. I understand that there will be examples where the user will have to escape certain shell special characters with a caret. However, I don't want the user to have to use 3 carets every time in these cases just because they happen to be calling a CMD script instead of an EXE.
I can solve this problem by replacing the CMD script with an EXE that does the same. However, is there any way to alter the CMD script so that it passes its parameters through to the EXE without interpreting the shell special characters?
One way is to work with delayed expansion inside of the batch, because then the special characters lose there "special" meanings.
The only problem is to get the parameters into a variable.
Something like this could help
#echo off
setlocal DisableDelayedExpansion
rem ** At this point the delayedExpansion should be disabled
rem ** otherwise an exclamation mark in %1 can remove carets
set "param1=%~1"
setlocal EnableDelayedExpansion
rem ** Now you can use the param1, independent of the content, even with carets or quotes
rem ** but be careful with call's, because they start a second round of expansion
echo !param1!
set "tmp=!param1:~1,4!"
Now the parameters can be surround by quotation marks, so there the carets aren't neccessary anymore.
Example
launcher.bat "abc>def&geh%ijk|lmn^opq!"
The only remaining problematic special character seems to be the quotation mark.
[Edit/Improve]
I create another way to retrieve a parameter, I assume it can accept any string also your second example.
Even really hard strings like
launcher "^
launcher ten^>one
launcher "&"^&
#echo off
setlocal DisableDelayedExpansion
set "prompt=X"
for %%a in (1 ) do (
#echo on
for %%b in (4) do (
rem #%1#
)
) > XY.txt
#echo off
for /F "delims=" %%a in (xy.txt) DO (
set "param=%%a"
)
setlocal EnableDelayedExpansion
set param=!param:~7,-4!
echo param='!param!'
How it works?
The only way I have found to expand %1 without expanding the special characters like " or ^ is in a REM statement (For REM that's not completly true, but that is an other story)
Ok, the only problem is that a REM is a remark and has no effect :-)
But if you use echo on also rem lines are echoed before they are executed (execute for rem is a nice word).
The next problem is that it is displayed and you can not redirect this debug output with the normal > debug.txt.
This is also true if you use a for-loop.
Ok, you can redirect the echo on output with a call like
echo on
call :myFunc > debug.txt
But if you call a function you can't access the %1 of the batch file anymore.
But with a double for-loop, it is possible to activate the redirection for the debug output and it's still possible to access %1.
I change the prompt to "X", so I know it is always only one character long.
The only thing left is to explain why I append a # to %1.
That's because, some special characters are recognized in some situations even in a REM line, obviously ;-)
rem This is a remark^
rem This_is_a_multiline^
rem "This is also a multiline"^
So the # suppress a possible multiline situation.
Does this help:
EscapPipes.Cmd:
#echo off
:Start
If [%1]==[] goto :eof
#Echo %1
shift
goto :Start
When started thus:
EscapPipes.Cmd Andy Pandy "Pudding | > < and pie"
gives
Andy
Pandy
"Pudding | > < and pie"
As soon as you strip the quotes the pipe symbols will become live.

Resources