在单行命令行中执行多行语句?

问题描述 投票:164回答:17

我正在使用Python与-c执行单行循环,即:

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

这很好用。但是,如果我在for循环之前导入模块,则会出现语法错误:

$ 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

知道如何解决这个问题吗?

将此作为单行程序对我来说很重要,这样我就可以将它包含在Makefile中。

python shell command-line heredoc
17个回答
155
投票

你能做到的

echo -e "import sys\nfor r in range(10): print 'rob'" | python

或没有管道:

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

要么

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

或@SilentGhost's answer / @Crast's answer


4
投票

到处都是single/double quotesbackslash

$ python -c 'exec("import sys\nfor i in range(10): print \"bob\"")'

好多了:

$ python -c '
> import sys
> for i in range(10):
>   print "bob"
> '

4
投票

(2010年11月23日19:48回答)我不是一个真正的大蟒蛇 - 但我发现这个语法一次,忘了从哪里来,所以我想我会记录它:

如果你使用sys.stdout.write而不是print(不同的是,sys.stdout.write将参数作为函数,在括号中 - 而print没有),那么对于单行,你可以通过反转命令和for的顺序逃脱,删除分号,并将命令括在方括号中,即:

python -c "import sys; [sys.stdout.write('rob\n') for r in range(10)]"

不知道如何在Python中调用这种语法:)

希望这可以帮助,

干杯!


(编辑Tue Apr 9 20:57:30 2013)嗯,我想我终于找到了这些方括号中的方括号;他们是“列表理解”(显然);首先在Python 2.7中注意这一点:

$ STR=abc
$ echo $STR | python -c "import sys,re; a=(sys.stdout.write(line) for line in sys.stdin); print a"
<generator object <genexpr> at 0xb771461c>

所以圆括号/括号中的命令被视为“生成器对象”;如果我们通过调用next()来“迭代”它 - 那么括号内的命令将被执行(注意输出中的“abc”):

$ echo $STR | python -c "import sys,re; a=(sys.stdout.write(line) for line in sys.stdin); a.next() ; print a"
abc
<generator object <genexpr> at 0xb777b734>

如果我们现在使用方括号 - 注意我们不需要调用next()来执行命令,它会在赋值时立即执行;然而,后来的检查显示aNone

$ echo $STR | python -c "import sys,re; a=[sys.stdout.write(line) for line in sys.stdin]; print a"
abc
[None]

对于方括号的情况,这并没有留下太多信息 - 但我偶然发现了这个我认为解释的页面:

Python Tips And Tricks – First Edition - Python Tutorials | Dream.In.Code

如果你还记得,单行生成器的标准格式是括号内的一行'for'循环。这将生成一个“一次性”可迭代对象,这个对象只能在一个方向上迭代,一旦到达终点就无法重复使用。

“列表理解”看起来与常规单行生成器几乎相同,只是常规括号 - ( - )被方括号 - []替换。 alist理解的主要优点是产生一个'list',而不是'one-shot'可迭代对象,这样你就可以在它中来回移动,添加元素,排序等等。

确实它是一个列表 - 它只是它的第一个元素在执行时变为none:

$ echo $STR | python -c "import sys,re; print [sys.stdout.write(line) for line in sys.stdin].__class__"
abc
<type 'list'>
$ echo $STR | python -c "import sys,re; print [sys.stdout.write(line) for line in sys.stdin][0]"
abc
None

列表理解在5. Data Structures: 5.1.4. List Comprehensions — Python v2.7.4 documentation中另外记录为“列表理解提供了创建列表的简明方法”;据推测,这就是列表中有限的“可执行性”在单行中发挥作用的地方。

好吧,希望我在这里不是太过分了......

EDIT2:这是一个带有两个非嵌套for循环的单行命令行;两者都包含在“列表理解”方括号内:

$ echo $STR | python -c "import sys,re; a=[sys.stdout.write(line) for line in sys.stdin]; b=[sys.stdout.write(str(x)) for x in range(2)] ; print a ; print b"
abc
01[None]
[None, None]

请注意,第二个“列表”b现在有两个元素,因为它的for循环显式运行了两次;然而,两个案例中sys.stdout.write()的结果(显然)是None


3
投票

这种变体最便于将多行脚本放在Windows上的命令行和* nix,py2 / 3,不带管道:

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

(到目前为止,这里看到的其他例子都没有这样做)

Windows上的整洁是:

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

整洁的bash / * nix是:

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

此函数将任何多行脚本转换为便携式命令 - 一行:

def py2cmdline(script):
    exs = 'exec(%r)' % re.sub('\r\n|\r', '\n', script.rstrip())
    print('python -c "%s"' % exs.replace('"', r'\"'))

