Multi-line commands with 'python -c'

C

Chris Angelico

Since lines are so critical to Python syntax, I'm a little surprised
there's no majorly obvious solution to this... or maybe I'm just
blind.

Problem: Translate this into a shell one-liner:

import os
for root, dirs, files in os.walk("."):
if len(dirs + files) == 1: print(root)

Solution 1: SyntaxError

python -c 'import os; for root, dirs, files in os.walk("."): if
len(dirs + files) == 1: print(root)'

You can't put a 'for' statement after an 'import' with just a semicolon.

Solution 2: SyntaxError

python -c 'import os\nfor root, dirs, files in os.walk("."): if
len(dirs + files) == 1: print(root)'

You can't put a backslash escape into your code like that! Makes no sense.

Solution 3: Silence

python -c 'import os' -c 'for root, dirs, files in os.walk("."): if
len(dirs + files) == 1: print(root)'

Haven't dug into exactly what this does, but the docs say that -c
terminates the option list, so I would guess that the second -c and
its arg get passed to the script.

Solution 4: Rely on the shell's ability to pass newlines inside arguments

$ python -c 'import os
for root, dirs, files in os.walk("."):
if len(dirs + files) == 1: print(root)
'

That works, but at that point, you aren't writing a one-liner any
more. It's also fiddly to edit.

Is there a better way to put multiple virtual lines into a 'python -c' command?

ChrisA
 
R

Rustom Mody

Since lines are so critical to Python syntax, I'm a little surprised
there's no majorly obvious solution to this... or maybe I'm just
blind.
Problem: Translate this into a shell one-liner:
import os
for root, dirs, files in os.walk("."):
if len(dirs + files) == 1: print(root)

I would have thought this would work:

python -m os -c 'for root, dirs, files in os.walk("."): if len(dirs + files) == 1: print(root)'

Unfortunately doesn't
But did show up a small doc-bug:

This fact is not documented in
$ man python
but is documented in
$ python -h

Anyways...

I thought when one signs up for python one has to sign an affidavit
saying:
"I shall not write one-liners\n" * 100
 
R

Rustom Mody

Heres a (poooor) approx

$ python -c 'import os, pprint; pprint.pprint ([ r for r, d, f in os.walk(".") if len(d+f) != 1])'


Mysterious that print after a ; is fine whereas for is not


Anyways...
I thought when one signs up for python one has to sign an affidavit
saying:
"I shall not write one-liners\n" * 100

I hope youve signed it by now <wink>
 
R

Rustom Mody

Heres a (poooor) approx

$ python -c 'import os, pprint; pprint.pprint ([ r for r, d, f in os.walk(".") if len(d+f) != 1])'

Without pprint: (pooor)

python -c 'import os; print "\n".join([ r for r, d, f in os.walk(".") if len(d+f) != 1])'

Or (poor)

python -c 'from os import walk; print "\n".join([ r for r, d, f in walk(".") if len(d+f) != 1])'
 
C

Chris Angelico

I thought when one signs up for python one has to sign an affidavit
saying:
"I shall not write one-liners\n" * 100

Certainly not. I write all my list comps on one line!

*ducking for cover*

ChrisA
 
P

Peter Otten

Rustom said:
Heres a (poooor) approx

$ python -c 'import os, pprint; pprint.pprint ([ r for r, d, f in
os.walk(".") if len(d+f) != 1])'

Without pprint: (pooor)

python -c 'import os; print "\n".join([ r for r, d, f in os.walk(".") if
len(d+f) != 1])'

Or (poor)

python -c 'from os import walk; print "\n".join([ r for r, d, f in
walk(".") if len(d+f) != 1])'

If there are a lot of matching folders:

$ python -c 'import os, sys; sys.stdout.writelines(p + "\n" for p, f, n in
os.walk(".") if len(n+f) == 1)'

With a little help from the shell:

$ echo -e "import os\nfor p, f, n in os.walk('.'):\n if len(f+n) == 1:
print(p)" | python
 
