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

时间:2010-01-11 17:11:27

标签: python shell command-line heredoc

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

$ 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中。

18 个答案:

答案 0 :(得分:151)

你可以做到

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

或w / out pipe:

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

答案 1 :(得分: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)。

答案 2 :(得分: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文件不是更好?

答案 3 :(得分:20)


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

bashkshzsh 中:

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

python

请注意python -c $'import sys\nfor r in range(10): print("rob")' \n语句之间的import以实现换行符。

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

sys.argv

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

使用name='rob' # value to pass to the Python script python -c $'import sys\nfor r in range(10): print(sys.argv[1])' "$name" 字符串安全地工作:

  • 原始源代码中的双 $'...'个实例。
    • \序列(例如\<char>在这种情况下,而且通常的嫌疑人,例如\n\t\r - 被{扩展{ {1}}(有关受支持的转义信息,请参阅\b
  • $'...'个实例转发为man printf

如果您必须保持 POSIX兼容

'command substitution

一起使用
\'

使用此类字符串安全地工作:

  • 原始源代码中的双 printf个实例。
    • python -c "$(printf %b 'import sys\nfor r in range(10): print("rob")')" 序列(例如\在这种情况下,而且通常的嫌疑人,例如\<char>\n\t - 被{扩展{ {1}}(有关支持的转义序列,请参阅\r。)
  • 单引号字符串传递给\b将嵌入的单引号转义为 printf(原文如此)。

    • 使用单引号可以保护字符串的内容免受 shell 的解释。

      • 也就是说,对于 Python脚本(如本例所示),您可以使用双引号字符串将 shell 变量值合并到脚本中 - 只要你知道相关的陷阱(见下一点);例如,shell将man printf扩展为当前用户的主目录。在以下命令中:

        • printf %b
      • 但是,通常首选的方法是通过 arguments 从shell传递值,并通过Python中的'\''访问它们;相当于上面的命令是:

        • $HOME
    • 使用双引号字符串更方便 - 它允许您使用嵌入式单引号未转义和嵌入双引号python -c "$(printf %b "import sys\nfor r in range(10): print('rob is $HOME')")" - 它还使字符串受 shell 的解释,这可能是也可能不是意图;源代码中的sys.argvpython -c "$(printf %b 'import sys\nfor r in range(10): print("rob is " + sys.argv[1])')" "$HOME"字符不适用于shell,可能会导致语法错误或意外更改字符串。

      • 此外,shell在双引号字符串中自己的\"处理可能会妨碍;例如,要使 Python 生成文字输出$,您必须将`传递给它;使用\ shell字符串和 doubled ro\b个实例,我们得到:
        ro\\b
        相比之下,按照预期的'...' shell字符串工作:
        \
        shell将 python -c "$(printf %b 'import sys\nprint("ro\\\\bs")')" # ok: 'ro\bs'"..."解释为文字python -c "$(printf %b "import sys\nprint('ro\\\\bs')")" # !! INCORRECT: 'rs',需要一些令人眼花缭乱的额外"\b"个实例才能达到预期效果:
        "\\b"

通过\b 而不是\传递代码:

注意:我在这里专注于单个 -line解决方案; xorho's answer显示了如何使用多行here-document - 请确保引用分隔符;例如,python -c "$(printf %b "import sys\nprint('ro\\\\\\\\bs')")",除非你明确要求shell预先扩展字符串(这与上面提到的警告一起提供)。


stdin-c<<'EOF' 中:

ANSI C-quoted string bash)与here-stringksh)相结合:

zsh

$'...'显式地告诉<<<...从stdin读取(默认情况下它是这样做的)。 在这种情况下,python - <<<$'import sys\nfor r in range(10): print("rob")' 是可选的,但如果您还想将参数传递给脚本,则需要它从脚本文件名中消除歧义:

-

如果您必须保持 POSIX兼容

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

-

有一个论点:

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

答案 4 :(得分:12)

  

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

你的问题是由于;分隔的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或列表推导来获取for-loops:

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

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

答案 5 :(得分:11)

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

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

答案 6 :(得分:8)

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

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

答案 7 :(得分:7)

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

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

答案 8 :(得分:7)

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

工作正常。 使用“[]”来内联你的循环。

答案 9 :(得分:4)

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

如果您使用sys.stdout.write而不是print区别在于,sys.stdout.write将参数作为函数加在括号中 - 而print不会< / em>),然后对于单行,您可以通过反转命令和for的顺序,删除分号,并将命令括在方括号中,即:

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

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

希望这有帮助,

干杯!


(EDIT 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”循环。这将生成一个“一次性”可迭代对象,这个对象只能在一个方向上迭代,一旦到达终点就无法重复使用。

     

'list comprehension'看起来与普通的单行生成器几乎相同,只是常规括号 - ( - )被方括号 - []替换。 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]

请注意,第二个“list”b现在有两个元素,因为它的for循环显式运行了两次;但是,sys.stdout.write()在两种情况下的结果都是(显然)None

答案 10 :(得分:3)

无处不在

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"
> '

答案 11 :(得分: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"""

答案 12 :(得分:2)

答案 13 :(得分: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
)

答案 14 :(得分:1)

使用带有三重引号的 python -c

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

答案 15 :(得分:0)

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

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

答案 16 :(得分:0)

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

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

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

答案 17 :(得分: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内部为输入命令内容创建临时文件。