Use shebang/hashbang in Windows Command Prompt - windows

I'm currently using the serve script to serve up directories with Node.js on Windows 7. It works well in the MSYS shell or using sh, as I've put node.exe and the serve script in my ~/bin (which is on my PATH), and typing just "serve" works because of it's Shebang (#!) directive which tells the shell to run it with node.
However, Windows Command Prompt doesn't seem to support normal files without a *.bat or *.exe extension, nor the shebang directive. Are there any registry keys or other hacks that I can get to force this behavior out of the built-in cmd.exe?
I know I could just write up a simple batch file to run it with node, but I was wondering if it could be done in a built-in fasion so I don't have to write a script for every script like this?
Update: Actually, I was thinking, is it possible to write a default handler for all 'files not found' etc. that I could automatically try executing within sh -c?
Thanks.

Yes, this is possible using the PATHEXT environment variable. Which is e.g. also used to register .vbs or .wsh scripts to be run "directly".
First you need to extend the PATHEXT variable to contain the extension of that serve script (in the following I assume that extension is .foo as I don't know Node.js)
The default values are something like this:
PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
You need to change it (through the Control Panel) to look like this:
PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.FOO
Using the control panel (Control Panel -> System -> Advanced System Settings -> Environment Variables is necessary to persist the value of the PATHEXT variable.
Then you need to register the correct "interpreter" with that extension using the commands FTYPE and ASSOC:
ASSOC .foo=FooScript
FTYPE FooScript=foorunner.exe %1 %*
(The above example is shamelessly taken from the help provided by ftype /?.)
ASSOC and FTYPE will write directly into the registry, so you will need an administrative account to run them.

Command prompt does not support shebang , however there are a lot hybrid techniques for different languages that you allow to combine batch and other languages syntax in one file.As your question concerns node.js here's a batch-node.js hybrid (save it with .bat or .cmd extension):
0</* :{
#echo off
node %~f0 %*
exit /b %errorlevel%
:} */0;
console.log(" ---Self called node.js script--- ");
console.log('Press any key to exit');
process.stdin.setRawMode(true);
process.stdin.resume();
process.stdin.on('data', process.exit.bind(process, 0));
It is possible to be done with many other languages like Ruby,Perl,Python,PHP and etc.

Here is a simple way to force windows to support shebang however it has a caveat regarding the file naming. Copy the following text in to a batch file and follow general idea in REM comments.
#echo off
REM This batch file adds a cheesy shebang support for windows
REM Caveat is that you must use a specific extension for your script files and associate that extension in Windows with this batch program.
REM Suggested extension is .wss (Windows Shebang Script)
REM One method to still easily determine script type visually is to use double extensions. e.g. script.pl.wss
setlocal enableextensions disabledelayedexpansion
if [%1] == [] goto usage
for /f "usebackq delims=" %%a IN (%1) do (
set shebang=%%a
goto decode_shebang
)
:decode_shebang
set parser=%shebang:~2%
if NOT "#!%parser%" == "%shebang%" goto not_shebang
:execute_script
"%parser%" %*
set exit_stat=%errorlevel%
echo script return status: %exit_stat%
goto finale
:not_shebang
echo ERROR script first line %shebang% is not a valid shebang
echo maybe %1 is not a shebanged script
goto finale
:usage
echo usage: %0 'script with #! shebang' [scripts args]+
echo This batch file will inspect the shebang and extract the
echo script parser/interpreter which it will call to run the script
:finale
pause
exit /B %exit_stat%

No, there's no way to "force" the command prompt to do this.
Windows simply wasn't designed like Unix/Linux.
Is there a shell extension that does something similar?
Not that I've heard of, but that should be asked on Super User, not here.

There's no way to execute random file, unless it is an actual executable binary file. Windows CreateProcess() function just not designed for it. The only files it can execute are those with MZ magic or with extensions from %PATHEXT% list.
However, CMD itself has a limited support for custom interpreters through EXTPROC clause. The limitation is that interpreter should also support and omit this clause in its execution.

#npocmaka Thanks for the hint! After some trial and error I found the equivalent for a batch/php hybrid is as follows:
<?/** :
#echo off
C:\tools\php81\php.exe -d short_open_tag=On %~f0 %*
exit /b
*/ ?>
<?php
header('Location: example.com/');
print("<body><h1>Hello PHP!<h1></body>");
?>

Related

Command Prompt: Execute Commands from Any Text File (Not Having ".bat" or ".cmd" Extensions)

I can't find any way to do, for example, the following:
cmd.exe /C "script.txt"
In other words, I need Command Prompt to (try) to execute file with any extension (not necessarily .bat or .cmd) if it contains valid batch script code. I'm looking for behavior similar to Unix shells:
./script.txt
While on Unix the shebang (#!/bin/sh) is responsible for understanding that the file is actually a script, it seems like on Windows .bat or .cmd extensions play the same role, indicating a batch script file for Command Prompt.
Is it possible to avoid that and force Command Prompt to interpret a file with any name?
NOTE: Please, no answers like:
Give your file .bat or .cmd extension.
That's not what the question is about.
This depends on the complexity of the NON-Batch file. If the NON-Batch file does not use these facilities:
Access to Batch file parameters via %1 %2 ... and execution of SHIFT command.
Execution of GOTO command.
Execution of CALL :NAME command (internal subroutine).
Execution of SETLOCAL/ENDLOCAL commands.
then you may execute any file as a "Batch file" via this trick:
cmd < anyFile.ext
Further details at this post
you first need an "installation" script :
#echo off
rem :: A files with .TEST extension will be able to execute batch code but is not perfect as the %0 argument is lost
rem :: "installing" a caller.
if not exist "c:\caller.bat" (
echo #echo off
echo copy "%%~nx1" "%%temp%%\%%~nx1.bat" /Y ^>nul
echo "%%temp%%\%%~nx1.bat" %%*
) > c:\caller.bat
rem :: associating file extension
assoc .test=batps
ftype batps=c:\caller "%%1" %*
then try a simple .test file:
#echo off
for /l (1;1;10) do (
echo testing .TEST extension
)
In fact ASSOC and FTYPE both have immediate effect so you can start a .test file right after "installation". With direct editing of the registry eventually you can get more control -> How to create file extension that behaves as .cmd/.bat? . Check also drop handlers -> http://msdn.microsoft.com/en-us/library/windows/desktop/cc144165%28v=vs.85%29.aspx

Check whether command is available in batch file

I'm writing a batch file for Windows and use the command 7z (7-Zip). I have put the location of it in the PATH. Is there a relatively easy way to check whether the command is available?
Do not execute the command to check its availability (i.e., found in the PATH environment variable). Use where instead:
where 7z.exe >nul 2>nul
IF NOT ERRORLEVEL 0 (
#echo 7z.exe not found in path.
[do something about it]
)
The >nul and 2>nul prevent displaying the result of the where command to the user. Executing the program directly has the following issues:
Not immediately obvious what the program does
Unintended side effects (change the file system, send emails, etc.)
Resource intensive, slow startup, blocking I/O, ...
You can also define a routine, which can help users ensure their system meets the requirements:
rem Ensures that the system has a specific program installed on the PATH.
:check_requirement
set "MISSING_REQUIREMENT=true"
where %1 > NUL 2>&1 && set "MISSING_REQUIREMENT=false"
IF "%MISSING_REQUIREMENT%"=="true" (
echo Download and install %2 from %3
set "MISSING_REQUIREMENTS=true"
)
exit /b
Then use it such as:
set "MISSING_REQUIREMENTS=false"
CALL :check_requirement curl cURL https://curl.haxx.se/download.html
CALL :check_requirement svn SlikSVN https://sliksvn.com/download/
CALL :check_requirement jq-win64 jq https://stedolan.github.io/jq/download/
IF "%MISSING_REQUIREMENTS%"=="true" (
exit /b
)
PowerShell:
On PowerShell, the Get-Command cmdlet can be considered to be the equivalent of cmd's where.exe.
Get-Command <cmd>
IF ($? -ne $true)
{
Write-Host "<cmd> not found in path"
# Do something about it
}
Yup:
#echo off
set found=
set program=7z.exe
for %%i in (%path%) do if exist %%i\%program% set found=%%i
echo "%found%"
An attempt to execute 7z.exe will return an %errorlevel% of 9009 if the command is not found. You can check that.
7z.exe
if %errorlevel%==9009 echo Command Not Found
Note: This solution is viable for this specific 7zip use case, and likely for plenty of others. But as a general rule, executing a command to determine whether it's present could potentially be harmful. So make sure you understand the effect of executing the command you're checking for, and use your discretion with this approach.
Yes, open a command window and type "7z" (I assume that is the name of the executable). If you get an error saying that the command or operation is not recognised then you know the path statement has a problem in it somewhere, otherwise it doesn't.

Finding the path of the program that will execute from the command line in Windows

Say I have a program X.EXE installed in folder c:\abcd\happy\ on the system. The folder is on the system path. Now suppose there is another program on the system that's also called X.EXE but is installed in folder c:\windows\.
Is it possible to quickly find out from the command line that if I type in X.EXE which of the two X.EXE's will get launched? (but without having to dir search or look at the process details in Task Manager).
Maybe some sort of in-built command, or some program out there that can do something like this? :
detect_program_path X.EXE
Use the where command. The first result in the list is the one that will execute.
C:\> where notepad
C:\Windows\System32\notepad.exe
C:\Windows\notepad.exe
According to this blog post, where.exe is included with Windows Server 2003 and later, so this should just work with Vista, Win 7, et al.
On Linux, the equivalent is the which command, e.g. which ssh.
As the thread mentioned in the comment, get-command in powershell can also work it out. For example, you can type get-command npm and the output is as below:
Here's a little cmd script you can copy-n-paste into a file named something like where.cmd:
#echo off
rem - search for the given file in the directories specified by the path, and display the first match
rem
rem The main ideas for this script were taken from Raymond Chen's blog:
rem
rem http://blogs.msdn.com/b/oldnewthing/archive/2005/01/20/357225.asp
rem
rem
rem - it'll be nice to at some point extend this so it won't stop on the first match. That'll
rem help diagnose situations with a conflict of some sort.
rem
setlocal
rem - search the current directory as well as those in the path
set PATHLIST=.;%PATH%
set EXTLIST=%PATHEXT%
if not "%EXTLIST%" == "" goto :extlist_ok
set EXTLIST=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH
:extlist_ok
rem - first look for the file as given (not adding extensions)
for %%i in (%1) do if NOT "%%~$PATHLIST:i"=="" echo %%~$PATHLIST:i
rem - now look for the file adding extensions from the EXTLIST
for %%e in (%EXTLIST%) do #for %%i in (%1%%e) do if NOT "%%~$PATHLIST:i"=="" echo %%~$PATHLIST:i

Why is QProcess converting the '=' in my arguments to spaces

I've run into a weird error with a Qt program running on Windows. The program uses QProcess to spawn a child process wit two arguments. The program and arguments passed to the QProcess::start() method are of the form:
"batchfile.bat" "--option1=some_value" "--option2=some_other_value\with_a\path"
For some reason by the time those options get to the batchfile for processing the equals signs have been converted to spaces and it now looks like:
"batchfile.bat" "--option1 some_value" "--option2 some_other_value\with_a\path"
because of this, the processing fails. Any ideas what could be causing the equal signs to be replaced by spaces? I'm using the mingw build of the QT 4.6.3 framework found on the Qt download page.
EDIT:
Here's the actual code. I didn't write it (I'm a complete Qt noob) but I've got to try to get it working. It's part of an automated build system that runs on two versions of RHEL (4 and 5), OS X, and Windows. And it works fine everywhere but Windows.
QProcess sconsProcess;
sconsProcess.setWorkingDirectory(build.getBuildLocation());
sconsProcess.setProcessChannelMode(QProcess::MergedChannels);
qDebug()<<"Starting scons process:"<<build.getSconsLocation()<<QString("--variant=%1-%2").arg(build.getOs()).arg(build.getVariant())<<
QString("--source-release=%1").arg(build.getSettings().getSetting("sourceReleaseLocation", QStringList()<<"BUILDLOCATION"<<"VERSION",
QStringList()<<build.getBuildLocation()<<build.getBuildPackage().getVersion()).toString());
sconsProcess.start(build.getSconsLocation(), QStringList()<<QString("--variant=%1-%2").arg(build.getOs()).arg(build.getVariant())<<
QString("--source-release=%1").arg(build.getSettings().getSetting("sourceReleaseLocation", QStringList()"BUILDLOCATION"<<"VERSION",
QStringList()<<build.getBuildLocation()<<build.getBuildPackage().getVersion()).toString()));
qDebug()<<"Source release build process started";
The actaul values that translates into in Windows (the bit that gets printed out in the first qDebug() print call) is:
DEBUG: Starting scons process: "V:\Glast_Software\Toaster\tools\Python2.5\Scripts\scons-1.3.0.bat" "--variant=Windows-i386-32bit-vc71-Debug" "--source-release=V:\Glast_Software\Toaster\ReleaseManagerBuild\Windows-i386-32bit-vc71\Debug\ScienceTools\LATEST-1-3163\ScienceTools-LATEST-1-3163-source.zip"
However inside the scons-1.3.0.bat (I had it echo all the commands executed) the passed parameters look like:
"--variant Windows-i386-32bit-vc71-Debug" "--source-release V:\Glast_Software\Toaster\ReleaseManagerBuild\Windows-i386-32bit-vc71\Debug\ScienceTools\LATEST-1-3163\ScienceTools-LATEST-1-3163-source.zip"
with the equal signs missing.
EDIT (6/29/10):
I should add that this system is designed to run on a small Windows batch farm using the LSF batch queuing system. It only fails when the process is running as a batch job. When I run this program from the command line on one of the batch machines, it works perfectly and does exactly what it is supposed to do. So maybe it is an environment problem.
There's a good chance that this is because the quotes aren't making it through (they may need to be escaped, see the docs for QProcess::start()).
cmd.exe treats equals signs in command line options that aren't quoted as a separator between arguments similar to a space or tab. Just one of very many bits of oddness in Windows cmd scripting:
C:\test>type c:\util\cmdechoargs.cmd
#echo off
setlocal
set /a i=0
echo args[*]: %*
:loop
if {%1} == {} goto :eof
echo argv[%i%]: %1
set /a i=%i% + 1
shift
goto :loop
C:\test>cmdechoargs testing=123
args[*]: testing=123
argv[0]: testing
argv[1]: 123
C:\test>cmdechoargs "testing=123"
args[*]: "testing=123"
argv[0]: "testing=123"
The best documentation I've come across for how to handle command line arguments in Windows cmd scripts is Tim Hill's "Windows NT Shell Scripting" - get one used for only a penny!
Based on the examples given in your update, I think you might want your options that have equals signs in them to have quotes embedded inside them:
"\"--variant=%1-%2\""
"\"--source-release=%1\""
Edit -- new material
The following script has a routine that will strip the quotes off of an argument passed to a cmd script. The routine returns the 'dequoted' argument in an environment variable named RET using an idiom/technique from Tim Hill's book I mentioned above. I stole some of the dequoting code from an example here: http://ss64.com/nt/syntax-esc.html, but made it a bit more robust to handle empty quotes.
#echo off
setlocal
set /a i=0
echo args[*]: %*
:loop
if {%1} == {} goto :eof
echo.
echo argv[%i%]: %1
call :dequote %1
set dequoted_arg=%RET%
echo argv[%i%] ^(dequoted^): %dequoted_arg%
set /a i=%i% + 1
shift
goto :loop
:dequote
setlocal
SET _string=###%1###
if {%_string%} == {######} goto :dequote_empty
if {%_string%} == {###""###} goto :dequote_empty
SET _string=%_string:"###=%
SET _string=%_string:###"=%
SET _string=%_string:###=%
goto :dequote_done
:dequote_empty
set _string=
:dequote_done
endlocal & (set RET=%_string%) & goto :eof
This kind of thing is why you want to avoid (in my opinion) cmd scripts except for the simplest of tasks. But, I hope this helps you pass unquoted arguments to your scons process through your batch file.
Have you tried escaping the = signs? Also, the paths in your example surely need escaping of the \ character.

Setting a variable from an executable

I am running an executable in a batch file with two parameters;
cmd /k ""executable" "param1" "param2""
This returns a string that I want to launch. I can't figure out how to set this return in a variable and subsequently launch it in IE.
Any ideas?
If the returned string contains a single line you may use FOR /F to set the value of an environment variable. For example:
s1.cmd
echo this is a one line string
s2.cmd
#SETLOCAL
#ECHO OFF
for /f "tokens=*" %%a in ('cmd /c s1.cmd') do set MY_VAR=%%a
echo got: %MY_VAR%
ENDLOCAL
Result
C:\> s2.cmd
got: this is a one line string
C:\>
You can use the following syntax to capture the output of your executable into a variable:
FOR /F "tokens=*" %%i in ('%~dp0YOUR_APP.exe') do SET TOOLOUTPUT=%%i
Source
then you can pass the value on to IE like so:
START "YOUR_WINDOW_NAME" /MAX /D"C:\Program Files\Internet Explorer\" iexplore %TOOLOUTPUT%
I take it that the application code that determines the url is too complicated to be reproduced in a batch file directly, or the source to the executable has been lost. If not I personally would prefer to have the logic visible in the batch file itself.
start %1 %2
Edit: Romulo A. Ceccon posted a much better solution which doesn't involve any file system access and dirty tricks. Left this here for reference (it works with command.com as well if you need 9x compatibility), but please prefer Romulo's solution.
Go through an environment variable you set by using an intermediate helper script you dynamically generate from a template. You will need write permissions somewhere, otherwise it cannot be done (the Windows command shell language is very, very limited.)
Let's call your helper script template helper.tpl with the following contents:
set INTERMEDVAR=
Make sure that helper.tpl has only a single line (no trailing CRLF!) and make sure you don't have any spaces after the equals sign there.
Now, in your main script, capture the output from your command into a temporary file (let's call it my_output_file.tmp):
cmd /k ""executable" "param1" "param2"" > my_output_file.tmp
Then copy the contents of the helper template and the output together into your helper script, let's call it my_helper_script.cmd:
copy /b helper.tpl + my_output_file.tmp my_helper_script.cmd
Then evaluate the helper script in the current context:
call my_helper_script.cmd
Now the INTERMEDVAR variable is set to the first line of the output from "executable" (if it outputs more than one line, you're on your own...) You can now invoke IE:
start iexplore.exe "%INTERMEDVAR%"
And don't forget to clean up the created files:
del /q /f my_output_file.tmp my_helper_script.cmd
This will obviously not work when invoked multiple times in parallel - you'll have to parametrize the temporary file and helper script names using the current cmd.exe's PID (for example) so that they won't overwrite each other's output, but the principle is the same.
However, if you can get a real shell, use that. cmd.exe is extremely cumbersome.

Resources