Batch scripting: Why does this print "= instead of the empty string? - windows

I'm trying to remove quotes in some text using parameter expansion in Batch. Can anyone tell me why this:
#echo off
setlocal
set args=%*
echo %args:"=%
prints "= instead of nothing? As far as I can see %args:"=% should replace all quotes with nothing, so I don't get why this is happening.
Any help would be appreciated, thanks!
edit: To clarify, I'm not passing any parameters to the batch script.

That is the result you get when you do not pass any arguments to your script.
If args is not defined, then %args:"=% is expanded as follows:
%args: is treated as a non existent variable expansion, which becomes nothing
"= is treated as itself
% (a lone percent) is stripped
It is not intuitive, but that happens to be how cmd.exe works. See https://stackoverflow.com/a/7970912/1012053 for more info.
You can prevent the problem by using if defined
#echo off
setlocal
set args=%*
if defined args echo %args:"=%

It works here
C:\Windows\system32>"C:\Users\User\Desktop\Test2.bat" dog
dog
Removing echo off shows this
C:\Windows\system32>setlocal
C:\Windows\system32>set args=dog
C:\Windows\system32>echo dog
dog
You are not passing any command line arguments.
C:\Windows\system32>"C:\Users\User\Desktop\Test2.bat"
C:\Windows\system32>setlocal
C:\Windows\system32>set args=
C:\Windows\system32>echo "=
"=

Related

How to assign a command line parameter which contains ! and & to a variable?

Please consider the following very simple batch script (the file is named test.cmd):
#echo off
set "var1=%~1"
echo %var1%
The script should be called with one command line parameter, should assign the string which is contained in that parameter to a variable, and should output the variable.
As expected, I get an error message when I call this script with a command line parameter which contains an ampersand (&):
C:\Batch>test "a&b"
a
'b' is not recognized as an internal or external command,
operable program or batch file.
The reason for this has been discussed in some other questions here and elsewhere, for example that one; the usual remedy is to use delayed expansion. So I changed the script accordingly:
#echo off
setLocal enableDelayedExpansion
set "var1=%~1"
echo !var1!
Now it works with the parameter from before:
C:\Batch>test "a&b"
a&b
But there is a new problem. When the command line parameter contains an exclamation mark (!), it will be dropped from the output:
C:\Batch>test a!b
ab
This behavior also has been discussed at several places, for example here; the crucial thing to note is that dropping the exclamation mark happens during the assignment, not during the echo.
Despite a lot of research, I did not find a question here which provided an elegant solution for both problems at once. That is, is there an elegant way to assign a command line parameter to a variable when that parameter contains an ampersand AND an exclamation mark?
It seems that I need the delayed expansion to treat the ampersand correctly, but this destroys the exclamation mark.
The only solution I currently see is to not use delayed expansion and to add code to explicitly quote all ampersands in the input string. This would be so ugly that I seriously think that I am missing something here.
As a side note, the reason for the problem actually seems to be that there (IMHO!) is no way to get the command line parameter in a delayed-expanded fashion. The syntax for the first parameter is %~1, there is no such thing as !~1.
Move the setLocal enableDelayedExpansion after the the set„ that's all.
#echo off
set "var1=%~1"
setLocal enableDelayedExpansion
echo !var1!

How to pass escaped double quote to batch file

