rom1v
rom1v

Reputation: 2979

Executing a script in MSYS2/MinGW

On Windows, if I start c:\msys64\mingw64.exe, it opens a shell, where I can build my project, let's say by calling a release bash script (to simplify). Everything works fine.

Now, I would like to execute my release script on mingw64 directly, without interaction.

I tried:

c:\msys64\mingw64.exe /c/the/full/path/release

A window opens and closes, it does not work.

I attempted to use bash directly, but it seems the environment is not correctly set:

> c:\msys64\usr\bin\bash -c ls
/usr/bin/bash: ls: command not found

> c:\msys64\usr\bin\bash -c /bin/ls
... it works ...

So it is obvious that the environment is not the same as when execute c:\msys64\mingw64.exe then call ls.

How to execute my release script as if I were in the shell started by mingw64.exe?

Upvotes: 24

Views: 25340

Answers (5)

Gabriel Staples
Gabriel Staples

Reputation: 52817

I'd like to extend @Tiago Becerra Paolini's answer here, which I've upvoted, and my own answer here: Installing & setting up MSYS2 from scratch, including adding all 7 profiles to Windows Terminal....

How to specify in which of the 7 MSYS2 terminals to run a command in Windows from Bash, Command Prompt, & PowerShell

Notes:

  1. For all commands below, if you are not sure which MSYS2 terminal to use, use the ucrt64 terminal.
  2. This assumes that you installed MSYS2 from https://www.msys2.org/ into the default location of C:\msys64, making msys2_shell.cmd available at C:\msys64\msys2_shell.cmd.

When running from Bash (ex: from within another MSYS2 terminal) in Windows

Important: for any case where you have a command plus arguments, not just a command, be sure to wrap the whole set of "command plus arguments" in single quotes, as shown in the examples below.

# Run in the MSYS2 msys2 terminal
/c/msys64/msys2_shell.cmd -defterm -here -no-start -msys2 -shell bash -c 'some_command_and_args'

# Run in the MSYS2 mingw32 **legacy** terminal
/c/msys64/msys2_shell.cmd -defterm -here -no-start -mingw32 -shell bash -c 'some_command_and_args'

# Run in the MSYS2 mingw64 terminal
/c/msys64/msys2_shell.cmd -defterm -here -no-start -mingw64 -shell bash -c 'some_command_and_args'

# [**RECOMMENDED**] Run in the MSYS2 UCRT64 terminal
/c/msys64/msys2_shell.cmd -defterm -here -no-start -ucrt64 -shell bash -c 'some_command_and_args'

# Run in the MSYS2 clang64 terminal
/c/msys64/msys2_shell.cmd -defterm -here -no-start -clang64 -shell bash -c 'some_command_and_args'

# Run in the MSYS2 clang32 **legacy** terminal
/c/msys64/msys2_shell.cmd -defterm -here -no-start -clang32 -shell bash -c 'some_command_and_args'

# Run in the MSYS2 clangarm64 terminal
/c/msys64/msys2_shell.cmd -defterm -here -no-start -clangarm64 -shell bash -c 'some_command_and_args'

To see which terminal a command is running in, run echo $MSYSTEM in the terminal.

Example runs:

/c/msys64/msys2_shell.cmd -defterm -here -no-start -msys2 -shell bash -c 'echo "$MSYSTEM"'
/c/msys64/msys2_shell.cmd -defterm -here -no-start -mingw32 -shell bash -c 'echo "$MSYSTEM"'
/c/msys64/msys2_shell.cmd -defterm -here -no-start -mingw64 -shell bash -c 'echo "$MSYSTEM"'
/c/msys64/msys2_shell.cmd -defterm -here -no-start -ucrt64 -shell bash -c 'echo "$MSYSTEM"'
/c/msys64/msys2_shell.cmd -defterm -here -no-start -clang64 -shell bash -c 'echo "$MSYSTEM"'
/c/msys64/msys2_shell.cmd -defterm -here -no-start -clang32 -shell bash -c 'echo "$MSYSTEM"'
/c/msys64/msys2_shell.cmd -defterm -here -no-start -clangarm64 -shell bash -c 'echo "$MSYSTEM"'

Example output:

MSYS
MINGW32
MINGW64
UCRT64
CLANG64
CLANG32
CLANGARM64

When running from a Windows Command Prompt

From a Windows Command Prompt, you just need to use Windows-style paths is all. This also works just fine in the MSYS-based Bash terminals in Windows, since MSYS2 terminals do automatic path translation, so you can also run these commands from within an MSYS2 terminal.

Example:

