'How to feed bytecode to python interpreter from stdin?

I know I can run a python script from stdin like this:

python - < script.py

I also can run a compiled python file:

python script.pyc

But I can't run a compiled python file from stdin:

python - < script.pyc
SyntaxError: Non-UTF-8 code starting with '\xee' in file <stdin> on line 1, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

Clearly I have to tell the interpreter that this is bytecode. But how?



Solution 1:[1]

TL/DR

It's possible with

python -c "import sys;import marshal;exec(marshal.loads(sys.stdin.buffer.read()[16:]))" < script.pyc

Long version

The previous answer is incorrect. It is possible to achieve this via stdin. The Python interpreter has a -c flag that allows it to interpret code. For example,

python -c "print('Hello, world!')"

will output Hello, world! Standard input can be read in a python program using the builtin library sys, specifically the sys.stdin.buffer.read() function. This can only be read once, though, and returns a bytes-like object.

.pyc files have a special structure - 4 magic bytes, a timestamp, and a marshalled code object. Together, the magic bytes and the timestamp are 16 bytes. From what I've found, the timestamp doesn't matter, but the magic bytes change between versions. Removing this, we have a marshalled code object. That's what the [16:] does - removing the magic bytes and the timestamp from the bytes-like object in standard input.

Python uses the marshal library to compress the code object that results from compilation, and provides the marshal.loads(bytes) function to convert the bytes-like object into the unmarshalled object, which is in this case an instance of types.CodeType - a code object.

Finally, while Python's exec() function normally takes a string, it also can take a code object. We pass it the code object, and it executes it.

A word of warning: Passing bytecode directly to standard input and then executing is a huge security issue and poor practice, but given that you're trying this in the first place, you probably don't care.

References:

sys — System-specific parameters and functions — Python 3.9.6 documentation

The structure of .pyc files | Ned Batchelder

Built-in Types — Python 3.9.6 documentation

marshal — Internal Python object serialization — Python 3.9.6 documentation

Built-in Functions — Python 3.9.6 documentation

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1