Hybrid code in shell scripts. Sharing variables

Getting a variable to Python

Since (when the EOF marker is not quoted) variable substitution occurs before text is passed from the heredoc to python's standard input, you can throw the variable right in the script.

python - <<EOF
some_text = "$some_text"
EOF

If some_text was test, python would see some_text = "test". Note however that it can be seen as a code injection vulnerability. If some_text was "; import os; os.system("evil-command"); x = ", for instance, python would see:

some_text = ""; import os; os.system("evil-command"); x = ""

and run that evil command.

If you want to be able to pull your Python code right into a script without any modifications, you could export your variable.

export some_text

and use os.environ to retrieve it.

some_text = os.environ['some_text']

That's a much saner/safer approach.


Getting output from Python

You can use command substitution to collect the script's output.

output=$(
python - <<EOF
import sys;
for r in range(3):
  print r
  for a in range(2):
    print "hello"
EOF
)

(note that all trailing newline characters are removed)


The problem with your approach is that the embedded python script no longer has access to the original stdin (since its stdin is... itself).

If that's an issue you can write:

python -c '
import sys;
for r in range(3):
  print r
  for a in range(2):
    print "hello"
'

Or if the python script may contain single quotes:

python -c "$(cat << 'EOF'
import sys;
for r in range(3):
  print r
  for a in range(2):
    print "hello"
EOF
)"

Or:

python <(cat << 'EOF'
import sys;
for r in range(3):
  print r
  for a in range(2):
    print "hello"
EOF
)

Use a dash as the filename:

ruby - a b <<'END'
puts ARGV.join(",")
END

python - a b <<'END'
import sys
print ",".join(sys.argv[1:])
END

I don't know if sys.argv[1:] is the right way to do this in Python. For -e / -c you can specify end of arguments with --:

set -- -a -b -c
ruby -e 'puts ARGV.join(",")' -- "$@"
python -c 'import sys; print ",".join(sys.argv[2:])' -- "$@"

Capturing output and redirecting STDERR:

x=$(ruby <<'END' 2> /dev/null
puts "a"
abort "b"
END
)