Single script to run in both Windows batch and Linux Bash?

What I have done is use cmd’s label syntax as comment marker. The label character, a colon (:), is equivalent to true in most POSIXish shells. If you immediately follow the label character by another character which can’t be used in a GOTO, then commenting your cmd script should not affect your cmd code.

The hack is to put lines of code after the character sequence “:;”. If you’re writing mostly one-liner scripts or, as may be the case, can write one line of sh for many lines of cmd, the following might be fine. Don’t forget that any use of $? must be before your next colon : because : resets $? to 0.

:; echo "Hi, I’m ${SHELL}."; exit $?
@ECHO OFF
ECHO I'm %COMSPEC%

A very contrived example of guarding $?:

:; false; ret=$?
:; [ ${ret} = 0 ] || { echo "Program failed with code ${ret}." >&2; exit 1; }
:; exit
ECHO CMD code.

Another idea for skipping over cmd code is to use heredocs so that sh treats the cmd code as an unused string and cmd interprets it. In this case, we make sure that our heredoc’s delimiter is both quoted (to stop sh from doing any sort of interpretation on its contents when running with sh) and starts with : so that cmd skips over it like any other line starting with :.

:; echo "I am ${SHELL}"
:<<"::CMDLITERAL"
ECHO I am %COMSPEC%
::CMDLITERAL
:; echo "And ${SHELL} is back!"
:; exit
ECHO And back to %COMSPEC%

Depending on your needs or coding style, interlacing cmd and sh code may or may not make sense. Using heredocs is one method to perform such interlacing. This could, however, be extended with the GOTO technique:

:<<"::CMDLITERAL"
@ECHO OFF
GOTO :CMDSCRIPT
::CMDLITERAL

echo "I can write free-form ${SHELL} now!"
if :; then
  echo "This makes conditional constructs so much easier because"
  echo "they can now span multiple lines."
fi
exit $?

:CMDSCRIPT
ECHO Welcome to %COMSPEC%

Universal comments, of course, can be done with the character sequence : # or :;#. The space or semicolon are necessary because sh considers # to be part of a command name if it is not the first character of an identifier. For example, you might want to write universal comments in the first lines of your file before using the GOTO method to split your code. Then you can inform your reader of why your script is written so oddly:

: # This is a special script which intermixes both sh
: # and cmd code. It is written this way because it is
: # used in system() shell-outs directly in otherwise
: # portable code. See https://stackoverflow.com/questions/17510688
: # for details.
:; echo "This is ${SHELL}"; exit
@ECHO OFF
ECHO This is %COMSPEC%

Thus, some ideas and ways to accomplish sh and cmd-compatible scripts without serious side effects as far as I know (and without having cmd output '#' is not recognized as an internal or external command, operable program or batch file.).


EDIT

The binki's answer is almost perfect but still can be improved:

:<<BATCH
    @echo off
    echo %PATH%
    exit /b
BATCH

echo $PATH

It uses again the : trick and the multi line comment. Looks like cmd.exe (at least on windows10) works without problems with the unix style EOLs so be sure that your script is converted into linux format. (same approach has been seen used before here and here ) . Though using shebang still will produce redundant output...


you can try this:

#|| goto :batch_part
 echo $PATH
#exiting the bash part
exit
:batch_part
 echo %PATH%

Probably you'll need to use /r/n as a new line instead of a unix style.If I remember correct the unix new line is not interpreted as a new line by .bat scripts.Another way is to create an #.exe file in the path that does do nothing in similar manner as my answer here: Is it possible to embed and execute VBScript within a batch file without using a temporary file?


I wanted to comment, but can only add an answer at the moment.

The techniques given are excellent and I use them also.

It is hard to retain a file which has two kinds of line breaks contained within it, that being /n for the bash part and /r/n for the windows part. Most editors try and enforce a common line break scheme by guessing what kind of file you are editing. Also most methods of transferring the file across the internet (particularly as a text or script file) will launder the line breaks, so you could start with one kind of line break and end up with the other. If you made assumptions about line breaks and then gave your script to someone else to use they might find it doesn't work for them.

The other problem is network mounted file systems (or CDs) that are shared between different system types (particularly where you can't control the software available to the user).

One should therefore use the DOS line break of /r/n and also protect the bash script from the DOS /r by putting a comment at the end of each line (#). You also cannot use line continuations in bash because the /r will cause them to break.

In this way whoever uses the script, and in whatever environment, it will then work.

I use this method in conjunction with making portable Makefiles!


The following works for me without any errors or error messages with Bash 4 and Windows 10, unlike the answers above. I name the file "whatever.cmd", do chmod +x to make it executable in linux, and make it have unix line endings (dos2unix) to keep bash quiet.

:; if [ -z 0 ]; then
  @echo off
  goto :WINDOWS
fi

if [ -z "$2" ]; then
  echo "usage: $0 <firstArg> <secondArg>"
  exit 1
fi

# bash stuff
exit

:WINDOWS
if [%2]==[] (
  SETLOCAL enabledelayedexpansion
  set usage="usage: %0 <firstArg> <secondArg>"
  @echo !usage:"=!
  exit /b 1
)

:: windows stuff

You can share variables:

:;SET() { eval $1; }

SET var=value

:;echo $var
:;exit
ECHO %var%