I know there were similar questions, but there is one thing I can't find in the answers. I'm trying to pass the following string to batch file:
hello " world
This is a single argument. Here is my batch file:
#echo off
#echo %1
Now if I run it from command line, I'm getting the following results:
>C:\file.bat "hello "" world"
"hello "" world"
>C:\file.bat "hello \" world"
"hello \"
>C:\file.bat "hello """ world"
"hello """
In all cases I'm getting the wrong result, how do I escape a double quote and pass it correctly? Or should I do any additional conversion steps in the batch file itself?
It is impossible to pass a string literal value of hello " world as a batch parameter for two reasons:
Space cannot be escaped with regard to parameter tokenization. Parameters are always delimited by unquoted token delimiters, even if they are preceded by the ^ escape character. The token parameter token delimiters are {space}, {tab}, ,, ; =, and {0xFF}. The only way to include a token delimiter as part of a parameter is to make sure the delimiter is quoted.
It is impossible to escape a quote within a quoted string. Quotes are a state machine: The first encountered quote turns on quote semantics, and the subsequent one turns it off. The next quote turns it back on again, etc. If quoting is off, then a quote literal can be escaped as ^" to keep quoting off. But once quoting has begun, then there is no way to escape a closing
quote: The next quote always turns quoting off.
So no matter how you add quotes and/or escaped quotes, there will always be an unquoted space, thus the string will be treated as two parameters.
You could adopt the strategy recommended by Hapax - Quote the entire string, and double any quote literals within the string: "Hello "" world".
However I would make a slight change and use %~1 instead of %1 so as to remove the outer quotes. Then the doubled quotes should be converted back to a single quote:
#echo off
set "arg1=%~1"
set "arg1=%arg1:""="%"
echo %arg1%
But there are other potential issues with passing string literals as batch parameters. The most insidious is caret doubling. It is impossible to pass a quoted caret as a parameter if the caller uses CALL. I won't get into the mechanism behind the problem. See https://stackoverflow.com/a/4095133/1012053 if you want more information. But the following illustrates the problem:
test.bat
#echo %1
-- Sample cmd session --
D:\test>test "^"
"^"
D:\test>call test "^"
"^^"
Because of the many complications with passing string literals as batch parameters, the most effective strategy used in advanced batch scripting is to store the value in an environment variable, and then pass the variable name as the parameter. The batch script can then use delayed expansion to get the correct value:
test2.bat
#echo off
setlocal enableDelayedExpansion
set "arg1=!%1!"
echo !arg1!
-- Sample cmd session --
D:\test>set "myVar=Hello world! ^&|<>" How are you doing? !^^^&^|^<^>^"
D:\test>set myVar
myVar=Hello world! ^&|<>" How are you doing? !^&|<>
D:\test>test2 myVar
Hello world! ^&|<>" How are you doing? !^&|<>
D:\test>call test2 myVar
Hello world! ^&|<>" How are you doing? !^&|<>
The way to keep quotes is to pass them doubled. However, this passes both of them.
You can then process the input and remove the doubles.
Executing using file.bat "hello "" world", we use:
#echo off
set param=%1
set param=%param:""="%
echo %param%
(result: "hello " world")
A shorter version:
#echo off
set param=%1
echo %param:""="%

& sign inside batch file parameter to be passed for another program

I have a batch file that I call with something like this
call do.cmd "one two"
In do.cmd I am launching a program and pass to it first parameter from above:
#echo off
some_program.exe -name='%1'
The value to some_program.exe for name variable must be passed inside single quotes without surrounding double quotes. To get rid of double quotes in passed parameter I make a temporary variable like this:
set v_tmp=%1
set v_tmp=%v_artist:"=%
And then launch my program by
some_programm.exe -name='%v_tmp%'
The problem start when with do.cmd some text having & sign is passed. If I leave batch file code as is, variaable setting will fail because & will act as a divider. If I escape & sign by
set v_tmp=%1
set v_tmp=%v_tmp:&"=^^^&%
set v_tmp=%v_tmp:"=%
then some_program will output text having ^&..
The question is how do I get from call do.cmd "one & two" line to the correct & sign escaping and double quote removal so that to have in result some_program.exe -name='one & two'?
You could try to use delayed expansion here.
#echo off
set "arg1=%~1"
setlocal EnableDelayedExpansion
some_program.exe -name='!arg1!'
This works, as %~1 removes enclosing quotes, if present.
And !arg1! always expands the variable in a safe manner.

Problem with quoted filenames in Batch

Let I have a batch program:
SET FOO=C:\temp\%1
bar.exe %FOO%
When I call it with double quoted file name as an argument I get these quotes in the middle; and that fact prevents other programs from working correctly:
> fail.bat "aa bb.jpg"
SET FOO=C:\temp\"aa bb.jpg"
> bar.exe C:\temp\"aa bb.jpg"
cannot find file
How to get variable containing correct value "C:\temp\aa bb.jpg"?
You can use %~1 instead, this removes the quotes from the parameter.
Then your code should look like
SET FOO="C:\temp\%~1"
bar.exe %FOO%
Try removing the drive letter as I have had issues with that in the past. Also does it work if the entire path name is in quotes not just the single item with spaces?

How can I echo a newline in a batch file?

How can you you insert a newline from your batch file output?
I want to do something like:
echo hello\nworld
Which would output:
hello
world
Use:
echo hello
echo:
echo world
echo hello & echo.world
This means you could define & echo. as a constant for a newline \n.
Here you go, create a .bat file with the following in it :
#echo off
REM Creating a Newline variable (the two blank lines are required!)
set NLM=^
set NL=^^^%NLM%%NLM%^%NLM%%NLM%
REM Example Usage:
echo There should be a newline%NL%inserted here.
echo.
pause
You should see output like the following:
There should be a newline
inserted here.
Press any key to continue . . .
You only need the code between the REM statements, obviously.
There is a standard feature echo: in cmd/bat-files to write blank line, which emulates a new line in your cmd-output:
#echo off
echo line1
echo:
echo line2
or
#echo line1 & echo: & echo line2
Output of cited above cmd-file:
line1
line2
Like the answer of Ken, but with the use of the delayed expansion.
setlocal EnableDelayedExpansion
(set \n=^
%=Do not remove this line=%
)
echo Line1!\n!Line2
echo Works also with quotes "!\n!line2"
First a single linefeed character is created and assigned to the \n-variable.
This works as the caret at the line end tries to escape the next character, but if this is a Linefeed it is ignored and the next character is read and escaped (even if this is also a linefeed).
Then you need a third linefeed to end the current instruction, else the third line would be appended to the LF-variable.
Even batch files have line endings with CR/LF only the LF are important, as the CR's are removed in this phase of the parser.
The advantage of using the delayed expansion is, that there is no special character handling at all.
echo Line1%LF%Line2 would fail, as the parser stops parsing at single linefeeds.
More explanations are at
SO:Long commands split over multiple lines in Vista/DOS batch (.bat) file
SO:How does the Windows Command Interpreter (CMD.EXE) parse scripts?
Edit: Avoid echo.
This doesn't answer the question, as the question was about single echo that can output multiple lines.
But despite the other answers who suggests the use of echo. to create a new line, it should be noted that echo. is the worst, as it's very slow and it can completly fail, as cmd.exe searches for a file named ECHO and try to start it.
For printing just an empty line, you could use one of
echo,
echo;
echo(
echo/
echo+
echo=
But the use of echo., echo\ or echo: should be avoided, as they can be really slow, depending of the location where the script will be executed, like a network drive.
echo. Enough said.
If you need it in a single line, use the &. For example,
echo Line 1 & echo. & echo line 3
would output as:
Line 1
line 3
Now, say you want something a bit fancier, ...
set n=^&echo.
echo hello %n% world
Outputs
hello
world
Then just throw in a %n% whenever you want a new line in an echo statement. This is more close to your \n used in various languages.
Breakdown
set n= sets the variable n equal to:
^ Nulls out the next symbol to follow:
& Means to do another command on the same line. We don't care about errorlevel(its an echo statement for crying out loud), so no && is needed.
echo. Continues the echo statement.
All of this works because you can actually create variables that are code, and use them inside of other commands. It is sort of like a ghetto function, since batch is not exactly the most advanced of shell scripting languages. This only works because batch's poor usage of variables, not designating between ints, chars, floats, strings, etc naturally.
If you are crafty, you could get this to work with other things. For example, using it to echo a tab
set t=^&echo. ::there are spaces up to the double colon
When echoing something to redirect to a file, multiple echo commands will not work. I think maybe the ">>" redirector is a good choice:
echo hello > temp
echo world >> temp
If you need to put results to a file, you can use:
(echo a & echo: & echo b) > file_containing_multiple_lines.txt
Just like Grimtron suggests - here is a quick example to define it:
#echo off
set newline=^& echo.
echo hello %newline%world
Output
C:\>test.bat
hello
world
You can also do like this,
(for %i in (a b "c d") do #echo %~i)
The output will be,
a
b
c d
Note that when this is put in a batch file, '%' shall be doubled.
(for %%i in (a b "c d") do #echo %%~i)
If anybody comes here because they are looking to echo a blank line from a MINGW make makefile, I used
#cmd /c echo.
simply using echo. causes the dreaded process_begin: CreateProcess(NULL, echo., ...) failed. error message.
I hope this helps at least one other person out there :)
Ken and Jeb solutions works well.
But the new lines are generated with only an LF character and I need CRLF characters (Windows version).
To this, at the end of the script, I have converted LF to CRLF.
Example:
TYPE file.txt | FIND "" /V > file_win.txt
del file.txt
rename file_win.txt file.txt
If one needs to use famous \n in string literals that can be passed to a variable, may write a code like in the Hello.bat script below:
#echo off
set input=%1
if defined input (
set answer=Hi!\nWhy did you call me a %input%?
) else (
set answer=Hi!\nHow are you?\nWe are friends, you know?\nYou can call me by name.
)
setlocal enableDelayedExpansion
set newline=^
rem Two empty lines above are essential
echo %answer:\n=!newline!%
This way multiline output may by prepared in one place, even in other scritpt or external file, and printed in another.
The line break is held in newline variable. Its value must be substituted after the echo line is expanded so I use setlocal enableDelayedExpansion to enable exclamation signs which expand variables on execution. And the execution substitutes \n with newline contents (look for syntax at help set). We could of course use !newline! while setting the answer but \n is more convenient. It may be passed from outside (try Hello R2\nD2), where nobody knows the name of variable holding the line break (Yes, Hello C3!newline!P0 works the same way).
Above example may be refined to a subroutine or standalone batch, used like call:mlecho Hi\nI'm your comuter:
:mlecho
setlocal enableDelayedExpansion
set text=%*
set nl=^
echo %text:\n=!nl!%
goto:eof
Please note, that additional backslash won't prevent the script from parsing \n substring.
After a sleepless night and after reading all answers herein, after reading a lot of SS64 > CMD and after a lot of try & error I found:
The (almost) Ultimate Solution
TL;DR
... for early adopters.
Important!
Use a text editor for C&P that supports Unicode, e.g. Notepad++!
Set Newline Environment Variable ...
... in the Current CMD Session
Important!
Do not edit anything between '=' and '^'! (There's a character in between though you don't see it. Neither here nor in edit mode. C&P works here.)
:: Sets newline variables in the current CMD session
set \n=​^&echo:
set nl=​^&echo:
... for the Current User
Important!
Do not edit anything between (the second) '␣' and '^'! (There's a character in between though you don't see it. Neither here nor in edit mode. C&P works here.)
:: Sets newline variables for the current user [HKEY_CURRENT_USER\Environment]
setx \n ​^&echo:
setx nl ​^&echo:
... for the Local Machine
Important!
Do not edit anything between (the second) '␣' and '^'! (There's a character in between though you don't see it. Neither here nor in edit mode. C&P works here.)
:: Sets newline variables for the local machine [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment]
setx \n ​^&echo: /m
setx nl ​^&echo: /m
Why just almost?
It does not work with double-quotes that are not paired (opened and closed) in the same printed line, except if the only unpaired double-quote is the last character of the text, e.g.:
works: ""echo %\n%...after "newline". Before "newline"...%\n%...after "newline" (paired in each printed line)
works: echo %\n%...after newline. Before newline...%\n%...after newline" (the only unpaired double-quote is the last character)
doesn't work: echo "%\n%...after newline. Before newline...%\n%...after newline" (double-quotes are not paired in the same printed line)
Workaround for completely double-quoted texts (inspired by Windows batch: echo without new line):
set BEGIN_QUOTE=echo ^| set /p !="""
...
%BEGIN_QUOTE%
echo %\n%...after newline. Before newline...%\n%...after newline"
It works with completely single-quoted texts like:
echo '%\n%...after newline. Before newline...%\n%...after newline'
Added value: Escape Character
Note
There's a character after the '=' but you don't see it here but in edit mode. C&P works here.
:: Escape character - useful for color codes when 'echo'ing
:: See https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#text-formatting
set ESC=
For the colors see also https://imgur.com/a/EuNXEar and https://gist.github.com/gerib/f2562474e7ca0d3cda600366ee4b8a45.
2nd added value: Getting Unicode characters easily
A great page for getting 87,461 Unicode characters (AToW) by keyword(s): https://www.amp-what.com/.
The Reasons
The version in Ken's answer works apparently (I didn't try it), but is somehow...well...you see:
set NLM=^
set NL=^^^%NLM%%NLM%^%NLM%%NLM%
The version derived from user2605194's and user287293's answer (without anything between '=' and '^'):
set nl=^&echo:
set \n=^&echo:
works partly but fails with the variable at the beginning of the line to be echoed:
> echo %\n%Hello%\n%World!
echo & echo:Hello & echo:World!
echo is ON.
Hello
World
due to the blank argument to the first echo.
All others are more or less invoking three echos explicitely.
I like short one-liners.
The Story Behind
To prevent set \n=^&echo: suggested in answers herein echoing blank (and such printing its status) I first remembered the Alt+255 user from the times when Novell was a widely used network and code pages like 437 and 850 were used. But 0d255/0xFF is ›Ÿ‹ (Latin Small Letter Y with diaeresis) in Unicode nowadays.
Then I remembered that there are more spaces in Unicode than the ordinary 0d32/0x20 but all of them are considered whitespaces and lead to the same behaviour as ›␣‹.
But there are even more: the zero width spaces and joiners which are not considered as whitespaces. The problem with them is, that you cannot C&P them since with their zero width there's nothing to select. So, I copied one that is close to one of them, the hair space (U+200A) which is right before the zero width space (U+200B) into Notepad++, opened its Hex-Editor plugin, found its bit representation E2 80 8A and changed it to E2 80 8B. Success! I had a non-whitespace character that's not visible in my \n environment variable.
To start a new line in batch, all you have to do is add "echo[", like so:
echo Hi!
echo[
echo Hello!
why not use substring/replace space to echo;?
set "_line=hello world"
echo\%_line: =&echo;%
Results:
hello
world
Or, replace \n to echo;
set "_line=hello\nworld"
echo\%_line:\n=&echo;%
For windows 10 with virtual terminal sequences there exists the means control the cursor position to a high degree.
To define the escape sequence 0x1b, the following can be used:
#Echo off
For /f %%a in ('echo prompt $E^| cmd')Do set \E=%%a
To output a single newline Between Strings:
<nul set /p "=Hello%\E%[EWorld"
To output n newlines where n is replaced with an integer:
<nul set /p "=%\E%[nE"
Many
Please note that all solutions that use cursor positioning according to Console Virtual Terminal Sequences, Cursor Positioning with:
Sequence
Code
Description
Behaviour
ESC [ <n> E
CNL
Cursor Next Line
Cursor down <n> lines from current position
only work as long as the bottom of the console window is not reached.
At the bottom there is no space left to move the cursor down so it just moves left (with the CR of CRLF) and the line printed before is overwritten from its beginning.
To echo a newline, add a dot . right after the echo:
echo.
This worked for me, no delayed expansion necessary:
#echo off
(
echo ^<html^>
echo ^<body^>
echo Hello
echo ^</body^>
echo ^</html^>
)
pause
It writes output like this:
<html>
<body>
Hello
</body>
</html>
Press any key to continue . . .
You can use #echo ( #echo + [space] + [insecable space] )
Note: The insecable space can be obtained with Alt+0160
Hope it helps :)
[edit] Hmm you're right, I needed it in a Makefile, it works perfectly in there. I guess my answer is not adapted for batch files... My bad.
simple
set nl=.
echo hello
echo%nl%
REM without space ^^^
echo World
Result:
hello
world
Be aware, this won't work in console because it'll simulate an escape key and clear the line.
Using this code, replace <ESC> with the 0x1b escape character or use this Pastebin link:
:: Replace <ESC> with the 0x1b escape character or copy from this Pastebin:
:: https://pastebin.com/xLWKTQZQ
echo Hello<ESC>[Eworld!
:: OR
set "\n=<ESC>[E"
echo Hello%\n%world!
Adding a variant to Ken's answer, that shows setting values for environment variables with new lines in them.
We use this method to append error conditions to a string in a VAR, then at the end of all the error checking output to a file as a summary of all the errors.
This is not complete code, just an example.
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
:: the two blank lines are required!
set NLM=^
set NL=^^^%NLM%%NLM%^%NLM%%NLM%
:: Example Usage:
Set ErrMsg=Start Reporting:
:: some logic here finds an error condition and appends the error report
set ErrMsg=!ErrMsg!!NL!Error Title1!NL!Description!NL!Summary!NL!
:: some logic here finds another error condition and appends the error report
set ErrMsg=!ErrMsg!!NL!Error Title2!NL!Description!NL!Summary!NL!
:: some logic here finds another error condition and appends the error report
set ErrMsg=!ErrMsg!!NL!Error Title3!NL!Description!NL!Summary!NL!
echo %ErrMsg%
pause
echo %ErrMsg% > MyLogFile.log
Log and Screen output look like this...

Resources