簡體   English   中英

在單行命令行中執行多行語句?

[英]Executing multi-line statements in the one-line command-line?

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

你可以

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的答案

這種樣式也可以在 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 文檔或此處)。

問題實際上不在於 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”內置和一些創造性的字符串插值之間,你可以做一些強大的單行。

問題是,你想走多遠,在什么時候編寫一個由你的 makefile 執行的小.py文件不是更好?


- 為了使這個答案也適用於 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")'

注意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 變量引用的利弊的討論,請參見下文。

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

  • 原始源代碼中的\\實例。
    • \\<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 )。
  • 單引號字符串傳遞給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'
        殼解釋兩個"\\b""\\\\b"為文字\\b ,這需要額外的令人眼花繚亂的數目\\實例以實現期望的效果:
        python -c "$(printf %b "import sys\\nprint('ro\\\\\\\\\\\\\\\\bs')")"

通過stdin而不是-c傳遞代碼

注意:我在這里專注於單線解決方案; xorho 的回答顯示了如何使用多行here-document - 但是一定要引用分隔符; 例如, <<'EOF' ,除非您明確希望 shell 在前面擴展字符串(帶有上述注意事項)。


bashkshzsh

ANSI C 引用的字符串( $'...' ) 與此處的字符串( <<<... ) 組合在一起:

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'

知道如何解決這個問題嗎?

您的問題是由 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)。

繼承人

使用heredoc (用<<創建)和 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

<<<<- )之后添加-允許您使用制表符進行縮進(Stackoverflow 將制表符轉換為空格,因此我縮進了 8 個空格以強調這一點)。 前導標簽將被剝離。

您可以在沒有標簽的情況下使用<<

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

EOF周圍加上引號可以防止參數算術擴展 這使得heredoc更加健壯。

Bash 多行字符串

如果你使用雙引號,你會得到 shell-expansion:

$ 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或 list comprehensions 來獲取 for 循環:

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

這些都是可悲和壞的。 不要做他們。

只需使用 return 並在下一行輸入:

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

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

工作正常。 使用“[]”來內聯你的 for 循環。

問題不在於import語句。 問題是控制流語句不能在 python 命令中內聯工作。 將該import語句替換為任何其他語句,您將看到相同的問題。

想一想:python 不可能內聯所有內容。 它使用縮進對控制流進行分組。

如果您的系統符合 Posix.2,它應該提供printf實用程序:

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

此變體最適合將多行腳本放在 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"""

(在 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 中調用此語法:)

希望這可以幫助,

干杯!


(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 技巧與竅門——第一版——Python 教程 | Dream.In.Code :

如果您還記得,單行生成器的標准格式是括號內的一種單行“for”循環。 這將產生一個“一次性”可迭代對象,它是一個只能在一個方向上迭代的對象,並且一旦到達終點就不能重復使用。

“列表推導式”看起來與常規的單行生成器幾乎相同,除了常規括號 - ( ) - 被方括號 - [ ] 替換。 alist comprehension 的主要優點是產生一個“列表”,而不是一個“一次性”的可迭代對象,這樣你就可以來回遍歷它,添加元素,排序等。

事實上,它是一個列表——它只是它的第一個元素在執行后立即變為 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. 數據結構:5.1.4中另有記錄 List Comprehensions — Python v2.7.4 文檔為“ List Comprehensions提供了一種創建列表的簡潔方法”; 據推測,這就是列表的有限“可執行性”在單行中發揮作用的地方。

好吧,希望我在這里不是太離譜......

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

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

我想要一個具有以下屬性的解決方案:

  1. 可讀
  2. 讀取標准輸入以處理其他工具的輸出

其他答案中沒有提供這兩個要求,因此這里是在命令行上執行所有操作時讀取 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
)

這個腳本提供了一個類似於 Perl 的命令行界面:

Pyliner - 在命令行上運行任意 Python 代碼的腳本(Python 配方)

當我需要這樣做時,我使用

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

請注意 sys.stdout.write 語句中換行符的三重反斜杠。

使用帶有三引號的 python -c

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

如果您不想像通過“python cmdfile.py”一樣觸摸 stdin 和模擬,您可以從 bash shell 執行以下操作:

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

如您所見,它允許您使用 stdin 來讀取輸入數據。 shell 在內部為輸入命令內容創建臨時文件。

還有一個選項,sys.stdout.write 返回 None,它使列表保持為空

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM