26

I need to extend a shell script (Bash). As I am much more familiar with Python I want to do this by writing some lines of Python code which depends on variables from the shell script. Adding an extra python file is not an option.

result=`python -c "import stuff; print('all $code in one very long line')"`

is not very readable.

I would prefer to specify my Python code as a multiline string and then execute it.

4 Answers 4

30

Use a here-doc:

result=$(python <<EOF
import stuff
print('all $code in one very long line')
EOF
)
Sign up to request clarification or add additional context in comments.

4 Comments

If one does not need the return value, is it then possible to ommit the "$("?
Yes. That's just the normal use of a here-document.
Note that <<EOF allows injection attacks -- <<'EOF' and requiring variables to be passed in via command line or environment is much safer.
@CharlesDuffy True, but that's a problem in the OP's code as well. I was only showing how to write equivalent code in multi-line format.
9

Thanks to this Stack Overflow answer, I found the answer myself:

#!/bin/bash

# Some Bash code
END_VALUE=10

PYTHON_CODE=$(cat <<END
# Python code starts here
import math

for i in range($END_VALUE):
    print(i, math.sqrt(i))

# Python code ends here
END
)

# Use the
res="$(python3 -c "$PYTHON_CODE")"

# Continue with Bash code
echo "$res"

3 Comments

How to return two variables to "res"? Thanks.
res contains a string with the output. So you have to parse the string. Constructs like a, b = "foo", "bar" are imho not possible in bash.
This is great because it doesn't tie up stdin
2

You can avoid the need for here-doc. In Bash and Z shell (executable zsh), you can use single quotes as in:

export PYTORCH_ENABLE_MPS_FALLBACK=1

python3 -c '
from transformers import pipeline
import torch
device = "mps" if torch.backends.mps.is_available() else "cpu"
pipe = pipeline("sentiment-analysis", device=device)
result = pipe("Nobody expects the Spanish inquisition!")
print(result)
'

❗ make sure to avoid single quotes in the actual python code

6 Comments

The code in the question has a variable $code that needs to be replaced. Variables don't get replaced inside single quotes.
@Barmar you can stack multiple single quote blocks together with double-quotes depending on your needs: python python3 -c ' print('\"$code\"') ' (line ends are chomped in comments)
True. Or more simply python -c "print('$code')". It would be best if you showed it in the answer.
the mixture of single/double quotes hurts the eyes, and python3 -m this says that readability counts ;)
Your comment has a mixture of single and double quotes, and the escaping hurts my eyes even more.
indeed, and that's the point where I'd rather change the shebang from bash to python, and avoid bash entirely, and if by chance you need this for a github action then remember you can use shell: python (available in all ootb runners) and do everything in python, including accessing env vars, but that's a different story..
0

You can simply use a here-document:

result=$(python <<EOF
import stuff

code = "${code}"
print("Hello from Python")
print("Code variable is:", code)
EOF
)

This way the Python code stays readable, and Bash variables like $code are expanded correctly

2 Comments

This is already covered by the accepted answer posted 9 years ago. Please only post unique answers and vote for answers you find useful.
Also, expanding $code is a security bug, not a feature. You don't want a shell variable containing " + os.system("rm -rf ~") + " to run rm. There are safe ways to transport shell variables into embedded Python scripts, but this isn't one of them.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.