【问题标题】:Executing multi-line statements in the one-line command-line?在单行命令行中执行多行语句?
【发布时间】:2011-01-03 20:20:18
【问题描述】:

我正在使用 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


【解决方案1】:

你可以的

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

【讨论】:

  • 最后一个选项适用于 Windows 中的 python3
  • @RudolfOlah 希望您现在已经知道了,但仅供参考,您需要为 python3+ 版本包装打印语句,例如:python -c "exec(\"import sys\nfor r in range(10): print('rob')\")"
  • 第二个选项也适用于使用 Python 3 的 Windows。
【解决方案2】:

使用带有三引号的 python -c

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

【讨论】:

    【解决方案3】:

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

    你的问题是由;分隔的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(一个 posix shell 的东西)。

    Heredocs

    使用heredoc(使用&lt;&lt; 创建)和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
    

    &lt;&lt;&lt;&lt;-)之后添加- 允许您使用制表符 进行缩进(Stackoverflow 将制表符转换为空格,因此我缩进了 8 个空格来强调这一点) .前导标签将被剥离。

    您可以在没有标签的情况下使用&lt;&lt;

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

    EOF 周围加上引号可防止parameterarithmetic expansion。这使得heredoc更加健壮。

    Bash 多行字符串

    如果你使用双引号,你会得到 shell-expansion:

    $ python -c "
    > import sys
    > for p in '$PATH'.split(':'):
    >     print(p)
    > "
    /usr/sbin
    /usr/bin
    /sbin
    /bin
    ...
    

    为避免外壳扩展,请使用单引号:

    $ python -c '
    > import sys
    > for p in "$PATH".split(":"):
    >     print(p)
    > '
    $PATH
    

    请注意,我们需要在 Python 中交换文字上的引号字符 - 我们基本上不能使用 BASH 解释的引号字符。不过,我们可以像在 Python 中那样交替使用它们——但这看起来已经很混乱了,这就是我不推荐这样做的原因:

    $ python -c '
    import sys
    for p in "'"$PATH"'".split(":"):
        print(p)
    '
    /usr/sbin
    /usr/bin
    /sbin
    /bin
    ...
    

    对已接受答案(和其他)的批评

    这不是很可读:

    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 中有 "'s,你会很糟糕:

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

    不要滥用 map 或列表推导来获取 for 循环:

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

    这些都是悲伤和糟糕的。不要这样做。

    【讨论】:

    • ++ 用于语法信息; (非扩展的)here-doc 很方便,也是最强大的解决方案,但显然不是单行的。如果必须使用单行解决方案,则使用 ANSI C 引用的字符串(bashkshzsh)是一个合理的解决方案:python -c $'import sys\nfor r in range(10): print(\'rob\')'(您只需要担心转义单行引号(可以通过使用双引号来避免)和反斜杠)。
    • 这是一个了不起的答案 - 无论是在 pythonbash 前线
    【解决方案4】:

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

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

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

    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
    )
    

    【讨论】:

      【解决方案5】:


      - 为了使这个答案也适用于 Python 3.xprint 被称为 函数:在 3.x 中,only 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")'
      

      注意importfor 语句之间的\n 以实现换行。

      将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 变量引用的优缺点的讨论见下文.

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

      • \ 您的原始源代码中的实例。
        • \&lt;char&gt; 序列 - 在这种情况下,例如 \n,还有常见的嫌疑人,例如 \t\r\b - 由 $'...' 扩展(有关支持的转义,请参阅 man printf
      • ' 实例转义为\'

      如果您必须保持 POSIX 兼容

      printfcommand substitution 一起使用:

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

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

      • \ 您的原始源代码中的实例。
        • \&lt;char&gt; 序列 - 在这种情况下,例如 \n,但还有常见的嫌疑人,例如 \t\r\b - 由 printf 扩展(有关支持的转义序列,请参阅 man printf) .
      • 单引号字符串传递给printf %b,并将嵌入的单引号转义为 '\''(原文如此)。

        • 使用单引号保护字符串的内容不被 shell 解释。

          • 也就是说,对于 short 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 将 both "\b""\\b" 解释为文字 \b,需要数量惊人的额外 \ 实例才能达到预期效果:
            python -c "$(printf %b "import sys\nprint('ro\\\\\\\\bs')")"

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

      注意:这里我关注的是单线解决方案; xorho's answer 展示了如何使用多行 here-document - 但是请务必引用分隔符;例如,&lt;&lt;'EOF',除非您明确希望 shell 预先扩展字符串(带有上述警告)。


      bashkshzsh

      ANSI C-quoted string ($'...') 与 here-string (&lt;&lt;&lt;...) 组合起来:

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

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

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

      如果您必须保持 POSIX 兼容

      如上使用printf,但使用管道以便通过标准输入传递其输出:

      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'
      

      【讨论】:

        【解决方案6】:

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

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

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

        【讨论】:

        • 这行得通,但由于您使用的是echo -e,它是非标准的并且需要bashkshzsh,您不妨直接使用$'...' 字符串,这也简化了转义:python -c $'import sys\nsys.stdout.write("Hello World!\\n")'
        • 此答案有效,其他答案不适用于 Python3
        【解决方案7】:

        如果您不想触摸标准输入并像传递“python cmdfile.py”一样进行模拟,您可以从 bash shell 执行以下操作:

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

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

        【讨论】:

        • ++ 用于不“用完”脚本本身的标准输入(尽管 -c "$(...)" 也这样做,并且符合 POSIX);给&lt;(...) 构造一个名字:process substitution;它也适用于kshzsh
        【解决方案8】:

        此变体最便于在 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"""
        

        【讨论】:

        • ++ 用于跨平台角度和转换器。您的第一个命令在可移植性方面与它一样好(撇开 PowerShell 不谈),但最终没有单一的、完全健壮的跨平台语法,因为需要使用双引号来承担不必要的 shell 扩展的风险,Windows 需要转义与 POSIX-like shell 不同的字符。在 PowerShell v3 或更高版本中,您可以通过在命令字符串参数之前插入“停止解析”选项 --% 来使命令行工作。
        • @mklement0 "不需要的 shell 扩展":好吧,将 shell 扩展插入到类似 print "path is %%PATH%%" 的东西中。 print "path is $PATH" 通常是人们在脚本或命令行中想要的选项 - 除非人们像往常一样为平台转义。与其他语言相同。 (Python 语法本身并不经常建议以相互竞争的方式使用 % 和 $。)
        • 如果您将 shell 变量引用直接插入到 Python 源代码中,根据定义,它将不可移植。我的观点是,即使您改为在 separate 变量中构建特定于平台的引用,您将 作为参数 传递给 single,“shell-free” Python 命令,可能 并不总是有效,因为您无法保护双引号字符串 portably:例如,如果您需要 literal 怎么办 @ 987654330@ 在你的 Python 代码中?如果您将其转义为 \$foo 以使用类似 POSIX 的 shell,cmd.exe 仍将看到额外的 \ 。这可能很少见,但值得了解。
        • 尝试在 Windows PowerShell 中执行此操作,但问题是 python -c exec("""...""") 不会产生任何输出,无论代码是否在... 是否能够被执行;我可以在那里放任何乱码,结果是一样的。我觉得 shell 正在“吃掉”stdout 和 stderr 流 - 我怎样才能让它吐出来?
        【解决方案9】:

        这种风格也可以在 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)。

        【讨论】:

        • 不错。要同时传递参数,只需将它们放在&lt;&lt;EOF 之后。但是请注意,最好引用 分隔符 - 例如,&lt;&lt;'EOF' - 以保护 here-document 的内容免受前期 shell 扩展的影响。
        • 太棒了!这是我想问的更简化的解决方案。
        【解决方案10】:

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

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

        【讨论】:

          【解决方案11】:

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

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

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

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

          希望对你有帮助,

          干杯!


          (2013 年 4 月 9 日星期二 20:57:30 编辑) 好吧,我想我终于找到了单行中的这些方括号的含义;它们是“列表理解”(显然);首先在 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 理解的主要优点是生成一个“列表”,而不是一个“一次性”可迭代对象,以便您可以在它中来回切换、添加元素、排序等。

          确实它是一个列表——它只是它的第一个元素在执行后立即变为无:

          $ 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

          【讨论】:

            【解决方案12】:

            这个脚本提供了一个类似 Perl 的命令行界面:

            Pyliner - Script to run arbitrary Python code on the command line (Python recipe)

            【讨论】:

            • 我认为他们想要解决这个问题,而不是使用其他工具。无论如何很好的提示
            【解决方案13】:

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

            工作正常。 使用 "[ ]" 内联你的 for 循环。

            【讨论】:

              【解决方案14】:

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

              【讨论】:

                【解决方案15】:

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

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

                【讨论】:

                • 很好的解决方案,但我建议使用printf %b '...' | python 来增加稳健性,因为它可以防止printf 解释输入字符串中的序列,例如%d(格式说明符)。此外,除非您明确希望预先将 shell 扩展应用于您的 Python 命令字符串(这可能会让人感到困惑),否则最好使用 '(单引号)进行外部引用,以避免扩展和反冲处理shell 适用于双引号字符串。
                【解决方案16】:

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

                例如,这些都有效:

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

                如果 import 是一个语句是一个问题,这会起作用,但它不会:

                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”内置函数和一些创造性的字符串插值之间,你可以做一些强大的单行。

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

                【讨论】:

                  【解决方案17】:

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

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

                  【讨论】:

                    【解决方案18】:

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

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

                    【讨论】:

                    • 说真的,如果你继续这样做,你会扭伤一些东西。 python $(srcdir)/myscript.py 为伟大的正义。
                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2011-10-30
                    • 1970-01-01
                    • 2019-09-07
                    • 1970-01-01
                    相关资源
                    最近更新 更多