C:\msys64\msys2_shell.cmd -defterm -here -no-start -msys2 -shell bash -c 'echo "$MSYSTEM"'
C:\msys64\msys2_shell.cmd -defterm -here -no-start -mingw32 -shell bash -c 'echo "$MSYSTEM"'
C:\msys64\msys2_shell.cmd -defterm -here -no-start -mingw64 -shell bash -c 'echo "$MSYSTEM"'
C:\msys64\msys2_shell.cmd -defterm -here -no-start -ucrt64 -shell bash -c 'echo "$MSYSTEM"'
C:\msys64\msys2_shell.cmd -defterm -here -no-start -clang64 -shell bash -c 'echo "$MSYSTEM"'
C:\msys64\msys2_shell.cmd -defterm -here -no-start -clang32 -shell bash -c 'echo "$MSYSTEM"'
C:\msys64\msys2_shell.cmd -defterm -here -no-start -clangarm64 -shell bash -c 'echo "$MSYSTEM"'

Example output: exact same as above.

When running from a Windows PowerShell

From a Windows PowerShell, you need to use the & operator to run the command.

Example:

& "C:\msys64\msys2_shell.cmd" -defterm -here -no-start -msys2 -shell bash -c 'echo "$MSYSTEM"'
& "C:\msys64\msys2_shell.cmd" -defterm -here -no-start -mingw32 -shell bash -c 'echo "$MSYSTEM"'
& "C:\msys64\msys2_shell.cmd" -defterm -here -no-start -mingw64 -shell bash -c 'echo "$MSYSTEM"'
& "C:\msys64\msys2_shell.cmd" -defterm -here -no-start -ucrt64 -shell bash -c 'echo "$MSYSTEM"'
& "C:\msys64\msys2_shell.cmd" -defterm -here -no-start -clang64 -shell bash -c 'echo "$MSYSTEM"'
& "C:\msys64\msys2_shell.cmd" -defterm -here -no-start -clang32 -shell bash -c 'echo "$MSYSTEM"'
& "C:\msys64\msys2_shell.cmd" -defterm -here -no-start -clangarm64 -shell bash -c 'echo "$MSYSTEM"'

Example output: exact same as above.

Bash function to wrap the commands above

You can also create a Bash function to wrap the command in order to significantly simplify the call. Here is a Bash wrapper function:

# Run the passed-in command + arguments inside the MSYS2 UCRT64 terminal 
# on Windows
msys2_ucrt64() {
    # IMPORTANT!: use `"$*"` instead of `"$@"` here or commands with arguments
    # won't work.
    /c/msys64/msys2_shell.cmd -defterm -here -no-start -ucrt64 -shell bash -c "$*"
}

# Another example wrapper function

msys2_clang64() {
    /c/msys64/msys2_shell.cmd -defterm -here -no-start -clang64 -shell bash -c "$*"
}

Here are some example runs and their outputs. Notice that single quotes around the set of "command plus arguments" are not needed anymore, which helps a lot for really complicated commands and arguments. But, in this case, we want to force the environment variable MSYSTEM to be expanded in the inner, or sub-shell, not the current, or outer shell, so we must surround just $MSYSTEM with single quotes to prevent immediate variable expansion.

Example calls:

msys2_ucrt64 echo '$MSYSTEM'
msys2_clang64 echo '$MSYSTEM'

Example output:

UCRT64
CLANG64

Final answer: a better Bash wrapper function to rule them all!

Let's improve the above wrapper by making the ucrt64 part a parameter to the function so that a single wrapper can handle all 7 MSYS2 terminals.

Let's also surround the call with some marker prints to make it obvious when looking at output in the terminal that it was called from within this specified sub-shell.

# Run the passed-in command + arguments inside the specified MSYS2 terminal
# on Windows.
# - See: https://stackoverflow.com/a/79201770/4561887
#
# Usage: msys2_terminal <terminal> <command> [<args>...]
# 
# Example: msys2_terminal ucrt64 echo '$MSYSTEM'
#
msys2_terminal() {
    local terminal="$1"
    # Remove the first argument from the list of arguments
    shift
    # IMPORTANT!: use `"$*"` instead of `"$@"` here or commands with arguments
    # won't work.
    local command="$*"

    # Ensure the terminal is one of the 7 MSYS2 terminals. 
    # - We will also accept `msys` as an alias for `msys2`, since 
    #   `msys2_shell.cmd` does too.
    case "$terminal" in
        msys|msys2|mingw32|mingw64|ucrt64|clang64|clang32|clangarm64)
            ;;
        *)
            echo "Invalid terminal: $terminal"
            return 1
            ;;
    esac

    # Print the terminal name to make it obvious in the terminal output
    echo "--Running in MSYS2 $terminal terminal...START--"
    /c/msys64/msys2_shell.cmd -defterm -here -no-start -"$terminal" -shell bash -c "$command"
    error_code="$?"
    echo "--Running in MSYS2 $terminal terminal...END--"

    return "$error_code"
}

