11

In bash, we can:

python - << EOF
import os
print 'hello'
EOF

to embed python code snippet in bash script.

But in Windows batch, this doesn't work - although I can still use python -c but that requires me to collpase my code into one line, which is something I try to avoid.

Is there a way to achieve this in batch script?

Thanks.

Baiyan Huang
  • 6,463
  • 8
  • 45
  • 72
  • 1
    This is related to the more general question of replicating [heredoc functionality in MS-DOS](http://stackoverflow.com/questions/1015163/heredoc-for-windows-batch). – Miklos Aubert Jul 04 '13 at 10:09
  • @djf it is not clear how would that question solves my problem particuraly - that is about write to a file, I want it get read into python intepreter – Baiyan Huang Jul 04 '13 at 12:16
  • Heredoc _could_ be one solution, but a pure hybrid could also be a solution, so it isn't a duplicate – jeb Jul 04 '13 at 12:21
  • You can do the almost 100% same script with **more +1 %0 | python & exit /b** on the first line. Definitely not the same question. Just same for some answers! – joojaa Jul 04 '13 at 12:23

6 Answers6

19

You could use a hybrid technic, this solution works also with an python import.

1>2# : ^
'''
@echo off
echo normal 
echo batch code
echo Switch to python
python "%~f0"
exit /b
rem ^
'''
print "This is Python code"

The batch code is in a multiline string ''' so this is invisible for python.
The batch parser doesn't see the python code, as it exits before.

The first line is the key.
It is valid for batch as also for python!
In python it's only a senseless compare 1>2 without output, the rest of the line is a comment by the #.

For batch 1>2# is a redirection of stream 1 to the file 2#.
The command is a colon : this indicates a label and labeled lines are never printed.
Then the last caret simply append the next line to the label line, so batch doesn't see the ''' line.

jeb
  • 78,592
  • 17
  • 171
  • 225
  • It's another solution, but if it's nicer? That's a matter of opinion. – jeb Jul 04 '13 at 12:25
  • well yes it a opinion but its shorter if you put this on first line: **more +1 %~f0 | python & exit /b** (or expand %0 for better results) Anyway its more generically the same as the script provided and it does not need to rely on python specific syntax so it would work for any language the same. – joojaa Jul 04 '13 at 12:28
  • @lzprgmr `%~f0` stands for the full path to the current batch file – jeb Jul 04 '13 at 13:23
  • “For batch 1>2# is a redirection of stream 1 to the file 2#.” - this should be a null-op whose purpose is to comment out the next line ''', right? – Baiyan Huang Jul 04 '13 at 22:09
  • 1
    No, for batch the colon is the null op, this _removes_ also the `1>2#` so that the batch doesn't try to open the file `2#` – jeb Jul 05 '13 at 11:33
  • why there is rem ^ ? this batch code is never executed, so no need to worry about batch code below exit / b – rmflow Aug 07 '18 at 13:23
19

Even more efficient, plus it passes all command-line arguments to and returns the exit code from the script:

@SETLOCAL ENABLEDELAYEDEXPANSION & python -x "%~f0" %* & EXIT /B !ERRORLEVEL!
# Your python code goes here...

Here's a break-down of what's happening:

  • @ prevents the script line from being printed
  • SETLOCAL ENABLEDELAYEDEXPANSION allows !ERRORLEVEL! to be evaluated after the python script runs
  • & allows another command to be run on the same line (similar to UNIX's ;)
  • python runs the python interpreter (Must be in %PATH%)
  • -x tells python to ignore the first line (Run python -h for details)
  • "%~f0" expands to the fully-qualified path of the currently executing batch script (Argument %0). It's quoted in case the path contains spaces
  • %* expands all arguments passed to the script, effectively passing them on to the python script
  • EXIT /B tells Windows Batch to exit from the current batch file only (Using just EXIT would cause the calling interpreter to exit)
  • !ERRORLEVEL! expands to the return code from the previous command after it is run. Used as an argument to EXIT /B, it causes the batch script to exit with the return code received from the python interpreter

NOTE: You may have to change "python" to something else if your python binary is not in the PATH or is in a non-standard location. For example:

@"C:\Path\To\Python.exe" -x ...
apresence
  • 310
  • 2
  • 5
  • Nice! When you change the first line to `@python -x "%~f0" %* & call set "EL=%%ErrorLevel%%" & setlocal EnableDelayedExpansion & exit /B !EL!` you can avoid problems with script paths (`%~f0`) or arguments (`%*`) that contain `!`-signs, which are otherwise consumed by delayed expansion (given that it is off by default). Unfortunately, `@python -x "%~f0" %* & call exit /B %ErrorLevel%` does not work as it always returns an exit code of `0` (most likely because the `call` of `exit`) succeeds)… – aschipfl Mar 12 '21 at 15:09
1

For a simple command: Windows cmd prompt only understands double quotes so you can triple them for this to work with -c option:

python -c print("""Hi""")

or use simple quotes which aren't interpreted at all by windows cmd:

python -c print('Hi')
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
0

Unhook yourself from the idea that you have to immediately specify the python script in the batch file. Place the python code in a separate file and then simply launch the separate file under python from the Windows batch file.

Michael Karas
  • 248
  • 2
  • 12
  • 2
    this is something I try to avoid - I want to keep concise by only delivering 1 script, not 2 – Baiyan Huang Jul 04 '13 at 11:56
  • 1
    There are certainly occasions when it is only feasible to deliver a single file without going to a great deal of trouble. Being able to combine two things into one is a pretty valuable feature. – mojo Feb 01 '18 at 15:06
0

The only way I know of to do this is to call the Python script from the batch file like this:

@echo off
REM whatever other code
python python_script.py
REM rest of batch file...
Brae
  • 484
  • 1
  • 3
  • 15
0
@echo off
setlocal EnableDelayedExpansion

set "fTmpPy=%TEMP%\~fTmp.py"

call :getLine "::python_beg" "::python_end" > "!fTmpPy!"

python "!fTmpPy!"

pause
exit /b 0

:getLine <beg str> <end str>
  set "bBegEnd=0"
  for /f "usebackq delims=" %%l in ("%~f0") do (
    if !bBegEnd! equ 1 (
      if "%%l" equ "%~2" ( exit /b 0 )
      setlocal DisableDelayedExpansion
      echo %%l
      endlocal
    ) else (
      if "%%l" equ "%~1" ( set "bBegEnd=1" )
    )
  )
exit /b 0

endlocal

::mark is unique - one character or string
::begin mark must be
::end mark is optional if it is on eof

::python_beg
print( 'Hello, world!' )
::python_end

It is possible of including any character or string, but only thing '!' exclamation is exceptinal. '!' is used for variable, so you have to set 'DisableDelayedExpansion'. At the end, you have to set 'endlocal'. That's all.

You can use this routine for any script or any document. Even though cmd script...

kygg
  • 21
  • 3