Выполнение многострочных операторов в однострочной командной строке? - PullRequest
168 голосов
/ 11 января 2010

Я использую 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.

Ответы [ 17 ]

154 голосов
/ 11 января 2010

вы могли бы сделать

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

80 голосов
/ 01 марта 2010

этот стиль можно использовать и в 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 или здесь ).

44 голосов
/ 11 января 2010

Проблема на самом деле не в операторе 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, встроенным «map» и некоторой творческой интерполяцией строк вы можете сделать несколько мощных однострочных.

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

21 голосов
/ 10 апреля 2015


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

In 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-последовательности) двойных кавычек командной строки с внедренными ссылками на переменные оболочки.

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

  • Double \ экземпляров в вашем оригинальном исходном коде.
    • \<char> - такие как \n в этом случае, но также и обычные подозреваемые, такие как \t, \r, \b - расширены на $'...' (см. man printf для поддерживаемых ускользает)
  • Escape ' экземпляры как \'.

Если вы должны остаться POSIX-совместимым :

Использовать printf с подстановкой команд :

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

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

  • Double \ экземпляров в вашем оригинальном исходном коде.
    • \<char> последовательности - такие как \n в данном случае, но также и обычные подозреваемые, такие как \t, \r, \b - расширяются на printf (см. man printf для поддерживаемых escape-последовательности).
  • Передать строку в одинарных кавычках в printf %b и экранировать встроенные одинарные кавычки как '\'' (sic).

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

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

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

Примечание: я сосредотачиваюсь на однолинейных -линейных решениях здесь; ответ 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'
12 голосов
/ 07 июля 2016

Есть идеи, как это можно исправить?

Ваша проблема вызвана тем фактом, что операторы 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.

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

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

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

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

Размещение кавычек вокруг EOF предотвращает параметр и арифметическое расширение . Это делает 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 -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))"

Это все грустно и плохо. Не делай их.

11 голосов
/ 11 января 2010

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

user@host:~$ python -c "import sys
> for r in range(10): print 'rob'"
rob
rob
...
8 голосов
/ 11 января 2010

Проблема не в выражении import. Проблема в том, что операторы потока управления не работают встроенными в команду python. Замените этот оператор import на любой другой оператор, и вы увидите ту же проблему.

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

7 голосов
/ 12 января 2010

Если ваша система соответствует стандарту Posix.2, она должна содержать утилиту printf:

$ printf "print 'zap'\nfor r in range(3): print 'rob'" | python
zap
rob
rob
rob
7 голосов
/ 11 января 2012

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

Работает нормально. Используйте «[]», чтобы вставить цикл for.

4 голосов
/ 11 декабря 2011

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