Search code examples
windowsbatch-filecmdshgit-bash

Calling CMD and passing inputs to EXE from a Bash shell script (using Git Bash for Windows)


I have a custom .exe program that performs computations. It can be run manually from Windows Command Line, but I created a .bat file for efficiency. The .bat file launches the .exe in Windows Command Line and pipes in the inputs. Specifically, it reads a list of 3 files from runs.txt and passes each file in the list to the program. The program then reads the info from the file, performs the computations, and saves the outputs.

I am trying to convert this workflow into bash so that I can run it with other workflows using Git Bash. I don't know how to convert the piping syntax to bash.

Folder structure:

myfolder
   |---- myprogram.exe
   |---- myshell.sh
   |---- mybatch.bat
   |---- runs.txt
   |---- RunA.csv
   |---- RunB.csv
   |---- RunC.csv

Contents of runs.txt:

RunA.csv
RunB.csv
RunC.csv

Here is the contents of mybatch.bat which works correctly when I run from Windows Command Line:

(for %%i in (3 runs.txt) do @echo %%i)| myprogram.exe

When I am in Git Bash terminal with myfolder as the pwd, I can run the following and it works from terminal:

cmd
(for %i in (3 runs.txt) do @echo %i)| myprogram.exe
exit

(Note that use %i when running from terminal and %%i when running from batch.) I know it is working because it prints the expected outputs and creates the expected files.

However, when I try to put this into myshell.sh and run sh myshell.sh from Git Bash terminal, nothing happens. No outputs are printed and no files are created. The terminal does, however, switch to Windows Command Line, so I think it is just reading the first line (cmd) and stopping.

Contents of myshell.sh:

cmd
(for %%i in (3 runs.txt) do @echo %%i)| myprogram.exe

(Note I also tried with just %i; same problem)

How can I get it to proceed to the next line as if I was entering the command by hand in terminal? Looking for solutions that will work in Git Bash; not using other bash emulators.

Update The code (for %%i in (3 runs.txt) do @echo %%i)| myprogram.exe needs to work in bash. It can do that by using CMD or running the EXE in bash. But it must read the input file name from the text file and pass it into the EXE.


Solution

  • Correction. Your mybatch.bat does NOT "pass[] each file in [runs.txt]" to the program. It passes only the count 3 and the filename runs.txt. If something is reading 3 filenames from runs.txt and processing them, it is the program, not CMD. Aside: when running programs in CMD you can always omit .exe (or .com or several other suffixes) from the name if you want.

    Why it doesn't work. When you are running git-bash and you type

    cmd
    (for %i in (3 runs.txt) do @echo %i)| myprogram.exe
    exit
    

    only the first line cmd is read by git-bash, causing it to run cmd.exe (which is actually CMD.EXE in Windows case-insensitive filesystem) with input from and output to the terminal; then the second and third lines are read and executed by CMD.EXE. This works because they use syntax valid for CMD.EXE, even though it is not valid for git-bash. However if you put those same three lines in a file and try to execute it as a script in git-bash, git-bash executes the first line by running CMD.EXE with input from and output to the terminal. That CMD doesn't receive, and cannot execute, the second and third lines.

    The minimal fix. If you want to use CMD to execute those lines (as is necessary with that syntax) you should provide them as input to CMD. The simplest way is to use a 'here-document' by making your script file contain:

    cmd <<ENDINPUT
    (for %i in (3 runs.txt) do @echo %i)| myprogram.exe
    exit
    ENDINPUT
    

    ENDINPUT can actually be any 'word' (string of non-special characters) you like as long as both instances (the << on the first line and the last line) are the same; many people like to use EOF.

    But you don't actually need the exit command since end-of-input does the same thing in CMD, so you can do this:

    cmd <<ENDINPUT
    (for %i in (3 runs.txt) do @echo %i)| myprogram.exe
    ENDINPUT
    

    Better options. You don't actually need a for loop at all; in CMD (either interactively or in a batch file) you could simply do

    ( echo 3 & echo runs.txt ) | myprogram.exe
    

    and in git-bash (or any other shell that runs on Windows, like cygwin), rather that going roundabout through CMD, you could directly run the program with the same input, with

    printf '%s\r\n' 3 runs.txt | myprogram.exe
    # in git-bash you can omit the .exe if 'myprogram' is unambiguous 
    

    or if myprogram.exe reads its input so that the Windows convention of CRLF is not required and lone-LF works (which is true of all C and C++ libraries on Windows I know of, for example) then you can use simpler

    { echo 3; echo runs.txt; } | myprogram.exe
    

    or even (in bash but not all other shells)

    myprogram.exe <<<$'3\nruns.txt'