用法:

>>> py2cmdline(getcliptext())
python -c "exec('print \'AA\tA\'\ntry:\n for i in 1, 2, 3:\n  print i / 0\nexcept:\n print \"\"\"longer\nmessage\"\"\"')"

输入是:

print 'AA   A'
try:
 for i in 1, 2, 3:
  print i / 0
except:
 print """longer
message"""

2
投票

1
投票

我想要一个具有以下属性的解决方案:

  1. 可读
  2. 读取stdin以处理其他工具的输出

其他答案中没有提供这两个要求,所以这里是如何在命令行上执行所有操作时读取stdin:

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
)

0
投票

还有一个选项,sys.stdout.write返回None,这使列表保持为空

cat somefile.log|python -c "import sys;[line for line in sys.stdin if sys.stdout.write(line*2)]"

0
投票

如果您不想触摸标准输入并模拟已经通过“python cmdfile.py”,则可以从bash shell执行以下操作:

$ python  <(printf "word=raw_input('Enter word: ')\nimport sys\nfor i in range(5):\n    print(word)")

如您所见,它允许您使用stdin读取输入数据。 shell内部为输入命令内容创建临时文件。


0
投票

当我需要这样做时,我会使用

python -c "$(echo -e "import sys\nsys.stdout.write('Hello World!\\\n')")"

请注意sys.stdout.write语句中换行符的三重反斜杠。


80
投票

这种风格也可以在makefile中使用(实际上它经常被使用)。

python - <<EOF
import sys
for r in range(3): print 'rob'
EOF

要么

python - <<-EOF
    import sys
    for r in range(3): print 'rob'
EOF

在后一种情况下,前导制表符也被删除(并且可以实现一些结构化的前景)

而不是EOF可以代表在行的开头不出现在此处文档中的任何标记词(另请参阅bash联机帮助页或here中的文档)。


44
投票

实际上问题不在于import语句,而是在for循环之前的任何事情。或者更具体地说,在内联块之前出现的任何内容。

例如,这些都有效:

python -c "import sys; print 'rob'"
python -c "import sys; sys.stdout.write('rob\n')"

如果导入是一个声明是一个问题,这将有效,但它不会:

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

对于您的基本示例,您可以将其重写为:

python -c "import sys; map(lambda x: sys.stdout.write('rob%d\n' % x), range(10))"

但是,lambdas只能执行表达式,而不能执行语句或多个语句,因此您可能仍然无法执行您想要执行的操作。但是,在生成器表达式,列表推导,lambdas,sys.stdout.write,内置的“map”和一些创造性的字符串插值之间,你可以做一些强大的单行。

问题是,你想走多远,在什么时候写一个你的makefile执行的小.py文件不是更好?


21
投票

- 为了使这个答案也适用于Python 3.x,print被称为函数:在3.x中,只有print('foo')有效,而2.x也接受print 'foo'。 - 有关包含Windows的跨平台视角,请参阅kxr's helpful answer

bashkshzsh

使用ANSI C-quoted string$'...'),它允许使用\n来表示在字符串传递给python之前扩展到实际换行符的换行符:

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

请注意\nimport语句之间的for以实现换行符。

要将shell变量值传递给这样的命令,最安全的方法是使用参数并通过Python脚本中的sys.argv访问它们:

name='rob' # value to pass to the Python script
python -c $'import sys\nfor r in range(10): print(sys.argv[1])' "$name"

请参阅下文,了解使用带有嵌入式shell变量引用的(转义序列 - 预处理)双引号命令字符串的优缺点。

要安全地使用$'...'字符串:

  • 原始源代码中的双\实例。 \<char>序列 - 例如\n在这种情况下,但通常的嫌疑人,如\t\r\b - 由$'...'扩展(请参阅man printf获取支持的逃逸)
  • 逃离'实例为\'

如果您必须保持POSIX兼容:

使用printfcommand substitution

python -c "$(printf %b 'import sys\nfor r in range(10): print("rob")')"

