Reputation: 53
I'm writing my own simple system allowing me to automatically sign APKs before they are uploaded to GPlay. I've got a batch file that does the signing; and a "wrapper" batch file, the content of which will be run on the command line by Jenkins post-build.
sign_apks.bat:
@echo off
set /p job= "Enter job name: "
set /p alias= "Enter key alias: "
set /p mobile= "Sign mobile? (y/n): "
set /p wear= "Sign wear? (y/n): "
echo.
echo "%job%"
echo "%alias%"
echo "%mobile%"
echo "%wear%"
[the rest of the code is sensitive so is emitted, but these variables are used later]
wrapper code:
@echo off
(echo test
echo test
echo y
echo y)| call sign_apks.bat
This article showed me how to pipe values into a program. To quote from the answer,:
Multiple lines can be entered like so:
(echo y
echo n) | executable.exe
...which will pass first 'y' then 'n'.
However, this doesn't work. This is the output I get when running the wrapper code:
Enter job name: Enter key alias: Sign mobile? (y/n): Sign wear? (y/n):
"test "
""
""
""
Help?
Upvotes: 5
Views: 7714
Reputation: 130849
There is something very odd with how SET /P interacts with your piped input that I do not fully understand.
But I do have some solutions :-)
The simplest solution is to write your responses to a temporary file, and then use that temp file as redirected input.
@echo off
(
echo test
echo test
echo y
echo y
)>responses.temp
call sign_apks.bat <responses.temp
delete responses.temp
That is how I would solve your problem. But some people do not like to use temporary files (why I don't know). So I decided I would attempt to solve it using a pipe without a temp file.
I discovered an odd variation of your code that almost solves the problem - but it appends an extra unwanted space at the end of each value.
@echo off
(
call echo test1
call echo test2
call echo y1
call echo y2
) | sign_apks.bat
--OUTPUT--
Enter job name: Enter key alias: Sign mobile? (y/n): Sign wear? (y/n):
"test1 "
"test2 "
"y1 "
"y2 "
I cannot explain why the CALL enables each of the SET /P statements to work properly. But I can explain why the space is appended to each value. It has to do with why CALL is not needed when you use a batch script with a pipe.
Each side of a pipe is executed in a brand new cmd.exe session. For example, the right side of the pipe becomes a command that looks something like:
C:\Windows\system32\cmd.exe /S /D /c" sign_apks.bat"
This is the reason why CALL is not needed - control will return after the new cmd.exe session terminates.
The unwanted spaces are an artifact of how pipes process parenthesized blocks. The parser must capture the entire piped code block and transform it into a single line that can be incorporated into the CMD.EXE /C argument. The CMD.EXE parser does this by putting an & between each command. Unfortunately, the parser also inserts some extra spaces. So the left side of the pipe is transformed into something like:
C:\Windows\system32\cmd.exe /S /D /c" ( call echo test & call echo test & call echo y & call echo y )"
Now you can easily see where the unwanted trailing spaces are coming from. See Why does delayed expansion fail when inside a piped block of code? for more information about how pipes are implemented.
I finally came up with one more solution. I created a helper batch script called WriteArgs.bat that simply ECHOs each argument passed to it.
WriteArgs.bat
@echo off
:loop
if .%1 equ . exit /b
echo %1
shift /1
goto loop
With this simple batch script, you can now solve your problem using:
WriteArgs.bat test test y y | sign_apks.bat
Again, I don't understand why SET /P works properly here, yet doesn't work with your original command. But this does solve the problem :-)
Update - Well, it solves the problem on my machine. But it seems to be a timing issue, and I don't have confidence that any given piped solution will always work. The only solution I feel is robust is the one that uses a temp file and redirection.
Upvotes: 5
Reputation: 82317
It's a problem of set /p
, it reads the input buffer, but it fails to split this buffer when multiple lines are available, it simply takes the first line from the buffer and the rest will be discarded.
This isn't a problem for a single echo
piped to a single set/p
, but when you pipe more lines to multiple set/p
you got random results.
The solution of dbenham can work, but it's depends on your system!
As both processes (line producer and the set/p
consumer) are asnchronously running in an own cmd.exe task, it depends on the cpu time each process gets.
But you can ensure a correct consuming by splitting the content by another program like more
or findstr
.
As these split the input buffer proberly at the line boundarys.
Upvotes: 1
Reputation: 24476
You know, the easiest solution would be to supply job
, alias
, mobile
, and wear
as script arguments rather than trying to pipe them into stdin. You can still set /p
if not defined, if you wish to run interactively without arguments.
@echo off
setlocal
set "job=%~1"
set "alias=%~2"
set "mobile=%~3"
set "wear=%~4"
if not defined job set /p "job=Enter job name: "
if not defined alias set /p "alias=Enter key alias: "
if not defined mobile set /p "mobile=Sign mobile? (y/n): "
if not defined wear set /p "wear=Sign wear? (y/n): "
echo.
echo "%job%"
echo "%alias%"
echo "%mobile%"
echo "%wear%"
Then when you call sign_apks.bat
, just call it like this:
call sign_apks.bat test test y y
Upvotes: 1