R

Rustom Mody

Certainly not. I write all my list comps on one line!
*ducking for cover*

Heres a more vile misuse of python3's print-as-function + list-comp-as-for:

python3 -c 'from os import walk; [print(r) for r, d, f in walk(".") if len(d+f) == 1]'

Well if C programmers can use ',' as one-line ';' and '?:' as one-line if
why not python also?

[To noobs who are reading: Dont do this!]
 
C

Chris Angelico

Certainly not. I write all my list comps on one line!
*ducking for cover*

Heres a more vile misuse of python3's print-as-function + list-comp-as-for:

python3 -c 'from os import walk; [print(r) for r, d, f in walk(".") if len(d+f) == 1]'

Well if C programmers can use ',' as one-line ';' and '?:' as one-line if
why not python also?

[To noobs who are reading: Dont do this!]

I actually crafted the exact same vile misuse, prior to asking the question.

https://lists.debian.org/debian-user/2014/05/msg02019.html

Modulo trivialities like whitespace and the from-import, it's exactly
the same as your version.

Incidentally, C's ?: operator maps to Python's ternary if/else
operator, which most definitely is valid in a one-liner. So it's just
the semicolon that you're looking at. In C, you can combine any two
statements onto one line; in Python, certain statements may not follow
a semicolon. So it's not really ; and ?: that are the point here, but
that Python, with its stricter rules about newlines (as opposed to
"any whitespace"), doesn't seem to have a convenient notation for
putting multiple lines into a -c command.

ChrisA
 
T

Terry Reedy

$ python -c 'import os, pprint; pprint.pprint ([ r for r, d, f in os.walk(".") if len(d+f) != 1])'

Mysterious that print after a ; is fine whereas for is not

Not at all. Simple statememts can follow ; or :, compound statements cannot.
 
D

Devin Jeanpierre

In unix shells you can literally use a new line. Or is that only bash?

-- Devin
 
C

Chris Angelico

In unix shells you can literally use a new line. Or is that only bash?

You can in bash, I know, but it's fiddly to type it; and more
importantly, it's not a good point in the "this is cleaner than a
series of pipes" argument. My primary recommendation, of course, was a
three-line script saved as an actual file, but for a more direct
parallel to the pipe-it-three-ways model, I wanted to use -c.

ChrisA
 
P

Peter Otten

Duncan said:
and you also wrote originally that it's fiddly to edit. I think that
Windows Powershell has (at least in the current ISE command line) got
the editing a bit better. It's a minor difference though and it has
taken Microsoft about 30 years to get to that point.

What may be a larger difference, or may just be my lack of Linux-foo, is
this:

PS C:\python33> $script = @"
import os
for root, dirs, files in os.walk("."):
if len(dirs + files) == 1: print(root)
"@

PS C:\python33> python -c $script
.\Doc
.\Lib\concurrent\__pycache__
.\Lib\curses\__pycache__
...

which is a style I've found useful for example when running a group of
related timeit.py commands as I can put things like multi-line setup
statements in a variable and then have a simpler command to repeat.

But bash as far as I can won't let me do that:

$ script='import os
for root, dirs, files in os.walk("."):
if len(dirs + files) == 1: print(root)
'
$ python -c $script
File "<string>", line 1
import
^
SyntaxError: invalid syntax

$ script='import os
for root, dirs, files in os.walk("."):
if len(dirs + files) == 1:
print(root)
'
$ python3 -c "$script"
..
../heureka

$ python3 -c 'import sys; print(sys.argv)' $script
['-c', 'import', 'os', 'for', 'root,', 'dirs,', 'files', 'in',
'os.walk("."):', 'if', 'len(dirs', '+', 'files)', '==', '1:', 'print(root)']
$ python3 -c 'import sys; print(sys.argv)' "$script"
['-c', 'import os\nfor root, dirs, files in os.walk("."):\n if len(dirs +
files) == 1:\n print(root)\n']
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,058
Latest member
QQXCharlot

Latest Threads

Top