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

Is it possible to write a single script file which executes in both Windows (treated as .bat) and Linux (via Bash)?
I know the basic syntax of both, but didn't figure out. It could probably exploit some Bash's obscure syntax or some Windows batch processor glitch.
The command to execute may be just a single line to execute other script.
The motivation is to have just a single application boot command for both Windows and Linux.
Update: The need for system's "native" shell script is that it needs to pick the right interpreter version, conform to certain well-known environment variables etc. Installing additional environments like CygWin is not preferable - I'd like to keep the concept "download & run".
The only other language to consider for Windows is Windows Scripting Host - WSH, which is preset by default since 98.

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%

The previous answers seem to cover pretty much all the options and helped me a lot. I'm including this answer here just to demonstrate the mechanism I used to include both a Bash script and a Windows CMD script in the same file.
LinuxWindowsScript.bat
echo >/dev/null # >nul & GOTO WINDOWS & rem ^
echo 'Processing for Linux'
# ***********************************************************
# * NOTE: If you modify this content, be sure to remove carriage returns (\r)
# * from the Linux part and leave them in together with the line feeds
# * (\n) for the Windows part. In summary:
# * New lines in Linux: \n
# * New lines in Windows: \r\n
# ***********************************************************
# Do Linux Bash commands here... for example:
StartDir="$(pwd)"
# Then, when all Linux commands are complete, end the script with 'exit'...
exit 0
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
:WINDOWS
echo "Processing for Windows"
REM Do Windows CMD commands here... for example:
SET StartDir=%cd%
REM Then, when all Windows commands are complete... the script is done.
Summary
In Linux
The first line (echo >/dev/null # >nul & GOTO WINDOWS & rem ^) will be ignored and the script will flow through each line immediately following it until the exit 0 command is executed. Once exit 0 is reached, the script execution will end, ignoring the Windows commands below it.
In Windows
The first line will execute the GOTO WINDOWS command, skipping the Linux commands immediately following it and continuing execution at the :WINDOWS line.
Removing Carriage Returns in Windows
Since I was editing this file in Windows, I had to systematically remove the carriage returns (\r) from the Linux commands or else I got abnormal results when running the Bash portion. To do this, I opened the file in Notepad++ and did the following:
Turn on the option for viewing end of line characters (View> Show Symbol > Show End of Line). Carriage returns will then show as CR characters.
Do a Find & Replace (Search > Replace...) and check the Extended (\n, \r, \t, \0, \x...) option.
Type \r in the Find what : field and blank out the Replace with : field so there's nothing in it.
Starting at the top of the file, click the Replace button until all of the carriage return (CR) characters have been removed from the top Linux portion. Be sure to leave the carriage return (CR) characters for the Windows portion.
The result should be that each Linux command ends in just a line feed (LF) and each Windows command ends in a carriage return and line feed (CR LF).

There are several ways of executing different commands on bash and cmd with the same script.
cmd will ignore lines that start with :;, as mentioned in other answers. It will also ignore the next line if the current line ends with the command rem ^, as the ^ character will escape the line break and the next line will be treated as a comment by rem.
As for making bash ignore the cmd lines, there are multiple ways. I have enumerated some ways to do that without breaking the cmd commands:
Non-existent # command (not recommended)
If there is no # command available on cmd when the script is run, we can do this:
# 2>nul & echo Hello cmd! & rem ^
echo 'Hello bash!' #
The # character at the beginning of the cmd line makes bash treat that line as a comment.
The # character at the end of the bash line is used to comment out the \r character, as Brian Tompsett pointed out in his answer. Without this, bash will throw an error if the file has \r\n line endings, required by cmd.
By doing # 2>nul, we're tricking cmd to ignore the error of some non-existent # command, while still executing the command that follows.
Don't use this solution if there is a # command available on the PATH or if you have no control over the commands available to cmd.
Using echo to ignore the # character on cmd
We can use echo with it's output redirected to insert cmd commands on bash's commented out area:
echo >/dev/null # >nul & echo Hello cmd! & rem ^
echo 'Hello bash!' #
Since the # character has no special meaning on cmd, it is treated as a part of the text to echo. All we had to do is redirect the output of the echo command and insert other commands after it.
Empty #.bat file
echo >/dev/null # 1>nul 2> #.bat
# & echo Hello cmd! & del #.bat & rem ^
echo 'Hello bash!' #
The echo >/dev/null # 1>nul 2> #.bat line creates an empty #.bat file while on cmd (or replaces existing #.bat, if any), and does nothing while on bash.
This file will be used by the cmd line(s) that follows even if there is some other # command on the PATH.
The del #.bat command on the cmd-specific code deletes the file that was created. You only have to do this on the last cmd line.
Don't use this solution if a #.bat file could be on your current working directory, as that file will be erased.
Recomended: using here-document to ignore cmd commands on bash
:; echo 'Hello bash!';<<:
echo Hello cmd! & ^
:
By placing the ^ character at the end of the cmd line we're escaping the line break, and by using : as the here-document delimiter, the delimiter line contents will have no effect on cmd. That way, cmd will only execute its line after the : line is over, having the same behaviour as bash.
If you want to have multiple lines on both platforms and only execute them at the end of the block, you can do this:
:;( #
:; echo 'Hello' #
:; echo 'bash!' #
:; );<<'here-document delimiter'
(
echo Hello
echo cmd!
) & rem ^
here-document delimiter
As long as there is no cmd line with exactly here-document delimiter, this solution should work. You can change here-document delimiter to any other text.
In all of the presented solutions, the commands will only be executed after the last line, making their behaviour consistent if they do the same thing on both platforms.
Those solutions must be saved to files with \r\n as line breaks, otherwise they won't work on cmd.

I use this technique to create runnable jar files. Since the jar/zip file starts at the zip header, I can put a universal script to run this file at the top:
#!/usr/bin/env sh\n
# 2>/dev/null # 2>nul & echo off & goto BOF\r\n
:\n
<shell commands go here with \n line endings>
exit\n
\r\n
:BOF\r\n
<cmd commands go here with \r\n line endings>\r\n
exit /B %errorlevel%\r\n
}
It is important to set the line endings as outlined above because they can cause issues on the different platforms. Also the goto statement will not work correctly in some cases if the proper line endings are missing around the jump label.
The technique above is what I use currently.
Below is an outdated version with an in-depth explaination:
#!/usr/bin/env sh
# 2>/dev/null # 2>nul & echo off
:; alias ::=''
:: exec java -jar $JAVA_OPTS "$0" "$#"
:: exit
java -jar %JAVA_OPTS% "%~dpnx0" %*
exit /B
The first line does echo off in cmd and doesn't print anything on sh. This is because the # in sh throws an error that is piped to /dev/null and after that a comment starts. On cmd the pipe to /dev/null fails because the file is not recognized on windows but since windows doesn't detect # as a comment the error is piped to nul. Then it does an echo off. Because the whole line is preceded by an # it doesn't get printet on cmd.
The second one defines ::, which starts a comment in cmd, to noop in sh. This has the benefit that :: does not reset $? to 0. It uses the ":; is a label" trick.
Now I can prepend sh commands with :: and they are ignored in cmd
On :: exit the sh script ends and I can write cmd commands
Only the first line (shebang) is problematic in cmd since it will print command not found.
You have to decide yourself if you need it or not.