# Convenience alias
alias ucrt64="msys2_terminal ucrt64"

Example calls:

msys2_terminal msys echo '$MSYSTEM'
msys2_terminal msys2 echo '$MSYSTEM'
msys2_terminal mingw32 echo '$MSYSTEM'
msys2_terminal mingw64 echo '$MSYSTEM'
msys2_terminal ucrt64 echo '$MSYSTEM'  # the preferred terminal
msys2_terminal clang64 echo '$MSYSTEM'
msys2_terminal clang32 echo '$MSYSTEM'
msys2_terminal clangarm64 echo '$MSYSTEM'

Example output:

--Running in MSYS2 msys terminal...START--
MSYS
--Running in MSYS2 msys terminal...END--
--Running in MSYS2 msys2 terminal...START--
MSYS
--Running in MSYS2 msys2 terminal...END--
--Running in MSYS2 mingw32 terminal...START--
MINGW32
--Running in MSYS2 mingw32 terminal...END--
--Running in MSYS2 mingw64 terminal...START--
MINGW64
--Running in MSYS2 mingw64 terminal...END--
--Running in MSYS2 ucrt64 terminal...START--
UCRT64
--Running in MSYS2 ucrt64 terminal...END--
--Running in MSYS2 clang64 terminal...START--
CLANG64
--Running in MSYS2 clang64 terminal...END--
--Running in MSYS2 clang32 terminal...START--
CLANG32
--Running in MSYS2 clang32 terminal...END--
--Running in MSYS2 clangarm64 terminal...START--
CLANGARM64
--Running in MSYS2 clangarm64 terminal...END--

Example call using the ucrt64 alias above:

ucrt64 echo '$MSYSTEM'

Output:

--Running in MSYS2 ucrt64 terminal...START--
UCRT64
--Running in MSYS2 ucrt64 terminal...END--

If you call the msys2_terminal function with an invalid terminal name, you will get an error message and a non-zero return code.

Example run and output. Note that "$?" is the return code of the last command run:

$ msys2_terminal msyss2 echo '$MSYSTEM'
Invalid terminal: msyss2
$ echo "$?"
1

Going further: appropriately handle running on Windows or Linux

See my answer: How to detect the OS from a Bash script?

Example code to run gcc build commands on both Windows and Linux:

if [[ "$OSTYPE" == "linux-gnu"* ]]; then
    # For Linux, run normally
    "./build_and_run_tests.sh"
elif [[ "$OSTYPE" == "msys" ]]; then 
    # For Windows, force running the tests inside the MSYS2 UCRT64 terminal 
    # where we have previously installed gcc with:
    #
    # ```bash
    # # Upgrade all packages
    # pacman -Syu
    #
    # # Install the gcc compiler
    # pacman -S mingw-w64-ucrt-x86_64-gcc
    # ```
    #
    msys2_terminal ucrt64 "./build_and_run_tests.sh"
fi

if [ "$?" -ne "0" ]; then
    echo "Test FAILED."
fi

# ...

And that's all I know about this topic...

