Выполнение многострочных операторов в однострочной командной строке?

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.

Мартино
источник

Ответы:

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

jspcal
источник
Последний вариант прекрасно работает с python3 в Windows
Ян Эллис
1
@RudolfOlah надеюсь, что вы уже знаете это, но просто для справки, вам нужно обернуть оператор печати для версий Python3 +, таких как:python -c "exec(\"import sys\nfor r in range(10): print('rob')\")"
systrigger
@systrigger спасибо, я пропустил, что я думаю, что я спешил и не понял, что хе
89

этот стиль также может использоваться в make-файлах (и на самом деле он используется довольно часто).

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 или здесь ).

xorho
источник
9
Ницца. Чтобы также передать аргументы , просто разместите их после <<EOF. Обратите внимание, однако, что лучше заключить в кавычки - например, <<'EOF'- чтобы защитить содержимое документа здесь от расширений внешней оболочки .
mklement0
56

Проблема на самом деле не в операторе 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))"

Однако лямбда-выражения могут выполнять только выражения, а не операторы или множественные операторы, поэтому вы все равно не сможете делать то, что хотите. Однако между выражениями генератора, списком, лямбдами, sys.stdout.write, встроенной «картой» и некоторой творческой интерполяцией строк вы можете сделать несколько мощных однострочных.

Вопрос в том, как далеко вы хотите пойти, и в какой момент не лучше написать небольшой .pyфайл, который вместо этого выполняет ваш make-файл?

Crast
источник
31


- Чтобы этот ответ работал и с Python 3.x , printвызывается как функция : в 3.x работает только print('foo') , тогда как 2.x также принимает print 'foo'.
- Для кроссплатформенной перспективы, включающей Windows , смотрите полезный ответ kxr. .

В bash, kshилиzsh :

Используйте ANSI C- заключенную в кавычки строку ( $'...'), которая позволяет использовать \nдля представления новых строк, которые расширяются до фактических новых строк перед передачей строки python:

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

Обратите внимание \nна операторы между importи forдля создания переноса строки.

Чтобы передать значения переменных оболочки в такую ​​команду, наиболее безопасно использовать аргументы и обращаться к ним через sys.argvскрипт Python:

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

Ниже приведено обсуждение плюсов и минусов использования командной строки (escape-предварительная обработка) с двойными кавычками со встроенными ссылками на переменные оболочки.

Для безопасной работы со $'...'строками:

  • Двойные \ экземпляры в вашем исходном коде.
    • \<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и избежать внедренные одиночные кавычки , как '\'' (СИК).

    • Использование одинарных кавычек защищает содержимое строки от интерпретации оболочкой .

      • Тем не менее, для коротких скриптов Python (как в данном случае) вы можете использовать строку в двойных кавычках для включения значений переменных оболочки в ваши скрипты - при условии, что вам известны связанные с этим подводные камни (см. Следующий пункт); например, оболочка расширяется $HOMEдо домашнего каталога текущего пользователя. в следующей команде:

        • python -c "$(printf %b "import sys\nfor r in range(10): print('rob is $HOME')")"
      • Однако обычно предпочтительный подход заключается в передаче значений из оболочки через аргументы и доступ к ним через sys.argvPython; эквивалент вышеупомянутой команды:

        • python -c "$(printf %b 'import sys\nfor r in range(10): print("rob is " + sys.argv[1])')" "$HOME"
    • Хотя использование строки в двойных кавычках более удобно - она ​​позволяет использовать встроенные одинарные кавычки, неэкранированные и встроенные двойные кавычки, поскольку \"- это также делает строку подлежащей интерпретации оболочкой , что может быть или не быть намерением; $и `символы в вашем исходном коде, которые не предназначены для оболочки, могут вызвать синтаксическую ошибку или неожиданно изменить строку.

      • Кроме того, мешает собственная \обработка оболочки в строках в двойных кавычках; например, чтобы заставить Python производить буквальный вывод ro\b, вы должны перейти ro\\bк нему; с '...'строкой оболочки и сдвоенными \ случаями, мы получаем:
        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, требующую головокружительное количество дополнительных \экземпляров для достижения желаемого эффекта:
        python -c "$(printf %b "import sys\nprint('ro\\\\\\\\bs')")"

Для того, чтобы пройти через кодstdin , а не -c:

Примечание: Я сосредоточен на отдельных -LINE решений здесь; В ответе xorho показано, как использовать многострочный документ здесь - однако обязательно указывайте разделитель; например, <<'EOF'если вы явно не хотите, чтобы оболочка расширила строку заранее (что сопровождается оговорками, указанными выше).


В bash, kshилиzsh :

Объединить ANSI C- кавычки строку ($'...') с здесь-строкой (<<<...):

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'
mklement0
источник
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 флагом становится очень неудобно.

Когда я демонстрирую Python в среде оболочки bash, я считаю очень полезным включать составные операторы. Единственный простой способ сделать это надежно - с помощью heredocs (posix shell).

Heredocs

Используйте 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

Добавление -после <<(the <<-) позволяет использовать вкладки для отступа (Stackoverflow преобразует вкладки в пробелы, поэтому я выделил 8 пробелов, чтобы подчеркнуть это). Ведущие вкладки будут удалены.