I needed this for some of my Python package install scripts. Most things between sh and bat file are same but few things like error handling are different. One way to do this is as follows:
common.inc
----------
common statement1
common statement2
Then you call this from bash script:
linux.sh
--------
# do linux specific stuff
...
# call common code
source common.inc
Windows batch file looks like this:
windows.bat
-----------
REM do windows specific things
...
# call common code
call common.inc

Try my BashWin project at https://github.com/skanga/bashwin which uses BusyBox for most Unix commands

There is a platform independent build tools like Ant or Maven with xml syntax (based on Java).
So, you could rewrite all your scripts in Ant or Maven an run them despite os type.
Or you could just create Ant wrapper script, which will analyze os type and run appropriate bat or bash script.

Related

Is it possible to have a single file that acts as both a batch and bash script? [duplicate]

Is it possible to write a single script file which executes in both Windows (treated as .bat) and Linux (via Bash)?
I know the basic syntax of both, but didn't figure out. It could probably exploit some Bash's obscure syntax or some Windows batch processor glitch.
The command to execute may be just a single line to execute other script.
The motivation is to have just a single application boot command for both Windows and Linux.
Update: The need for system's "native" shell script is that it needs to pick the right interpreter version, conform to certain well-known environment variables etc. Installing additional environments like CygWin is not preferable - I'd like to keep the concept "download & run".
The only other language to consider for Windows is Windows Scripting Host - WSH, which is preset by default since 98.
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%
The previous answers seem to cover pretty much all the options and helped me a lot. I'm including this answer here just to demonstrate the mechanism I used to include both a Bash script and a Windows CMD script in the same file.
LinuxWindowsScript.bat
echo >/dev/null # >nul & GOTO WINDOWS & rem ^
echo 'Processing for Linux'
# ***********************************************************
# * NOTE: If you modify this content, be sure to remove carriage returns (\r)
# * from the Linux part and leave them in together with the line feeds
# * (\n) for the Windows part. In summary:
# * New lines in Linux: \n
# * New lines in Windows: \r\n
# ***********************************************************
# Do Linux Bash commands here... for example:
StartDir="$(pwd)"
# Then, when all Linux commands are complete, end the script with 'exit'...
exit 0
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
:WINDOWS
echo "Processing for Windows"
REM Do Windows CMD commands here... for example:
SET StartDir=%cd%
REM Then, when all Windows commands are complete... the script is done.
Summary
In Linux
The first line (echo >/dev/null # >nul & GOTO WINDOWS & rem ^) will be ignored and the script will flow through each line immediately following it until the exit 0 command is executed. Once exit 0 is reached, the script execution will end, ignoring the Windows commands below it.
In Windows
The first line will execute the GOTO WINDOWS command, skipping the Linux commands immediately following it and continuing execution at the :WINDOWS line.
Removing Carriage Returns in Windows
Since I was editing this file in Windows, I had to systematically remove the carriage returns (\r) from the Linux commands or else I got abnormal results when running the Bash portion. To do this, I opened the file in Notepad++ and did the following:
Turn on the option for viewing end of line characters (View> Show Symbol > Show End of Line). Carriage returns will then show as CR characters.
Do a Find & Replace (Search > Replace...) and check the Extended (\n, \r, \t, \0, \x...) option.
Type \r in the Find what : field and blank out the Replace with : field so there's nothing in it.
Starting at the top of the file, click the Replace button until all of the carriage return (CR) characters have been removed from the top Linux portion. Be sure to leave the carriage return (CR) characters for the Windows portion.
The result should be that each Linux command ends in just a line feed (LF) and each Windows command ends in a carriage return and line feed (CR LF).
There are several ways of executing different commands on bash and cmd with the same script.
cmd will ignore lines that start with :;, as mentioned in other answers. It will also ignore the next line if the current line ends with the command rem ^, as the ^ character will escape the line break and the next line will be treated as a comment by rem.
As for making bash ignore the cmd lines, there are multiple ways. I have enumerated some ways to do that without breaking the cmd commands:
Non-existent # command (not recommended)
If there is no # command available on cmd when the script is run, we can do this:
# 2>nul & echo Hello cmd! & rem ^
echo 'Hello bash!' #
The # character at the beginning of the cmd line makes bash treat that line as a comment.
The # character at the end of the bash line is used to comment out the \r character, as Brian Tompsett pointed out in his answer. Without this, bash will throw an error if the file has \r\n line endings, required by cmd.
By doing # 2>nul, we're tricking cmd to ignore the error of some non-existent # command, while still executing the command that follows.
Don't use this solution if there is a # command available on the PATH or if you have no control over the commands available to cmd.
Using echo to ignore the # character on cmd
We can use echo with it's output redirected to insert cmd commands on bash's commented out area:
echo >/dev/null # >nul & echo Hello cmd! & rem ^
echo 'Hello bash!' #
Since the # character has no special meaning on cmd, it is treated as a part of the text to echo. All we had to do is redirect the output of the echo command and insert other commands after it.
Empty #.bat file
echo >/dev/null # 1>nul 2> #.bat
# & echo Hello cmd! & del #.bat & rem ^
echo 'Hello bash!' #
The echo >/dev/null # 1>nul 2> #.bat line creates an empty #.bat file while on cmd (or replaces existing #.bat, if any), and does nothing while on bash.
This file will be used by the cmd line(s) that follows even if there is some other # command on the PATH.
The del #.bat command on the cmd-specific code deletes the file that was created. You only have to do this on the last cmd line.
Don't use this solution if a #.bat file could be on your current working directory, as that file will be erased.
Recomended: using here-document to ignore cmd commands on bash
:; echo 'Hello bash!';<<:
echo Hello cmd! & ^
:
By placing the ^ character at the end of the cmd line we're escaping the line break, and by using : as the here-document delimiter, the delimiter line contents will have no effect on cmd. That way, cmd will only execute its line after the : line is over, having the same behaviour as bash.
If you want to have multiple lines on both platforms and only execute them at the end of the block, you can do this:
:;( #
:; echo 'Hello' #
:; echo 'bash!' #
:; );<<'here-document delimiter'
(
echo Hello
echo cmd!
) & rem ^
here-document delimiter
As long as there is no cmd line with exactly here-document delimiter, this solution should work. You can change here-document delimiter to any other text.
In all of the presented solutions, the commands will only be executed after the last line, making their behaviour consistent if they do the same thing on both platforms.
Those solutions must be saved to files with \r\n as line breaks, otherwise they won't work on cmd.
I use this technique to create runnable jar files. Since the jar/zip file starts at the zip header, I can put a universal script to run this file at the top:
#!/usr/bin/env sh\n
# 2>/dev/null # 2>nul & echo off & goto BOF\r\n
:\n
<shell commands go here with \n line endings>
exit\n
\r\n
:BOF\r\n
<cmd commands go here with \r\n line endings>\r\n
exit /B %errorlevel%\r\n
}
It is important to set the line endings as outlined above because they can cause issues on the different platforms. Also the goto statement will not work correctly in some cases if the proper line endings are missing around the jump label.
The technique above is what I use currently.
Below is an outdated version with an in-depth explaination:
#!/usr/bin/env sh
# 2>/dev/null # 2>nul & echo off
:; alias ::=''
:: exec java -jar $JAVA_OPTS "$0" "$#"
:: exit
java -jar %JAVA_OPTS% "%~dpnx0" %*
exit /B
The first line does echo off in cmd and doesn't print anything on sh. This is because the # in sh throws an error that is piped to /dev/null and after that a comment starts. On cmd the pipe to /dev/null fails because the file is not recognized on windows but since windows doesn't detect # as a comment the error is piped to nul. Then it does an echo off. Because the whole line is preceded by an # it doesn't get printet on cmd.
The second one defines ::, which starts a comment in cmd, to noop in sh. This has the benefit that :: does not reset $? to 0. It uses the ":; is a label" trick.
Now I can prepend sh commands with :: and they are ignored in cmd
On :: exit the sh script ends and I can write cmd commands
Only the first line (shebang) is problematic in cmd since it will print command not found.
You have to decide yourself if you need it or not.
I needed this for some of my Python package install scripts. Most things between sh and bat file are same but few things like error handling are different. One way to do this is as follows:
common.inc
----------
common statement1
common statement2
Then you call this from bash script:
linux.sh
--------
# do linux specific stuff
...
# call common code
source common.inc
Windows batch file looks like this:
windows.bat
-----------
REM do windows specific things
...
# call common code
call common.inc
Try my BashWin project at https://github.com/skanga/bashwin which uses BusyBox for most Unix commands
There is a platform independent build tools like Ant or Maven with xml syntax (based on Java).
So, you could rewrite all your scripts in Ant or Maven an run them despite os type.
Or you could just create Ant wrapper script, which will analyze os type and run appropriate bat or bash script.

