Executing multi-line statements in the one-line command-line

Viewed 195249

I'm using Python with -c to execute a one-liner loop, i.e.:

python -c "for r in range(10): print 'rob'"

This works fine. However, if I import a module before the for loop, I get a syntax error:

python -c "import sys; for r in range(10): print 'rob'"

  File "<string>", line 1
    import sys; for r in range(10): print 'rob'
              ^
SyntaxError: invalid syntax

How can this be fixed?

It's important to me to have this as a one-liner so that I can include it in a Makefile.

18 Answers

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

Or SilentGhost's answer or Crast's answer.

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).

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?

Just use Return and type it on the next line:

python -c "import sys
for r in range(10): print 'rob'"

rob
rob
...

The problem is not with the import statement. The problem is that the control flow statements don't work inlined in a Python interpreter command. Replace that import statement with any other statement, and you'll see the same problem.

Think about it: Python can't possibly inline everything. It uses indentation to group control-flow.

If your system is POSIX.2-compliant it should supply the printf utility:

printf "print 'zap'\nfor r in range(3): print 'rob'" | python

zap
rob
rob
rob

I wanted a solution with the following properties:

  1. Readable
  2. Read standard input for processing output of other tools

Both requirements were not provided in the other answers, so here's how to read standard input while doing everything on the command line:

grep special_string -r | sort | python3 <(cat <<EOF
import sys
for line in sys.stdin:
    tokens = line.split()
    if len(tokens) == 4:
        print("%-45s %7.3f    %s    %s" % (tokens[0], float(tokens[1]), tokens[2], tokens[3]))
EOF
)

Use python -c with triple quotes:

python -c """
import os
os.system('pwd')
os.system('ls -l')
print('Hello, World!')
for _ in range(5):
    print(_)
"""
Related