References

  1. @Tiago Becerra Paolini's answer here
  2. My own answer here: Installing & setting up MSYS2 from scratch, including adding all 7 profiles to Windows Terminal....
  3. My answer: How to detect the OS from a Bash script? - see if you're running on Windows or Linux.
  4. Various chats with GitHub Copilot for help with Bash syntax and things. (This answer is all my own, however, and I've fully manually tested it).

Upvotes: 0

Tiago Becerra Paolini
Tiago Becerra Paolini

Reputation: 146

Another way of doing it is using the msys2_shell.cmd launcher on MSYS2' installation folder. Assuming that it was installed to the default folder, the command goes:

C:\msys64\msys2_shell.cmd -defterm -no-start -mingw64 -here -c "your command here"

The option -defterm sends the output to the current terminal, -no-start makes so a new terminal window is not opened, -here makes the current directory as the working directory, -mingw64 is the environment to use (other possibilities are -mingw32, -ucrt64, -clang64), and -c is your shell command itself.

Here is an example msys2_shell.cmd help menu as shown when run inside of the Git Bash MSYS2-based terminal:

$ /c/msys64/msys2_shell.cmd --help
Usage:
    msys2_shell.cmd [options] [login shell parameters]

Options:
    -mingw32 | -mingw64 | -ucrt64 | -clang64 | -msys[2]   Set shell type
    -defterm | -mintty | -conemu                            Set terminal type
    -here                            Use current directory as working
                                     directory
    -where DIRECTORY                 Use specified DIRECTORY as working
                                     directory
    -[use-]full-path                 Use full current PATH variable
                                     instead of trimming to minimal
    -no-start                        Do not use "start" command and
                                     return login shell resulting
                                     errorcode as this batch file
                                     resulting errorcode
    -shell SHELL                     Set login shell
    -help | --help | -? | /?         Display this help and exit

Any parameter that cannot be treated as valid option and all
following parameters are passed as login shell command parameters.

Upvotes: 12

Recep Bashir
Recep Bashir

Reputation: 41

add an supplement to the above: if u want to the output of shell script

  • reference:https://mintty.github.io/mintty.1.html

-l, --log FILE|-

Copy all output into the specified log file, or standard output if a dash is given instead of a file name. (Implies -o Logging=yes.)

If FILE contains %d it will be substituted with the process ID. See description of equivalent option "Log file" (Log=) below for further formatting options and hints.

Note that logging can be toggled from the extended context menu.

Add A complete example:

C:\msys64\usr\bin\mintty.exe -w hide -l - c:\msys64\usr\bin\env MSYSTEM=MINGW64 c:\msys64\usr\bin\bash -l -c "PATH=\"$PATH\" /C/Users/Administrator/Desktop/myProject/Demo_C_C++/shell/textProcess/bookNoteHandler.sh" | find /v "/v:Displays all lines that don't contain the specified"

=========

enter image description here

Upvotes: 0

rom1v
rom1v

Reputation: 2979

Thanks to the answers from @David Grayson, I managed to call my release script with msys2/mingw from a Windows console (cmd), with additional directories (for Java and Meson) in $PATH:

c:\msys64\usr\bin\env MSYSTEM=MINGW64 c:\msys64\usr\bin\bash -l -c "PATH=\"/c/Program Files/Java/jdk1.8.X_XXX/bin:/c/Program Files/Meson:$PATH\" /c/Users/rom1v/project/release"

Upvotes: 8

David Grayson
David Grayson

Reputation: 87446

To run a Bash shell script in MSYS2 without showing a window, you should right-click on your Desktop or somewhere else in Windows Explorer, select "New", select "Shortcut", and then enter something like this for the shortcut target:

C:\msys64\usr\bin\mintty.exe -w hide /bin/env MSYSTEM=MINGW64 /bin/bash -l /c/Users/rom1v/project/release.sh

Note that there are 4 paths in here. The path to mintty and release.sh are absolute paths that you will need to adjust. The paths to env and bash are relative to your MSYS2 installation directory. Note also that the first path must be a standard Windows path, since Windows expects that when it is running a shortcut.

Explanation

It might seem odd to use MinTTY for a non-interactive script, but we need to use some program that was compiled for the Windows subsystem (-mwindows option to GCC), or else Windows will automatically start a new console when we run the program. We pass the -w hide option to MinTTY to tell it not to actually show a window. Everything after that option is interpreted by MinTTY as a command to run.

So MinTTY will run /bin/env from the MSYS2 distribution and pass the remainder of the arguments on to it. This is a handy utility that is actually a standard part of Linux as well as MSYS2. It sets the MSYSTEM environment variable to MINGW64 (which is important later) and then it runs /bin/bash with the remainder of the command-line arguments.

We pass -l to Bash so that it acts as a login script, and runs certain startup scripts. In particular, the /etc/profile script from MSYS2 is essential because it looks at the MSYSTEM environment variable, sees that it is MINGW64, and then sets a bunch of other environment variables (e.g. PATH) to give you the MinGW 64-bit shell environment.

Finally, we pass the name of your script as the main argument to bash, so it will run that script after running the initialization scripts.

Error handling

Note that if your Bash script has an error, you won't get any notification, because the shortcut above doesn't open any console windows. I personally would find that pretty annoying. I'd probably remove the -w hide option, then make a wrapper bash script that just does something like:

run_my_main_script || sleep 10000

So if the main script is successful, exit right away, otherwise keep the window open for 10000 seconds. You don't have to even put that wrapper script in its own file, you can just put it in the shortcut as the argument to Bash's -c option (don't forget to wrap it in double quotes).

Upvotes: 22

Related Questions