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


197

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


这回答了你的问题了吗?使用字典选择要执行的功能
pfabri

Answers:


182

你可以做

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的答案 / @ Crast的答案


最后一个选项在Windows中与python3一起使用时效果很好
Ian Ellis

1
@RudolfOlah希望您现在就知道了,但仅供参考,您需要包装python3 +版本的print语句,例如:python -c "exec(\"import sys\nfor r in range(10): print('rob')\")"
systrigger

@systrigger谢谢,我错过了我以为我很着急,却没有意识到,

89

这种样式也可以在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可以忍受行首未出现在here文档中的任何标记词(另请参见bash联机帮助页中的here文档或here)。


9
真好 要也传递论点,只需将它们放在之后<<EOF。但是请注意,最好引用定界符-例如,<<'EOF'以便保护here-document的内容免遭前期Shell扩展的影响。
mklement0 2015年

56

问题实际上与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))"

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

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


31


-为了使该答案也适用于Python 3.xprint被称为一个函数:在3.x中, print('foo')适用,而2.x也接受print 'foo'
-有关包括Windows的跨平台观点,请参阅kxr的有用答案

bashkshzsh

使用ANSI C引号的字符串$'...'),该字符串允许使用\n来表示在将字符串传递给之前扩展为实际换行符的换行符python

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

注意和语句\n之间的来实现换行符。importfor

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

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\b-通过膨胀$'...'(参见man printf用于所支持逃逸)
  • '实例转义为\'

如果您必须保持POSIX兼容

使用printf带有命令替换

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

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

  • \您的原始源代码中的实例。
    • \<char>序列-例如\n在此情况下,也是通常的嫌疑人如\t\r\b-通过扩展printf(见man printf所支持的转义序列)。
  • 单引号字符串传递给(sic)printf %b转义嵌入的单引号 '\''

    • 使用单引号可以防止shell解释字符串的内容。

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

        • python -c "$(printf %b "import sys\nfor r in range(10): print('rob is $HOME')")"
      • 但是,通常首选的方法是通过参数从shell传递值,并sys.argv在Python中通过访问它们。以上命令的等效项是:

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

      • 另外,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的答案显示了如何使用多行here-document-但是请务必引用定界符;例如,,<<'EOF'除非您明确希望外壳程序将字符串扩展到前面(上述注意事项附带)。


bashkshzsh

结合使用ANSI C引号的字符串$'...')和here字符串<<<...):

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

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

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'

2
这应该是当选答案!
首相

1
这也可行,并且可能是最好的答案,因为它包括完整的解释,太棒了!

22

任何想法如何解决?

您的问题是由以下事实引起的:用分隔的Python语句;仅允许为“小语句”,它们都是一线的。从Python文档中的语法文件中:

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

使用定界符(与创建<<)和Python的命令行界面选项-

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

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

您只需使用以下选项卡就可以做到<<

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

用引号引起来EOF可防止参数算术扩展。这使heredoc更加健壮。

重击多行字符串

如果使用双引号,则会得到shell扩展:

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

为了避免shell扩展,请使用单引号:

$ 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中有,那么您将度过一段糟糕的时光:

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

不要滥用map或列出理解以获得循环:

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

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


3
++的语法信息;(非扩展的)here-doc很方便,也是最可靠的解决方案,但显然不是一线之作。如果必须使用单行解决方案,则使用ANSI C引号的字符串(bashkshzsh)是一种合理的解决方案:(python -c $'import sys\nfor r in range(10): print(\'rob\')'您只需要担心转义单引号(可以避免使用双引号)和反斜杠)。
mklement0 '16

14

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

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

6
认真地讲,如果您继续这样做,将会使某些东西扭伤。python $(srcdir)/myscript.py为了伟大的正义。
杰森·奥伦多夫

10

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

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


9

问题不在于import语句。问题在于控制流语句无法在python命令中内联。用import其他任何语句替换该语句,您将看到相同的问题。

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


7

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

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

2
好的解决方案,但我建议使用printf %b '...' | python来增加鲁棒性,因为它会阻止printf解释%d输入字符串中的序列,例如(格式说明符)。另外,除非您明确希望将外壳扩展名预先应用到您的Python命令字符串(这可能会造成混淆),否则最好使用'(单引号)作为外部引号,以避免扩展名和外壳所应用的反冲处理双引号字符串。
mklement0 '16

5

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

(在10年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 EDIT)好吧,我想我终于找到了这些单线括起来的方括号。它们是“列表理解”(显然);首先请注意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技巧和窍门–第一版-Python教程| Dream.In.Code

回想一下,单行生成器的标准格式是括号内的一种“ for”循环。这将产生一个“一次性”可迭代对象,该对象只能在一个方向上迭代,到达终点后就不能重复使用。

“列表理解”看起来与常规单行生成器几乎相同,除了常规方括号-()被方括号-[]代替。列表理解的主要优点是产生了一个“列表”,而不是一个“一次性”的可迭代对象,因此您可以在它之间来回移动,添加元素,排序等。

实际上,它是一个列表-它只是它的第一个元素在执行后立即变为空:

$ 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中有其他说明。数据结构:5.1.4。列表推导— Python v2.7.4文档为“列表推导提供了一种创建列表的简洁方法”;大概就是列表的有限“可执行性”发挥作用的地方。

好吧,希望我在这里不会太过分……

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


4

此变体最便于移植,用于在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类外壳不同的字符。在PowerShell v3或更高版本中,可以通过--%在command-string参数之前插入“ stop-parsing”选项来使命令行正常工作。
mklement0 '16

@ mklement0“ 不需要的shell扩展 ”:好吧,将shell扩展插入到诸如print "path is %%PATH%%"resp之类的东西中。print "path is $PATH"通常是人们希望在脚本或命令行中使用的选项-除非人们像往常一样对平台进行转义。与其他语言相同。(Python语法本身并不经常建议以竞争方式使用%和$。)
kxr

如果将shell变量引用直接插入Python源代码中,那么根据定义,它将是不可移植的。我的观点是,即使您在单独的变量中构造了特定于平台的引用,并将它们作为参数传递给单个“无外壳” Python命令,也可能并不总是有效,因为您无法便携地保护双引号字符串:例如,如果您需要Python代码中的文字 $foo该怎么办?如果您\$foo为了像POSIX一样的外壳而逃避它,cmd.exe仍然会看到额外的东西\ 。这可能很少见,但值得一读。
mklement0

尝试在Windows PowerShell中执行此操作,但是问题是python -c exec(“”“ ...”“”)不会产生任何输出,无论...中的代码是否能够执行或不; 我可以在这里放些乱七八糟的东西,结果是一样的。我感觉外壳正在“吃” stdout和stderr流-如何使其吐出?
尤里


1

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

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")'
mklement0 '16

此答案有效,其他答案不适用于Python3

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; [如果sys.stdout.write(line * 2),则为sys.stdin中的行的一行]”


0

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

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

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


++用于不与脚本本身“用完” stdin(尽管-c "$(...)"相同,并且符合POSIX);给<(...)构造一个名字:过程替换;它也可以在ksh和中使用zsh
mklement0 '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.