Executing multi-line statements in the one-line command-line
--
Music by Eric Matyas
https://www.soundimage.org
Track title: Puzzle Game 5
--
Chapters
00:00 Question
00:44 Accepted answer (Score 237)
01:08 Answer 2 (Score 115)
01:52 Answer 3 (Score 70)
03:06 Answer 4 (Score 42)
09:05 Thank you
--
Full question
https://stackoverflow.com/questions/2043...
Question links:
[Makefile]: https://en.wikipedia.org/wiki/Make_(soft...
Accepted answer links:
[SilentGhost's answer]: https://stackoverflow.com/questions/2043...
[Crast's answer]: https://stackoverflow.com/questions/2043...
Answer 2 links:
[here]: http://tldp.org/LDP/abs/html/here-docs.h...
Answer 4 links:
[kxr's helpful answer]: https://stackoverflow.com/a/35651859/453...
[ANSI C-quoted string]: http://www.gnu.org/software/bash/manual/...
[command substitution]: http://mywiki.wooledge.org/CommandSubsti...
[xorho's answer]: https://stackoverflow.com/a/2356490/4537...
[here-document]: http://mywiki.wooledge.org/HereDocument
[ANSI C-quoted string]: http://www.gnu.org/software/bash/manual/...
[here-string]: http://mywiki.wooledge.org/HereDocument?...
--
Content licensed under CC BY-SA
https://meta.stackexchange.com/help/lice...
--
Tags
#python #shell #commandline #heredoc
#avk47
ACCEPTED ANSWER
Score 250
You could do
echo -e "import sys\nfor r in range(10): print 'rob'" | python
Or without pipes:
python -c "exec(\"import sys\nfor r in range(10): print 'rob'\")"
Or
(echo "import sys" ; echo "for r in range(10): print 'rob'") | python
ANSWER 2
Score 119
This style can be used in makefiles too (and in fact it is used quite often).
python - <<EOF
import random
for r in range(3): print(random.randint(1, 42))
EOF
Or with hard tabs:
python - <<-EOF
import random
for r in range(3): print(random.randint(1, 42))
EOF
# Important: Replace the indentation above w/ hard tabs.
In above case, leading TAB characters are removed too (and some structured outlook can be achieved).
Instead of EOF can stand any marker word not appearing in the here document at a beginning of a line (see also here documents in the bash man page or here).
ANSWER 3
Score 73
The issue is not actually with the import statement. It's with anything being before the for loop. Or more specifically, anything appearing before an inlined block.
For example, these all work:
python -c "import sys; print 'rob'"
python -c "import sys; sys.stdout.write('rob\n')"
If import being a statement were an issue, this would work, but it doesn't:
python -c "__import__('sys'); for r in range(10): print 'rob'"
For your very basic example, you could rewrite it as this:
python -c "import sys; map(lambda x: sys.stdout.write('rob%d\n' % x), range(10))"
However, lambdas can only execute expressions, not statements or multiple statements, so you may still be unable to do the thing you want to do. However, between generator expressions, list comprehension, lambdas, sys.stdout.write, the "map" builtin, and some creative string interpolation, you can do some powerful one-liners.
The question is, how far do you want to go, and at what point is it not better to write a small .py file which your makefile executes instead?
ANSWER 4
Score 45
- To make this answer work with Python 3.x as well, print is called as a function: in 3.x, only print('foo') works, whereas 2.x also accepts print 'foo'.
- For a cross-platform perspective that includes Windows, see kxr's helpful answer.
In bash, ksh, or zsh:
Use an ANSI C-quoted string ($'...'), which allows using \n to represent newlines that are expanded to actual newlines before the string is passed to python:
python -c $'import sys\nfor r in range(10): print("rob")'
Note the \n between the import and for statements to effect a line break.
To pass shell-variable values to such a command, it is safest to use arguments and access them via sys.argv inside the Python script:
name='rob' # value to pass to the Python script
python -c $'import sys\nfor r in range(10): print(sys.argv[1])' "$name"
See below for a discussion of the pros and cons of using an (escape sequence-preprocessed) double-quoted command string with embedded shell-variable references.
To work safely with $'...' strings:
- Double
\instances in your original source code.\<char>sequences - such as\nin this case, but also the usual suspects such as\t,\r,\b- are expanded by$'...'(seeman printffor the supported escapes)
- Escape
'instances as\'.
If you must remain POSIX-compliant:
Use printf with a command substitution:
python -c "$(printf %b 'import sys\nfor r in range(10): print("rob")')"
To work safely with this type of string:
- Double
\instances in your original source code.\<char>sequences - such as\nin this case, but also the usual suspects such as\t,\r,\b- are expanded byprintf(seeman printffor the supported escape sequences).
Pass a single-quoted string to
printf %band escape embedded single quotes as'\''(sic).Using single quotes protects the string's contents from interpretation by the shell.
That said, for short Python scripts (as in this case) you can use a double-quoted string to incorporate shell variable values into your scripts - as long as you're aware of the associated pitfalls (see next point); e.g., the shell expands
$HOMEto the current user's home dir. in the following command:python -c "$(printf %b "import sys\nfor r in range(10): print('rob is $HOME')")"
However, the generally preferred approach is to pass values from the shell via arguments, and access them via
sys.argvin Python; the equivalent of the above command is:python -c "$(printf %b 'import sys\nfor r in range(10): print("rob is " + sys.argv[1])')" "$HOME"
While using a double-quoted string is more convenient - it allows you to use embedded single quotes unescaped and embedded double quotes as
\"- it also makes the string subject to interpretation by the shell, which may or may not be the intent;$and`characters in your source code that are not meant for the shell may cause a syntax error or alter the string unexpectedly.- Additionally, the shell's own
\processing in double-quoted strings can get in the way; for instance, to get Python to produce literal outputro\b, you must passro\\bto it; with a'...'shell string and doubled\instances, we get:
python -c "$(printf %b 'import sys\nprint("ro\\\\bs")')" # ok: 'ro\bs'
By contrast, this does not work as intended with a"..."shell string:
python -c "$(printf %b "import sys\nprint('ro\\\\bs')")" # !! INCORRECT: 'rs'
The shell interprets both"\b"and"\\b"as literal\b, requiring a dizzying number of additional\instances to achieve the desired effect:
python -c "$(printf %b "import sys\nprint('ro\\\\\\\\bs')")"
- Additionally, the shell's own
To pass the code via stdin rather than -c:
Note: I'm focusing on single-line solutions here; xorho's answer shows how to use a multi-line here-document - be sure to quote the delimiter, however; e.g., <<'EOF', unless you explicitly want the shell to expand the string up front (which comes with the caveats noted above).
In bash, ksh, or zsh:
Combine an ANSI C-quoted string ($'...') with a here-string (<<<...):
python - <<<$'import sys\nfor r in range(10): print("rob")'
- tells python explicitly to read from stdin (which it does by default).
- is optional in this case, but if you also want to pass arguments to the scripts, you do need it to disambiguate the argument from a script filename:
python - 'rob' <<<$'import sys\nfor r in range(10): print(sys.argv[1])'
If you must remain POSIX-compliant:
Use printf as above, but with a pipeline so as to pass its output via stdin:
printf %b 'import sys\nfor r in range(10): print("rob")' | python
With an argument:
printf %b 'import sys\nfor r in range(10): print(sys.argv[1])' | python - 'rob'