要安全地使用这种类型的字符串:

  • 原始源代码中的双\实例。 \<char>序列 - 例如\n在这种情况下,但通常的嫌疑人,如\t\r\b - 由printf扩展(请参阅man printf获得支持的逃逸序列)。
  • 将单引号字符串传递给printf %b并将嵌入的单引号转义为'\''(原文如此)。 使用单引号可以保护字符串的内容不受shell的解释。 也就是说,对于简短的Python脚本(如本例所示),您可以使用双引号字符串将shell变量值合并到脚本中 - 只要您知道相关的陷阱(参见下一点);例如,shell将$HOME扩展到当前用户的主目录。在以下命令中: python -c "$(printf %b "import sys\nfor r in range(10): print('rob is $HOME')")" 但是,通常首选的方法是通过参数从shell传递值,并通过Python中的sys.argv访问它们;相当于上面的命令是: python -c "$(printf %b 'import sys\nfor r in range(10): print("rob is " + sys.argv[1])')" "$HOME" 虽然使用双引号字符串更方便 - 它允许您使用嵌入的单引号非转义和嵌入双引号作为\" - 它也使字符串受shell的解释,这可能是也可能不是意图;源代码中的$`字符不适用于shell可能会导致语法错误或意外更改字符串。 另外,用双引号字符串处理shell自己的\可能会妨碍它;例如,要让Python生成文字输出ro\b,你必须将ro\\b传递给它;使用'...' shell字符串和加倍的\实例,我们得到: python -c "$(printf %b 'import sys\nprint("ro\\\\bs")')" # ok: 'ro\bs' 相比之下,这不适用于"..." shell字符串: python -c "$(printf %b "import sys\nprint('ro\\\\bs')")" # !! INCORRECT: 'rs' shell将"\b""\\b"解释为文字\b,需要一些令人眼花缭乱的额外\实例才能达到预期的效果: python -c "$(printf %b "import sys\nprint('ro\\\\\\\\bs')")"

要通过stdin而不是-c传递代码:

注意:我在这里专注于单线解决方案; xorho's answer展示了如何使用多行here-document - 请务必引用分隔符;例如,<<'EOF',除非你明确要求shell预先扩展字符串(这与上面提到的警告一起)。


bashkshzsh

结合ANSI C-quoted string$'...')和here-string<<<...):

python - <<<$'import sys\nfor r in range(10): print("rob")'

-明确地告诉python从stdin读取(默认情况下它是这样做的)。在这种情况下,-是可选的,但是如果你还想将参数传递给脚本,你需要它来消除脚本文件名中的参数:

python - 'rob' <<<$'import sys\nfor r in range(10): print(sys.argv[1])'

如果您必须保持POSIX兼容:

如上所述使用printf,但使用管道以通过stdin传递其输出:

printf %b 'import sys\nfor r in range(10): print("rob")' | python

有一个论点:

printf %b 'import sys\nfor r in range(10): print(sys.argv[1])' | python - 'rob'

12
投票

Any idea how this can be fixed?

你的问题是由;分隔的Python语句只允许是“小语句”这些事实创建的,这些语句都是单行语句。从Python docs中的语法文件:

stmt: simple_stmt | compound_stmt
simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE
small_stmt: (expr_stmt | del_stmt | pass_stmt | flow_stmt |
             import_stmt | global_stmt | nonlocal_stmt | assert_stmt)

复合语句不能通过分号与其他语句包含在同一行 - 因此使用-c标志执行此操作会变得非常不方便。

在bash shell环境中演示Python时,我发现包含复合语句非常有用。这样做的唯一简单方法是使用heredocs。

here文档

使用heredoc(使用<<创建)和Python的command line interface option-

$ python - <<-"EOF"
        import sys                    # 1 tab indent
        for r in range(10):           # 1 tab indent
            print('rob')              # 1 tab indent and 4 spaces
EOF

-<<)之后添加<<-允许你使用制表符缩进(Stackoverflow将制表符转换为空格,所以我缩进了8个空格来强调这一点)。前导标签将被剥离。

你可以在没有只有<<的标签的情况下做到这一点:

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

EOF周围加上引号可以阻止parameterarithmetic expansion。这使得heredoc更加强大。

批准接受的答案(和其他人)

这不是很可读:

echo -e "import sys\nfor r in range(10): print 'rob'" | python

不太可读,并且在出现错误的情况下另外难以调试:

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

也许更具可读性,但仍然相当难看:

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

如果你的python中有",你将度过一段美好的时光:

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

不要滥用map或list comprehensions来获取for循环:

python -c "import sys; map(lambda x: sys.stdout.write('rob%d\n' % x), range(10))"

这些都是悲伤和坏事。不要这样做。


11
投票

只需使用return并在下一行输入:

user@host:~$ python -c "import sys
> for r in range(10): print 'rob'"
rob
rob
...

8
投票

问题不在于import声明。问题是控制流语句不能在python命令中内联工作。将import语句替换为任何其他语句,您将看到相同的问题。

想想看:python不可能内联一切。它使用缩进来分组控制流。


7
投票

如果您的系统符合Posix.2,它应该提供printf实用程序:

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

7
投票

$ python2.6 -c "import sys; [sys.stdout.write('rob\n') for r in range(10)]"

工作良好。使用“[]”来内联你的for循环。

© www.soinside.com 2019 - 2024. All rights reserved.