Batch scripting: Why does this condition evaluate to true? - windows

Create a Batch file with the following contents:
#echo off
setlocal
echo %1
if [%1] == [] echo hi
Then run it from the command prompt like so:
script.cmd ==
It will output the following:
ECHO is off.
hi
Why is this, and how does it happen? Also, how can I more reliably check for empty strings in my batch scripts?
Thanks.

In this case, I suggest using the following:
#echo off
setlocal
echo(%~1
if "%~1"=="" echo hi
And instead of script.cmd == use script.cmd "=="
Note that I used echo( which will echo a newline if the variable is seen as empty, and that I used %~1 which removes surrounding quotes from the %1 argument.
You need the surrounding quotes because an equal-sign is treated as a delimeter in the arguments, unless inside quotes, just like spaces.

#ECHO OFF
SETLOCAL
SET "cmdtail=%*"
ECHO(%cmdtail%
IF "%cmdtail%"=="==" ECHO two "="
But back to the unstated original problem.
Note that this syntax will detect the == provided as the command tail whereas %~1 will not evaluate to ==.

Related

Trying to concatenate the last 10 lines of a log file to a batch variable using powershell

I'm new to Windows scripting, but have quite a lot of experience in bash and python.
Here's the issue. Whenever I run this, (and this is the best result I've gotten so far) it makes it most of the way through and then errors with "The filename, directory name, or volume label syntax is incorrect."
Ignore the code designed for newlines, I'm still fighting with that as well.
setlocal EnableDelayedExpansion
set LF=^
set LAST_TEN=Here are the last 10 lines of the download log:
for /f "tokens=* usebackq" %%x in (`powershell -command "& {Get-Content download.log | Select-Object -last 10 | ForEach-Object {$_.substring(2)}}"`) do (
set LAST_TEN=!LAST_TEN!%%x
)
echo %LAST_TEN%
The reason I'm taking the substring is because some of the lines in the logfile start with < and > . I thought that was my only issue, but that is not the case. Please let me know if any more info is needed. Thank you!
Note: Your own answer shows the effective solution, but I thought I'd provide some background information.
Squashman has provided the crucial pointer:
Switching from echo %LAST_TEN% to echo !LAST_TEN! avoids problems with metacharacters (special characters such as < and >) in the variable value, which are what caused your error message.
The alternative would be to double-quote the variable reference - echo "%LAST_TEN%" - but, sadly, the double quotes are then included in the output.
In other words: If you need to echo the value of a variable that (potentially) contains metacharacters unquoted:
Place setlocal EnableDelayedExpansion at the start of your batch file.
Then reference the variable of interest as !VAR! instead of %VAR%: the delayed expansion this results in prevents the value from becoming part of the source-code line that cmd.exe parses (due to the macro-style up-front expansion that happens with %VAR%).
As an aside: Loop variables - such as %%x in your code - despite using % rather than ! as the delimiter, are of necessity always expanded in a delayed fashion, which is the reason that set LAST_TEN=!LAST_TEN!%%x worked even without the double-quoting around enclosing both the variable name and value that is normally required for literals and values of non-delayed variable references containing metacharacters (e.g.
set "LAST_TEN=a < b")
A simplified example:
#echo off
setlocal enableDelayedExpansion
:: Define a sample variable
:: Note the "..." enclosing both the name and the value.
set "var=a value with metacharacters: < > & |"
:: Thanks to using !var!, echoing the value *unquoted* works
echo !var!
Scoping setlocal enableDelayedExpansion:
One pitfall of delayed expansion is that that all ! characters are then considered part of delayed variable references, typically resulting in their quiet removal; e.g., echo hi! outputs just hi.
To escape ! characters in literal strings that should be used verbatim, you need ^^! (sic) in unquoted strings, and ^! inside "...".
The escaping is also needed for %...% variable references (e.g., echo %var:!=^^!%), but is again avoided for !...! ones.
To avoid such escaping headaches you can enable setlocal enableDelayedExpansion on demand, for a given line or block of lines, and disable it again with endlocal:
#echo off
:: Define a sample variable
:: Note the "..." enclosing both the name and the value.
set "var=a value with metacharacters: < > & |"
:: Because setlocal enableDelayedExpansion is NOT (yet)
:: in effect, the use of "!" is not a problem.
echo hi!
:: Localize the use of delayed expansion
setlocal enableDelayedExpansion
echo !var!
endlocal
:: Use of "!" is again fine.
echo hi again!
Caveat: Since setlocal creates a copy of the environment variables, which endlocal then discards, do not try to set variables between setlocal and endlocal if you need later code to see these changes.
As you're already using PowerShell, why not let it do the donkey work?
Grab the last ten lines and concatenate them within parentheses, for example:
For /F Delims^=^ EOL^= %%G In ('%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -Command "(Get-Content 'download.log' | Select-Object -Last 10) -Join ''"') Do Set "LAST_TEN=%%G"
Changed
echo %LAST_TEN%
to
echo !LAST_TEN!

Batch character escape on ^| don't work

^| Should be escape for the | character.
set good=Debug^|Win32
echo Incorrect paramets %good%
pause > nul
Why this give me error?
You have two ways to insert special characters in a variable. Escaping them:
set good=Debug^|Win32
or enclosing them between quotes:
set "good=Debug|Win32"
However, the only way to correctly show a variable value that contain special characters is using delayed expansion:
setlocal EnableDelayedExpansion
set good=Debug^|Win32
echo Incorrect paramets !good!
pause > nul
I try to add some explanations to the main problem.
As James L. noted, you have the expected content in your variable, but it's problematic to echo it.
echo Incorrect parameters %good:|=^|%
It's a problem of the parser.
After a percent expansion special characters will be parsed, if they aren't escaped by a caret or quote.
The way of James L. only works if there are only pipes, but no other special characters in the variable, as you can't replace two different characters this way.
But you could escape all special characters twice, like:
set good=Debug^^^|Win32^^^&Ampersand
echo Incorrect parameters %good%
or the same
set "good=Debug^|Win32^&Ampersand"
echo Incorrect parameters %good%
This works, but the content of good is Debug^|Win32^&Ampersand which is in the most cases not wanted.
The way of Aacini, to use delayed expansion works always with any content, as after the delayed expansion the content will not further be parsed, so the special characters are harmless.
This is one of the major advantages of the delayed expansion about percent expansion.
There is another way to use quotes like the solution of Axel Kemper.
echo "Incorrect parameters %good%"
But in the most cases the quotes shouldn't be printed.
Try using a label.
In batch, labels are preceded by the colon.
:ok
is a label.
It is not necessary to usu the colon in a goto
goto ok
goto :ok
are equivalent.
You properly escaped the | character by set good=Debut^|Win32. When you echo it, you have to replace the | character with the escape sequence, like this:
#echo off
set good=Debug^|Win32
echo Incorrect paramets %good:|=^|%
pause > nul
This makes it echo the escaped pipe (^\) instead of piping it to Win32.
use set/p for output:
#echo off&setlocal
set "good=Debug|Win32"
<nul set/p"=Incorrect parameters %good%"
echo(
output is:
Incorrect parameters Debug|Win32
The following works as intended:
set good=Debug^|Win32
echo "Incorrect parameters %good%"
pause > nul
If you omit the quotes in the echo statement, Windows tries to execute "Win32" which is impossible.
I would recommend:
echo Incorrect paramets Debug^|Win32
pause > nul

Why does this batch variable never change even when set?

#echo off
SET first=0
FOR %%N IN (hello bye) DO (
SET first=1
echo %first%
echo %%N
)
It seems that the variable "first" is always 0. Why?
With batch files, variables are expanded when their command is read - so that would be as soon as the for executes. At that point, it no longer says echo %first%, it literally says echo 0, because that was the value at the point of expansion.
To get around that, you need to use delayed expansion by surrounding your variable name with ! instead of % - so that would be echo !first!. This may require you to start cmd.exe with the /V parameter, or use setlocal enabledelayedexpansion in the beginning of your batch file (just after echo off).
If you type set /?, you'll see a much more detailed explanation of this at the end of the output.

Remove quotes from named environment variables in Windows scripts

I want to store a URL prefix in an Windows environment variable. The ampersands in the query string makes this troublesome though.
For example: I have a URL prefix of http://example.com?foo=1&bar= and want to create a full URL by providing a value for the bar parameter. I then want to launch that URL using the "start" command.
Adding quotes around the value for the SET operation is easy enough:
set myvar="http://example.com?foo=1&bar="
Windows includes the quotes in the actual value though (thanks Windows!):
echo %myvar%
"http://example.com?foo=1&bar=true"
I know that I can strip quotes away from batch file arguments by using tilde:
echo %~1
However, I can't seem to do it to named variables:
echo %~myvar%
%~myvar%
What's the syntax for accomplishing this?
echo %myvar:"=%
This is not a limitation of the environment variable, but rather the command shell.
Enclose the entire assignment in quotes:
set "myvar=http://example.com?foo=1&bar="
Though if you try to echo this, it will complain as the shell will see a break in there.
You can echo it by enclosing the var name in quotes:
echo "%myvar%"
Or better, just use the set command to view the contents:
set myvar
While there are several good answers already, another way to remove quotes is to use a simple subroutine:
:unquote
set %1=%~2
goto :EOF
Here's a complete usage example:
#echo off
setlocal ENABLEDELAYEDEXPANSION ENABLEEXTENSIONS
set words="Two words"
call :unquote words %words%
echo %words%
set quoted="Now is the time"
call :unquote unquoted %quoted%
echo %unquoted%
set word=NoQuoteTest
call :unquote word %word%
echo %word%
goto :EOF
:unquote
set %1=%~2
goto :EOF
This works
for %a in (%myvar%) do set myvar=%~a
I would also use this if I wanted to print a variable that contained and ampersand without the quotes.
for %a in ("fish & chips") do echo %~a
To remove only beginning and ending quotes from a variable:
SET myvar=###%myvar%###
SET myvar=%myvar:"###=%
SET myvar=%myvar:###"=%
SET myvar=%myvar:###=%
This assumes you don't have a ###" or "### inside your value, and does not work if the variable is NULL.
Credit goes to http://ss64.com/nt/syntax-esc.html for this method.
Use delayed environment variable expansion and use !var:~1,-1! to remove the quotes:
#echo off
setlocal enabledelayedexpansion
set myvar="http://example.com?foo=1&bar="
set myvarWithoutQuotes=!myvar:~1,-1!
echo !myvarWithoutQuotes!
Use multiple variables to do it:
set myvar="http://example.com?foo=1&bar="
set bar=true
set launch=%testvar:,-1%%bar%"
start iexplore %launch%
#echo off
set "myvar=http://example.com?foo=1&bar="
setlocal EnableDelayedExpansion
echo !myvar!
This is because the variable contains special shell characters.
I think this should do it:
for /f "tokens=*" %i in (%myvar%) do set %myvar%=%~i
But you do not need this,
set myvar="http://example.com?foo=1&bar="
start "" %myvar%
Will work too, you just need to supply a title to the start command.

Escape angle brackets in a Windows command prompt

I need to echo a string containing angle brackets (< and >) to a file on a Windows machine. Basically what I want to do is the following:
echo some string < with angle > brackets >>myfile.txt
This doesn't work since the command interpreter gets confused with the angle brackets. I could quote the whole string like this:
echo "some string < with angle > brackets" >>myfile.txt
But then I have double quotes in my file that I don't want.
Escaping the brackets ala unix doesn't work either:
echo some string \< with angle \> brackets >>myfile.txt
Ideas?
The Windows escape character is ^, for some reason.
echo some string ^< with angle ^> brackets >>myfile.txt
True, the official escape character is ^, but be careful because sometimes you need three ^ characters. This is just sometimes:
C:\WINDOWS> echo ^<html^>
<html>
C:\WINDOWS> echo ^<html^> | sort
The syntax of the command is incorrect.
C:\WINDOWS> echo ^^^<html^^^> | sort
<html>
C:\WINDOWS> echo ^^^<html^^^>
^<html^>
One trick out of this nonsense is to use a command other than echo to do the output and quote with double quotes:
C:\WINDOWS> set/p _="<html>" <nul
<html>
C:\WINDOWS> set/p _="<html>" <nul | sort
<html>
Note that this will not preserve leading spaces on the prompt text.
There are methods that avoid ^ escape sequences.
You could use variables with delayed expansion. Below is a small batch script demonstration
#echo off
setlocal enableDelayedExpansion
set "line=<html>"
echo !line!
Or you could use a FOR /F loop. From the command line:
for /f "delims=" %A in ("<html>") do #echo %~A
Or from a batch script:
#echo off
for /f "delims=" %%A in ("<html>") do echo %%~A
The reason these methods work is because both delayed expansion and FOR variable expansion occur after special operators like <, >, &, |, &&, || are parsed. See How does the Windows Command Interpreter (CMD.EXE) parse scripts? for more info.
sin3.14 points out that pipes may require multiple escapes. For example:
echo ^^^<html^^^>|findstr .
The reason pipes require multiple escapes is because each side of the pipe is executed in a new CMD process, so the line gets parsed multiple times. See Why does delayed expansion fail when inside a piped block of code? for an explanation of many awkward consequences of Window's pipe implementation.
There is another method to avoid multiple escapes when using pipes. You can explicitly instantiate your own CMD process, and protect the single escape with quotes:
cmd /c "echo ^<html^>"|findstr .
If you want to use the delayed expansion technique to avoid escapes, then there are even more surprises (You might not be surprised if you are an expert on the design of CMD.EXE, but there is no official MicroSoft documentation that explains this stuff)
Remember that each side of the pipe gets executed in its own CMD.EXE process, but the process does not inherit the delayed expansion state - it defaults to OFF. So you must explicitly instantiate your own CMD.EXE process and use the /V:ON option to enable delayed expansion.
#echo off
setlocal disableDelayedExpansion
set "line=<html>"
cmd /v:on /c echo !test!|findstr .
Note that delayed expansion is OFF in the parent batch script.
But all hell breaks loose if delayed expansion is enabled in the parent script. The following does not work:
#echo off
setlocal enableDelayedExpansion
set "line=<html>"
REM - the following command fails
cmd /v:on /c echo !test!|findstr .
The problem is that !test! is expanded in the parent script, so the new CMD process is trying to parse unprotected < and >.
You could escape the !, but that can get tricky, because it depends on whether the ! is quoted or not.
If not quoted, then double escape is required:
#echo off
setlocal enableDelayedExpansion
set "line=<html>"
cmd /v:on /c echo ^^!test^^!|findstr .
If quoted, then a single escape is used:
#echo off
setlocal enableDelayedExpansion
set "line=<html>"
cmd /v:on /c "echo ^!test^!"|findstr .
But there is a surprising trick that avoids all escapes - enclosing the left side of the pipe prevents the parent script from expanding !test! prematurely:
#echo off
setlocal enableDelayedExpansion
set "line=<html>"
(cmd /v:on /c echo !test!)|findstr .
But I suppose even that is not a free lunch, because the batch parser introduces an extra (perhaps unwanted) space at the end when parentheses are used.
Aint batch scripting fun ;-)
In order to use special characters, such as '>' on Windows with echo, you need to place a special escape character before it.
For instance
echo A->B
will no work since '>' has to be escaped by '^':
echo A-^>B
See also escape sequences.
There is a short batch file, which prints a basic set of special character and their escape sequences.
Escaping the brackets ala unix doesn't
work either:
echo some string \< with
angle \> brackets >>myfile.txt
The backslash would be considered the start of a absolute pathname.
You can also use double quotes to escape special characters...
echo some string "<" with angle ">" brackets >>myfile.txt

Resources