Self-executable scripts

From IronPython Cookbook

To run IronPython scripts on Windows, you have to ask the IronPython interpreter (ipy.exe) to invoke them. As a result, you always need to type ipy SCRIPT_PATH , which can be rather annoying and you have to instruct users of your script to do the same. To make the scripts self-executable so that you can just type the name (even skip the extension) and go, you need to simply do two things:

  • Rename the script extension from .py to .cmd or .bat.
  • Make the first line of the script read exactly:
@setlocal enabledelayedexpansion && ipy -x %~f0 %*& exit /b !ERRORLEVEL!

For example, put the following into a file called hello.cmd:

@setlocal enabledelayedexpansion && ipy -x %~f0 %*& exit /b !ERRORLEVEL!
# Python code starts from here, second line onwards
import sys
args = sys.argv[1:]
if args:
    print 'You said:', ' '.join(args)
print 'I say, greetings!'

Now you can just type hello and the script runs!

The way this works is that the whole script is treated like a Windows shell (batch/command) script. The first line invokes the IronPython interpreter, which will be ideally in your PATH, and passes the absolute path to the file (%~f0) along with all arguments (%*). The -x switch tells the interpreter to skip the first line of the script so that it does not trump on it, being illegal Python code. When the interpreter returns, exit /b !ERRORLEVEL! will make sure that rest of the now-back-to-shell-script is skipped because the Python code makes for illegal shell commands.

Both worlds are hip and happy!

Full Breakdown

Here is a break down of what the first line does:

@setlocal enabledelayedexpansion && ipy -x %~f0 %* & exit /b !ERRORLEVEL!

It is actually three shell script statements compressed into one line because that is all the interpreter affords us. You could almost read it as if it was written as:

setlocal enabledelayedexpansion
ipy -x %~f0 %*
exit /b !ERRORLEVEL!

The starting @ prevents the line itself from being echoed.

The second statement is the simplest to understand. It invokes the IronPython interpreter, telling it to skip the first line via the -x switch. The third statement, exit /b !ERRORLEVEL! prevents the rest of (illegal) shell script from running by exiting immediately. More importantly, however, it propagates the exit code from the Python script out to the caller of the shell script. This is important if the caller is going to use the exit code, the usual practice, as an indicator of success or failure. Upon success (exit code zero), the caller may want carry out further actions like send out an e-mail or delete some files. You don't want that to happen unless the script did, in fact, succeed!

The first statement, setlocal enabledelayedexpansion, affects how the third one is interpreted. It makes sure that the special ERRORLEVEL environment variable holding the exit code of the last executed command is expanded when exit will be executed, not when the line is read by the command processor (cmd.exe), the default behavior with %ERRORLEVEL%. Had exit /b %ERRORLEVEL% been used instead, it would have always returned zero as the exit code because that would have been the value of ERRORLEVEL when the entire first line is read by the command processor. !ERRORLEVEL!, on the other hand, will use the value in effect after the IronPython interpreter returns.

Note: The same technique can be applied to CPython on Windows using the same -x flag.

Back to Contents.