I wrote a bat file to generate a keystore via keytool.
My code is:
cd C:\Java\jdk-11.0.1\bin
set Pass=12345678
set RepetitionPass=test
set FLname=test
set OrganUnit=test
set Organ=test
set City=test
set State=test
set Country=US
set Pass=%Pass: =%
set RepetitionPass=%RepetitionPass: =%
set FLname=%FLname: =%
set OrganUnit=%OrganUnit: =%
set Organ=%Organ: =%
set City=%City: =%
set State=%State: =%
set Pass=%Pass: =%
set Country=%Country: =%
(echo.%Pass% && echo.%RepetitionPass% && echo.%FLname% && echo.%OrganUnit% && echo.%Organ% && echo.%City% && echo.%State% && echo.%Country% && echo y)| keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000
pause
I put this code inside Notepad and then changed its extension to .bat.
Everything works fine My only problem is that the end of a space password is automatically added. For example, if my password is "12345678", when the key is generated, its password will be "12345678 ".
For some reason, I do not want to use Android Studio to generate the key, and I must produce my Key Store this way.
where is the problem from? How do I solve this problem?
The Windows Command Prompt is rather sensitive to SPACEs, so when you provide such, they may have some effect. For instance: echo foo & echo bar will output a line foo + SPACE and a line bar (given that there is no "invisible" trailing SPACE, of course).
But the situation at hand is even more complicated: You have got a pipe (|) involved in your batch script, which will initiate a new cmd.exe instance for the left side since there is a parenthesised block1; this new instance receives the left code somehow rebuilt, so even more unwanted SPACEs become inserted. Returning to the previous example, (echo foo&echo bar) | more would return even both lines with a trailing SPACE each2, because the left command would become rebuilt as something like ( echo foo & echo bar ).
Even the following would still return a trailing SPACE per line:
(
echo foo
echo bar
) | more
since the command block on the left side would again become rebuilt to something like ( echo foo & echo bar ).
A possible solution is to escape the ampersand, so it becomes passed over to the new cmd.exe instance literally without modification:
(echo foo^& echo bar) | more
Obviously this only prevents the first line from being appended with a SPACE, but when we append another escaped ampersand plus a command that literally does nothing, like rem/3, the solution is complete:
(echo foo^& echo bar^& rem/) | more
The same can also be applied to the block approach:
(
echo foo^& rem/
echo bar^& rem/
) | more
which would eventually become translated to something like ( echo foo& rem/ & echo bar& rem/ ).
Now let us apply this to your code, together with another few changes:
The cd command requires the /D option in order to change the current drive as well, and quoting paths is generally a good practice. Also consider what to do when the path does not exist or cannot be accessed for some reason (conditional execution && or || may serve here).
You should generally prefer the quoted syntax of the set command, like set "Pass=12345678" instead of set Pass=12345678, in order to protect special characters and to avoid unwanted trailing SPACEs.
I entirely skipped the code block for removal of SPACEs, because I assume this was just a failed attempt to remove the unwanted SPACEs in the output (if you do want that block, you should use the quoted syntax like set "Pass=%Pass: =%" rather than set Pass=%Pass: =%).
Although widely used, echo. is a bad way of echoing a (potentially) empty line, because a file called echo. is actually searched before the internal command echo becomes executed. If, for whatever reason, such a file exists, it is attempted to be executed instead. Therefore, use the syntax echo(, which looks odd but is safe.
You do not need conditional execution to chain echo commands, an unconditional & operator is fine.
So here is the probably fixed code:
#echo off
cd /D "C:\Java\jdk-11.0.1\bin" || exit /B
set "Pass=12345678"
set "RepetitionPass=test"
set "FLname=test"
set "OrganUnit=test"
set "Organ=test"
set "City=test"
set "State=test"
set "Country=US"
(
echo(%Pass%^& rem/
echo(%RepetitionPass%^& rem/
echo(%FLname%^& rem/
echo(%OrganUnit%^& rem/
echo(%Organ%^& rem/
echo(%City%^& rem/
echo(%State%^& rem/
echo(%Country%^& rem/
echo y^& rem/
) | keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000
pause
1) For a detailed explanation refer to this post: How does the Windows Command Interpreter (CMD.EXE) parse scripts? (see phase 5.3)
2) Write the output into a file using output redirection (>) in order to prove it.
3) It has to be rem/ and not rem, because the latter would comment out the whole remainder of the command line.
Related
I have a batch that wraps AnyConnect Mobility Client CLI (vpncli.exe) and asks username and password to later handle them to vpncli.
Simplified code:
set /p user_id=Username:
set /p pwd=Password:
echo %user_id%> c:\temp\configvpn.txt
echo %pwd%>> c:\temp\configvpn.txt
set install_dir="C:\Program Files (x86)\Cisco\Cisco AnyConnect Secure Mobility Client"
%install_dir%\vpncli.exe connect myvpn.mydomain.TLD -s < c:\temp\configvpn.txt
net use h: \\fileserver\sharename /user:domain\%user_id% %pwd%
The last line it's why we do it this way: to not prompt user password twice (first for connecting VPN and second to map network drive)
For security reasons I'm improving the script to not write password to disk. I need a fileless equivalent of this "< c:\temp\configvpn.txt"
I tried :
(
#echo %user_id%
#echo %pwd%
) | %install_dir%\vpncli.exe connect myvpn.mydomain.TLD -s
Not success so far. The output is this loop:
>> Please enter your username and password.
Group: VPN-TESTGROUP
Username: [myUsername] Password:
>> Login failed.
Group: VPN-TESTGROUP
Username: [myUsername] Password:
>> Login failed.
(repeats indefinitely)
Is there a way to do this?
It's probably a problem of the piped block, as there are spaces added at the end of each command.
This code avoids the spaces and should solve your problem
(SET LF=^
%=empty=%
)
(
echo %user_id%%%LF%%rem.
echo %pwd%%%LF%%rem.
) | %install_dir%\vpncli.exe connect myvpn.mydomain.TLD -s
But you should test it without any special characters in you user_id or password!
As you already find out, the problems is trailing spaces inside the pipe, And as you can see in jeb's answer getting ride of the trailing spaces requires special handling.
One aspect of pipes that leads to confusion is that the piped commands are not executed in batch file context with batch syntax rules, but they are executed in command line context with command line syntax rules in a child cmd instance.
One needs to thoroughly understand the mechanics of the CMD/Batch pipes to be able to construct and maintain a working solution this way, and it is not a straightforward task for slightly more complex piped blocks.
Here is an alternate way which enables piping of any complex blocks of commands with the same level of flexibility as you have in normal batch codes.
#echo off
if "%~1"=="/LPipe" goto :/LPipe
if "%~1"=="/RPipe" goto :/RPipe
set /p user_id=Username:
set /p pwd=Password:
"%~f0" /LPipe | "%~f0" /RPipe
exit /b
:/LPipe
REM This will be executed inside a pipe but in batch context
REM Enable delayed expansion to be able to send any special characters
setlocal EnableDelayedExpansion
REM It's easy to take care of trailing spaces, no special hacks needed.
echo !user_id!
echo !pwd!
goto :EOF
:/RPipe
REM This will be executed inside a pipe but in batch context
%install_dir%\vpncli.exe connect myvpn.mydomain.TLD -s
goto :EOF
I'm working on a script that executes a command line application which requires user input at runtime (sadly command line arguments are not provided).
So my first attempt looked like this:
#echo off
(echo N
echo %~dp0%SomeOther\Directory\
echo Y)|call "%~dp0%SomeDirectory\SadSoftware.exe"
At first glance it looked like it worked pretty well, but as it turned out it didn't. After investigation I found out that the directory I was passing to the software contained extra space at the end, which resulted in some problems.
I looked around for a while and found out following question:
echo is adding space when used with a pipe .
This explained what is going on, but didn't really help me solve my problem (I'm not too familiar with batch programming).
At the moment I "kind of solved" my problem with an ugly workaround:
echo N> omg.txt
echo %~dp0%SomeOther\Directory\>> omg.txt
echo Y>> omg.txt
"%~dp0%SomeDirectory\SadSoftware.exe"<omg.txt
del omg.txt
This solution works, but I'm less than happy with it. Is there some prettier way? Or even uglier way, but without temporary file?
Solution1: Linefeeds
You can use a linefeed to avoid the spaces.
The rem. is required to avoid problems with the injected & by cmd.exe
SET LF=^
REM ** The two empts lines are required for the new line **
(
echo line1%%LF%%rem.
echo line2%%LF%%rem.
echo line3%%LF%%rem.
) | sort
When a block is piped the cmd.exe will rebuild the block by building a single line combined with ampersands between each command.
(
echo line1
echo line2
) | sort
Will be converted to
C:\Windows\system32\cmd.exe /S /D /c" (<space>echo line1<space>&<space>echo line2<space>)"
So the linefeed trick results in a line like
C:\Windows\system32\cmd.exe /S /D /c" ( echo line1%LF%rem. & echo line2%LF%rem. )"
And this expands to
( echo line1
rem. & echo line2
rem. )
Solution2: Escaped ampersands
You can also use directly ampersands, but they need to be escaped, else the spaces are injected anyway.
The last rem. is to avoid a space after the last echo
(
echo Line1^&echo Line2^&echo Line3^&rem.
) | more
Solution3: Create a single command
#MCND Mentioned the trick to use directly the form:
cmd /q /c"(echo line1&echo line2&echo line3)" | sort
This works, as the parser only sees the cmd command, the rest is handled as parameters for this single command.
Therefore there aren't any problems with spaces.
Or with the linefeeds it looks like
cmd /q /c"(echo line1%%LF%%echo line2%%LF%%echo line3)"| (sort > out.txt)
And then you can modify it also to
cmd /q /c^"(echo line1%%LF%%^
echo line2%%LF%%^
echo line3)^"| (sort > out.txt)
I'm over my head with this - spent too much time searching already - evidently I don't understand the basics of CMD variables etc. - and it always gives me such a headache
why wouldn't this work?
for %a in (*) do ( set tmpx=%a & echo %tmpx% )
the above code outputs the value of %tmpx% in some other scope - and it is always constant
yes, i run setlocal ENABLEDELAYEDEXPANSION
basically i need to do a simple rename of all files in folder from constantstring_somenameXX.tif to somenameXX.tif, where i.e. constantstring=0000000005
i had to use set because other posts rightly suggested that %a in a for loop has a special behaviour, and the substitutions wouldn't work for it as it is.
i would prefer not to use scripts and/or powershell - unless not using them is impossible
thank you
for %a in (*) do ( set tmpx=%a & echo %tmpx% )
The problem with the previous code is delayed expansion. Yes, you enabled it, but you have not used it, and depending on how you enabled it, it will not work
In cmd, when a line or block of lines (code inside parenthesis) is reached, it is first parsed and then executed. During the parse phase, variable read operations are removed from the command, replaced with the value in the variable before the command starts to execute. So, if you change the value of a variable inside a line/block you can not retrieve the changed value inside the same line/block as there are no variable reads (they were replaced)
setlocal enabledelayedexpansion allows to replace (where needed) the variable read syntax from %var% to !var!, indicating to the parser that the read operation will be delayed until the execution phase.
So, in your case, your code should have been something like
setlocal enabledelayedexpansion & for %a in (*) do ( set "tmpx=%a" & echo !tmpx! )
BUT this will not work (in default configured environments).
cmd has two execution modes: batch file and command line. In your case, you are using command line (no escaped percent sign in for loop) and in command line mode the setlocal enabledelayedexpansion will not work. It is intended for batch files (see setlocal /?)
How to make it work from the command line? By default cmd is started with delayed expansion disabled and you can not enable it if not inside a batch file. But you can start cmd with delayed expansion enabled and run your command in this started instance (see cmd /?)
cmd /v:on /c "for %a in (*) do ( set "tmpx=%a" & echo !tmpx! )"
Anyway, to solve your rename problem, delayed expansion is not needed
for %a in (*_*.tif) do for /f "tokens=1,* delims=_" %b in ("%~nxa") do echo ren "%a" "%c"
That is, for each tif file with an underscore, take the name and extension of the file (%~nxa) as a string, and using the underscore as a delimiter between tokens, retrieve the first token (the text on the left of the first underscore) in %b and the rest of the text (to the right of the underscore) into %c. Now, just rename the original file name (stored in %a) to the contents of %c (the text on the right of the underscore)
In this code rename operations are only echoed to console. If the output is correct, remove the echo command.
! is the character to use rather than % when wanting execution time value. % does when it's read value.
CMD was written by IBM engineers and they were trying to make MSDos a programming language while making sure Dos commands ran the same. So we get a hodge podge.
& seperates commands on a line.
&& executes this command only if previous command's errorlevel is 0.
|| (not used above) executes this command only if previous command's errorlevel is NOT 0
> output to a file
>> append output to a file
< input from a file
| output of one command into the input of another command
^ escapes any of the above, including itself, if needed to be passed to a program
" parameters with spaces must be enclosed in quotes
+ used with copy to concatinate files. E.G. copy file1+file2 newfile
, used with copy to indicate missing parameters. This updates the files modified date. E.G. copy /b file1,,
%variablename% a inbuilt or user set environmental variable
!variablename! a user set environmental variable expanded at execution time, turned with SelLocal EnableDelayedExpansion command
%<number> (%1) the nth command line parameter passed to a batch file. %0 is the batchfile's name.
%* (%*) the entire command line.
%<a letter> or %%<a letter> (%A or %%A) the variable in a for loop. Single % sign at command prompt and double % sign in a batch file.
Here is a part of my batch...
for /f "delims=" %%i in ('C:\pathto\dig.exe www.example.com #8.8.8.8 +short') do set RIP=%%i
If %RIP%==93.184.216.119 (
ECHO - PASS >> results-file.txt
) else (
ECHO - FAIL >> results-file.txt
)
...this works fine - except, let's say the DNS server 8.8.8.8 is unavailable or broken (so for testing replace it with a bad IP) and then, normally from the command-line dig.exe will timeout and eventually return...
;; connection timed out; no servers could be reached
...followed by a return to the command prompt. So I would expect that string to get set in %RIP% and thus FAIL would be printed to the file, but what happens instead is dig.exe runs, timeout, and then cmd.exe just closes, and nothing gets written to the file, and the rest of the batch does not continue.
Any idea where I've gone wrong?
There is at least one reason why the RIP variable is not set - the default FOR /F EOL value is semicolon, meaning all lines that begin with ; are ignored. You need to set EOL to something you know the line cannot begin with, or disable it entirely (tricky).
It is also possible that the error message is sent to stderr instead of stdout, and FOR /F only captures stdout. If this is happening, then you must also redirect stderr to stdout using 2>&1. The special characters must be escaped (or quoted) when in the FOR /F IN() clause.
There is an awkward syntax to disable both DELIMS and EOL that can solve your problem:
for /f delims^=^ eol^= %%i in ('C:\pathto\dig.exe www.example.com #8.8.8.8 +short 2^>^&1') do set RIP=%%i
The reason your IF fails if RIP is not set is the left side is completely empty, causing the parser to fail. You can fix this by adding quotes (or any character) to both sides. But you should also explicitly clear the RIP value before the loop, just in case a prior command previously set RIP.
set "RIP="
for /f "delims=" %%i in ('C:\pathto\dig.exe www.example.com #8.8.8.8 +short') do set RIP=%%i
If "%RIP%"=="93.184.216.119" (
ECHO - PASS >> results-file.txt
) else (
ECHO - FAIL >> results-file.txt
)
I have a batch script which needs to perform an action on each of its arguments. Each argument is a file name (there are not switches), and of course file names may contain spaces. The batch script is run either by dragging files into the .bat icon in Explorer or by entering the files at the command line, enclosing arguments with spaces in quotes.
Within the batch script, there are problems with handling arguments with spaces. If I use %* as follows, the quotations are ignored and each 'word' between spaces is treated as an argument.
for %%x in (%*) do (
echo %%x
)
I have also tried using shift, which doesn't seem to work right either, choking on files with spaces in their name:
:next
if not %1 == "" (
echo %1
shift /1
goto next
)
What is the ideal way to iterate through all arguments?
In Bash, one would simply use "$#" and everything Just Works™, but of course that doesn't seem to be the case with Windows batch scripts.
The substitution modifiers for for variable references also allow for using the ~ expansions. See for command reference.
By using "%%~x" you should get a properly quoted parameter, similar to how bash handles "$#".
#echo off
setlocal enableextensions
for %%x in (%*) do (
echo "%%~x"
)
The characters , and ; can be used to separate command parameters. See command shell overview. Thus you have to put quotes around file names that contain these characters.
If you drag a file from the Explorer onto the .bat, Explorer will only quote the file correctly if it has a white space character in its path. E.g., D:\a,b,c.exe will not be quoted by Explorer and thus will be parsed as three separate arguments by cmd.exe.
To make the script work with drag and drop from the Explorer for these freak cases, you can use the following (ugly) work-around:
#echo off
setlocal enableextensions enabledelayedexpansion
set "args=%*"
set "args=%args:,=:comma:%"
set "args=%args:;=:semicolon:%"
for %%x in (%args%) do (
set "filepath=%%~x"
set "filepath=!filepath::comma:=,!"
set "filepath=!filepath::semicolon:=;!"
echo "!filepath!"
)
The script introduces a helper variable args, where each occurrence of a troublesome character is replaced with a placeholder (note that the colon character itself cannot be used in a file name under Windows).
The body of the for loop uses another helper variable filepath which undos the transformation to produce the original path.
I had a similar issue with file names that contain equal signs (=), which causes the file name to be split into multiple arguments. I solved it by using "%*".
If you have a file with spaces, e.g. foo bar baz.txt, this will be quoted twice: ""foo bar baz.txt"". Now the double double-quotes are escaped: foo bar baz.txt, resulting in %1 = foo, %2 = bar, and %3 = baz.txt. So this does not work.
If you have a file with spaces AND/OR equal signs, you can use:
set input=""%*""
set input=%input:"=%
your_program "%input%"
Now, foo bar=baz.txt will be quoted thrice: """foo bar=baz.txt""". Two quotes will be escaped and input becomes "foo bar=baz.txt". With the second line, double-quotes are replaced by nothing (removed). You need to put the quotes around input again when you enter it into your_program, otherwise it will see spaces as separate input!
If you only have equal signs, then ""%*"" makes foo=bar=baz.txt into ""foo=bar=baz.txt"", which enters your program as %1 = foo=bar=baz.txt.