How does the echo command works in batch programming

I'm have started to learn batch programming to go a little more deeper in the Windows Machines. In Internet I have seen some commands with echo like "#echo off" or "on" and also this: "echo." but I don't know what are they doing. If anyone can explain me the functions of the echo command, please answer me.
The ECHO command in Windows CMD is used to print out text to the screen, display the actual setting of the Command-line which means when you do:
#echo off
The "C:\Users[User]" line before your command input will disappear.
You can restore it with:
#echo on
Here all functions of ECHO explained:
#echo [on/off] (to set the command-line settings)
echo [text] (to print text to the screen)
echo. (to print an empty line to the screen)
echo (displays the current setting of the command-line)
echo /? (displays help for the command ECHO)
I hope I was able to help you.
By default, every command executed in a batch file is also echoed to the output - not just the output of the command, but the command itself.
The echo command has three modes:
When called with some arguments AFTER a space, it will output the arguments.
When called as echo. (no space) it will output just a blank line.
When called with just the arguments on or off, it controls the behaviour of printing the commands as they execute.
So, the echo off command turns off the output at the start of the batch file. However, that command itself is still echoed before it has a chance to turn off the echoing. The # symbol has the effect of turning off the output for only the current command.
Combining the two, the #echo off at the start of a batch file turns off the echoing without itself being echoed.
It follows from this that if you try to echo just the word off, without quotes, it will turn off command printing instead. If you try to work around this by quoting the word "off", the output will include the quotes.
The answer, thanks to #JeffZeitlin's comment below, is:
In the presumably unusual case of wanting to echo just the word off or on (i.e., not a part of any other string), it turns out that echo.off and echo.on do the trick.
Partial answer is this: What does "#" mean in Windows batch scripts
The # before the command means do not print that command when running it.
The off argument tells the script not output any other commands, however without the # would output the echo off (since echoing hasn't yet been turned off)
The on argument turns command echoing back on.
Any other arguments are just echoed to the display