Вы можете сделать это без вкладок просто <<:

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

Помещение кавычек EOFпредотвращает расширение параметров и арифметику . Это делает heredoc более устойчивым.

Bash многострочные строки

Если вы используете двойные кавычки, вы получите расширение оболочки:

$ 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 -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-заключенной в кавычки строки ( bash, kshили zsh) является разумным решением: python -c $'import sys\nfor r in range(10): print(\'rob\')'(вам придется беспокоиться только об экранировании одинарных кавычек (чего можно избежать, используя двойные кавычки) и обратные косые).
mklement0
14

просто используйте return и введите его в следующую строку:

user@host:~$ python -c "import sys
> for r in range(10): print 'rob'"
rob
rob
...
SilentGhost
источник
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утверждение любым другим утверждением, и вы увидите ту же проблему.

Подумайте об этом: питон не может встроить все. Он использует отступ для группировки потока управления.

Дэвид Бергер
источник
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
5

single/double quotesи backslashвезде:

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

Намного лучше:

$ python -c '
> import sys
> for i in range(10):
>   print "bob"
> '
кэв
источник
так. много. лучше.
Марк
4

(ответил 23 ноября 2010 года в 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 :)

Надеюсь это поможет,

Ура!


(РЕДАКТИРОВАТЬ Вт 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()команду для ее выполнения, она выполняется сразу после назначения; Однако, спустя осмотр показывает , что aэто None:

$ 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 - Первое издание - Уроки 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.

sdaau
источник
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') """)

Neat на 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"""
KXR
источник
++ для кроссплатформенного угла и конвертера. Ваша первая команда хороша с точки зрения переносимости (оставляя в стороне PowerShell), но в конечном итоге не существует единого полностью надежного кроссплатформенного синтаксиса, поскольку необходимость использования двойных кавычек в таком случае несет риск нежелательных расширений оболочки. Windows требует экранирования символов, отличных от POSIX-подобных оболочек. В PowerShell v3 или выше вы можете заставить ваши командные строки работать, вставив параметр «stop-parsing» --%перед аргументом командной строки.
mklement0
@ mklement0 " нежелательные расширения оболочки ": ну, вставка расширений оболочки в нечто вроде print "path is %%PATH%%"соотв. print "path is $PATH"обычно это вариант, который требуется в скрипте или командной строке - если не избежать обычных вещей для платформы. То же самое с другими языками. (Сам синтаксис Python не
всегда
Если вы вставите ссылки на переменные оболочки непосредственно в исходный код Python, он по определению не будет переносимым. Суть в том , что даже если вы вместо того, чтобы построили ссылки конкретной платформы в отдельных переменных , которые проходят в качестве аргументов в виде единой «оболочка свободной» команду Python, что может не всегда работать, потому что вы не можете защитить двойные кавычки строки переносимой : например, что если вам нужен литерал $foo в вашем коде Python? Если вы избежите этого как \$fooв пользу POSIX-подобных оболочек, cmd.exeвсе равно увидите дополнительное \ . Это может быть редко, но это стоит знать.
mklement0
Попытка сделать это в Windows PowerShell, но проблема в том, что python -c exec ("" "..." "") не производит никакого вывода, независимо от того, может ли код в ... быть в состоянии выполнить или не; Я мог бы положить туда любую тарабарщину, и результат был бы таким же. Я чувствую, что оболочка «съедает» потоки stdout и stderr - как я могу заставить их выплюнуть их?
Юрий
2

Этот скрипт предоставляет Perl-подобный интерфейс командной строки:

Pyliner - скрипт для запуска произвольного кода Python из командной строки (рецепт Python)

Дрю Гулино
источник
Я думаю, что они хотели что-то исправить, чтобы не использовать другой инструмент. Во всяком случае, хороший совет
enrico.bacis
Я согласен с @ enrico.bacis, но я все еще рад, что вы добавили этот ответ. Он отвечает на вопрос, который у меня был, когда я погуглил эту страницу.
tbc0
1

Когда мне нужно было сделать это, я использую

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

Обратите внимание на тройную обратную косую черту для новой строки в операторе sys.stdout.write.

Devilholk
источник
Это работает, но поскольку вы используете echo -e, что является нестандартным и требует bash, kshили zshвы также можете использовать $'...'строку напрямую, что также упрощает экранирование:python -c $'import sys\nsys.stdout.write("Hello World!\\n")'
mklement0
Этот ответ работает, другие ответы не работают для 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.stdin if sys.stdout.write (line * 2)]"

user993533
источник
0

Если вы не хотите трогать stdin и имитировать, как если бы вы передали «python cmdfile.py», вы можете сделать следующее из оболочки bash:

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

Как видите, он позволяет использовать stdin для чтения входных данных. Внутренне оболочка создает временный файл для содержимого входной команды.

Thava
источник
++ для того, чтобы не «использовать» стандартный ввод с самим сценарием (хотя -c "$(...)"делает то же самое и является POSIX-совместимым); дать <(...)конструкции имя: подстановка процесса ; это также работает в kshи zsh.
mklement0