How do I conditionally turn #ECHO ON/OFF in a Windows batch file?

I have a batch file that can be run locally or on the build server to do some kind of interesting job. On the local machine, I want #ECHO OFF so that your console isn't full of debug strings. On the build server, I'd rather have #ECHO ON so that failures can be investigated.
The build server context can be determined if a special environment variable exists (TEAMCITY_PROJECT_NAME). So, I thought I could do something like
IF DEFINED TEAMCITY_PROJECT_NAME #ECHO ON ELSE #ECHO OFF
But, that's not working, I get the IF statement echoed along with everything else... any thoughts?
The command is not actually named #echo. The # is an optional flag valid at the beginning of a statement to indicate that echo is suppressed for that statement. So using the # on each reference to the echo command will hide the echo command itself, but not the if command that precedes it.
But the issue that is actually causing your command to fail to operated as expected is that the first echo command will slurp up the rest of the tokens on the line as its arguments, so the else echo off part will not be interpreted by CMD. Since CMD.EXE echoes by default it prints the if command, and then either executes a single echo command or nothing. Since the # does have meaning at the start of the statement that is the body of the if, neither echo command would be printed.
In general, the solution to that is to use parenthesis to delimit the command boundaries. However, since echo is on by default and you really only want to suppress it if TEAMCITY_PROJECT is not define, we can say that directly.
#IF NOT DEFINED TEAMCITY_PROJECT_NAME ECHO OFF
I've left a single # at the beginning to suppress echo of this line, and as a result this line will not appear in your server's logs.
Related note
The echo state also applies to an interactive session. Typing echo off at an interactive CMD session prompt will cause it to stop showing the prompt. Which is a tad disconcerting, if not expected. Typing echo on will restore normal behavior.
More on Parsing
(I've edited the earlier section for clarity, and added this section to attempt to document what is really happening.)
The CMD.EXE program that interprets .BAT and .CMD scripts, and provides the interactive command prompt in modern Windows is surprisingly well documented while effectively being undocumented. My attempts to search for an official document explaining that the at-sign has this effect and exactly where it can be used have been largely unsuccessful.
It is clear from experimentation that the at-sign is parsed and interpreted if it appears as the first non whitespace character of a command. I've tried to express that by using the phrase "beginning of a statement" above.
As far as I can tell there is no formal grammar published for CMD's language. But we do know from the documentation for CDM itself (type cmd /? to a prompt) as well as if /? and the built-in help text for other "interesting" built-in commands that there are rules that are followed when CMD parses its source text.
The start of a command appears to be at the beginning of a line, or after the conditional of an if, after the else, or after an open parenthesis (. Once the at-sign has been recognized, it applies to (most) of the balance of that command.
Try the following batch file yourself, and play with moving the at signs around and you'll quickly get the sense that the rules are hard to state precisely:
rem this remark will echo
#rem this one will not
# rem neither will this
#rem nor this one
#rem the if command (and else) will echo, but not either echo command
if exist q17241089.bat ( # echo saw q17241089.bat ) else # echo foo
if not exist q17241089.bat ( # echo no q17241089.bat ) else # echo bar
# rem none of the following command is echoed
# if exist q17241089.bat ( # echo saw q17241089.bat ) else # echo spam
When run on my Win 7 Pro I see:
C...>
C...>q17241089.bat
C...>rem this remark will echo
C...>if exist q17241089.bat () else
saw q17241089.bat
C...>if not exist q17241089.bat () else
bar
saw q17241089.bat
C...>
As with most of the fine details of BAT files, there is a mystery underneath any surface you scratch.
set parentheses (see docu...):
IF DEFINED TEAMCITY_PROJECT_NAME (#ECHO ON) ELSE #ECHO OFF
Well, it seems that ECHO is on by default, so you'll get the IF statement output anyway you test that conditional. You should start with
#ECHO OFF
And add your conditional as another statement to evaluate
#ECHO OFF && IF DEFINED TEAMCITY_PROJECT_NAME #ECHO ON
As noted in earlier responses, ECHO is on by default. An alternative way of achieving the same behavior is:
#if "%TEAMCITY_PROJECT_NAME%" == "" #echo off

What does "#" mean in Windows batch scripts

I saw # is used in such contexts:
#echo off
#echo start eclipse.exe
What does # mean here?
It means not to output the respective command. Compare the following two batch files:
#echo foo
and
echo foo
The former has only foo as output while the latter prints
H:\Stuff>echo foo
foo
(here, at least). As can be seen the command that is run is visible, too.
echo off will turn this off for the complete batch file. However, the echo off call itself would still be visible. Which is why you see #echo off in the beginning of batch files. Turn off command echoing and don't echo the command turning it off.
Removing that line (or commenting it out) is often a helpful debugging tool in more complex batch files as you can see what is run prior to an error message.
It means "don't echo the command to standard output".
Rather strangely,
echo off
will send echo off to the output! So,
#echo off
sets this automatic echo behaviour off - and stops it for all future commands, too.
Source: http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/batch.mspx?mfr=true
By default, a batch file will display its command as it runs. The purpose of this first command which #echo off is to turn off this display. The command "echo off" turns off the display for the whole script, except for the "echo off" command itself. The "at" sign "#" in front makes the command apply to itself as well.
The # disables echo for that one command. Without it, the echo start eclipse.exe line would print both the intended start eclipse.exe and the echo start eclipse.exe line.
The echo off turns off the by-default command echoing.
So #echo off silently turns off command echoing, and only output the batch author intended to be written is actually written.
It inherits the meaning from DOS.
From the '#' section of Technical Notes > Programming > Batch File Commands (archived version):
#: In DOS version 3.3 and later, hides the echo of a batch command. Any output generated by the command is echoed.
The at-sign can be prefixed to any DOS command, program name, or batch file name within a batch file.
Without it, you could turn off command echoing using the echo off command, but that command would be echoed first.
In batch file:
1 #echo off(solo)=>output nothing
2 echo off(solo)=> the “echo off” shows in the command line
3 echo off(then echo something) =>
4 #echo off(then echo something)=>
See, echo off(solo), means no output in the command line, but itself shows;
#echo off(solo), means no output in the command line, neither itself;
Another useful time to include # is when you use FOR in the command line. For example:
FOR %F IN (*.*) DO ECHO %F
Previous line show for every file: the command prompt, the ECHO command, and the result of ECHO command. This way:
FOR %F IN (*.*) DO #ECHO %F
Just the result of ECHO command is shown.
you can include # in a 'scriptBlock' like this:
#(
echo don't echoed
hostname
)
echo echoed
and especially do not do that :)
for %%a in ("#") do %%~aecho %%~a

How do I run two commands in one line in Windows CMD?

I want to run two commands in a Windows CMD console.
In Linux I would do it like this
touch thisfile ; ls -lstrh
How is it done on Windows?
Like this on all Microsoft OSes since 2000, and still good today:
dir & echo foo
If you want the second command to execute only if the first exited successfully:
dir && echo foo
The single ampersand (&) syntax to execute multiple commands on one line goes back to Windows XP, Windows 2000, and some earlier NT versions. (4.0 at least, according to one commenter here.)
There are quite a few other points about this that you'll find scrolling down this page.
Historical data follows, for those who may find it educational.
Prior to that, the && syntax was only a feature of the shell replacement 4DOS before that feature was added to the Microsoft command interpreter.
In Windows 95, 98 and ME, you'd use the pipe character instead:
dir | echo foo
In MS-DOS 5.0 and later, through some earlier Windows and NT versions of the command interpreter, the (undocumented) command separator was character 20 (Ctrl+T) which I'll represent with ^T here.
dir ^T echo foo
A quote from the documentation:
Source: Microsoft, Windows XP Professional Product Documentation, Command shell overview
Also: An A-Z Index of Windows CMD commands
Using multiple commands and conditional processing symbols
You can run multiple commands from a single command line or script using conditional processing symbols. When you run multiple commands with conditional processing symbols, the commands to the right of the conditional processing symbol act based upon the results of the command to the left of the conditional processing symbol.
For example, you might want to run a command only if the previous command fails. Or, you might want to run a command only if the previous command is successful.
You can use the special characters listed in the following table to pass multiple commands.
& [...]
command1 & command2
Use to separate multiple commands on one command line. Cmd.exe runs the first command, and then the second command.
&& [...]
command1 && command2
Use to run the command following && only if the command preceding the symbol is successful. Cmd.exe runs the first command, and then runs the second command only if the first command completed successfully.
|| [...]
command1 || command2
Use to run the command following || only if the command preceding || fails. Cmd.exe runs the first command, and then runs the second command only if the first command did not complete successfully (receives an error code greater than zero).
( ) [...]
(command1 & command2)
Use to group or nest multiple commands.
; or ,
command1 parameter1;parameter2
Use to separate command parameters.
& is the Bash equivalent for ; ( run commands) and && is the Bash equivalent of && (run commands only when the previous has not caused an error).
If you want to create a cmd shortcut (for example on your desktop) add /k parameter (/k means keep, /c will close window):
cmd /k echo hello && cd c:\ && cd Windows
You can use & to run commands one after another. Example: c:\dir & vim myFile.txt
You can use call to overcome the problem of environment variables being evaluated too soon - e.g.
set A=Hello & call echo %A%
A number of processing symbols can be used when running several commands on the same line, and may lead to processing redirection in some cases, altering output in other case, or just fail. One important case is placing on the same line commands that manipulate variables.
#echo off
setlocal enabledelayedexpansion
set count=0
set "count=1" & echo %count% !count!
0 1
As you see in the above example, when commands using variables are placed on the same line, you must use delayed expansion to update your variable values. If your variable is indexed, use CALL command with %% modifiers to update its value on the same line:
set "i=5" & set "arg!i!=MyFile!i!" & call echo path!i!=%temp%\%%arg!i!%%
path5=C:\Users\UserName\AppData\Local\Temp\MyFile5
cmd /c ipconfig /all & Output.txt
This command execute command and open Output.txt file in a single command
So, I was trying to enable the specific task of running RegAsm (register assembly) from a context menu. The issue I had was that the result would flash up and go away before I could read it. So I tried piping to Pause, which does not work when the command fails (as mentioned here Pause command not working in .bat script and here Batch file command PAUSE does not work). So I tried cmd /k but that leaves the window open for more commands (I just want to read the result). So I added a pause followed by exit to the chain, resulting in the following:
cmd /k C:\Windows\Microsoft.NET\Framework\v4.0.30319\regasm.exe "%1" /codebase \"%1\" & pause & exit
This works like a charm -- RegAsm runs on the file and shows its results, then a "Press any key to continue..." prompt is shown, then the command prompt window closes when a key is pressed.
P.S. For others who might be interested, you can use the following .reg file entries to add a dllfile association to .dll files and then a RegAsm command extension to that (notice the escaped quotes and backslashes):
[HKEY_CLASSES_ROOT\.dll]
"Content Type"="application/x-msdownload"
#="dllfile"
[HKEY_CLASSES_ROOT\dllfile]
#="Application Extension"
[HKEY_CLASSES_ROOT\dllfile\Shell\RegAsm]
#="Register Assembly"
[HKEY_CLASSES_ROOT\dllfile\Shell\RegAsm\command]
#="cmd /k C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\regasm.exe \"%1\" /codebase \"%1\" & pause & exit"
Now I have a nice right-click menu to register an assembly.
In windows, I used all the above solutions &, && but nothing worked
Finally ';' symbol worked for me
npm install; npm start
Well, you have two options: Piping, or just &:
DIR /S & START FILE.TXT
Or,
tasklist | find "notepad.exe"
Piping (|) is more for taking the output of one command, and putting it into another. And (&) is just saying run this, and that.
In order to execute two commands at the same time, you must put an & (ampersand) symbol between the two commands. Like so:
color 0a & start chrome.exe
Cheers!
I try to have two pings in the same window, and it is a serial command on the same line. After finishing the first, run the second command.
The solution was to combine with start /b on a Windows 7 command prompt.
Start as usual, without /b, and launch in a separate window.
The command used to launch in the same line is:
start /b command1 parameters & command2 parameters
Any way, if you wish to parse the output, I don't recommend to use this.
I noticed the output is scrambled between the output of the commands.
Use & symbol in windows to use command in one line
C:\Users\Arshdeep Singh>cd Desktop\PROJECTS\PYTHON\programiz & jupyter notebook
like in linux
we use,
touch thisfile ; ls -lstrh
I was trying to create batch file to start elevated cmd and to make it run 2 separate commands.
When I used & or && characters, I got a problem. For instance, this is the text in my batch file:
powershell.exe -Command "Start-Process cmd \"/k echo hello && call cd C:\ \" -Verb RunAs"
I get parse error:
After several guesses I found out, that if you surround && with quotes like "&&" it works:
powershell.exe -Command "Start-Process cmd \"/k echo hello "&&" call cd C:\ \" -Verb RunAs"
And here's the result:
May be this'll help someone :)
No, cd / && tree && echo %time%. The time echoed is at when the first command is executed.
The piping has some issue, but it is not critical as long as people know how it works.
One more example: For example, when we use the gulp build system, instead of
gulp - default > build
gulp build - build build-folder
gulp watch - start file-watch
gulp dist - build dist-folder
We can do that with one line:
cd c:\xampp\htdocs\project & gulp & gulp watch
Yes there is. It's &.
&& will execute command 2 when command 1 is complete providing it didn't fail.
& will execute regardless.
With windows 10 you can also use scriptrunner:
ScriptRunner.exe -appvscript demoA.cmd arg1 arg2 -appvscriptrunnerparameters -wait -timeout=30 -rollbackonerror -appvscript demoB.ps1 arg3 arg4 -appvscriptrunnerparameters -wait -timeout=30 -rollbackonerror
it allows you to start few commands on one line you want you can run them consecutive or without waiting each other, you can put timeouts and rollback on error.
Try to create a .bat ot .cmd file with those lines using doskey key and $T which is equivalent to & to do several command line in just one line :
touch=echo off $T echo. ^> $* $T dir /B $T echo on
It'll create an empty file.
Example:
touch myfile
In cmd you'll get something like this:
But as mentioned previously by others, it is really advised to use & operator to do many command line in one line from CMD prompt.
Enjoy =)
When you try to use or manipulate variables in one line beware of their content! E.g. a variable like the following
PATH=C:\Program Files (x86)\somewhere;"C:\Company\Cool Tool";%USERPROFILE%\AppData\Local\Microsoft\WindowsApps;
may lead to a lot of unhand-able trouble if you use it as %PATH%
The closing parentheses terminate your group statement
The double quotes don't allow you to use %PATH% to handle the parentheses problem
And what will a referenced variable like %USERPROFILE% contain?
It's simple: just differentiate them with && signs.
Example:
echo "Hello World" && echo "GoodBye World".
"Goodbye World" will be printed after "Hello World".

Resources