Виконання багаторядкових операторів у однорядковому командному рядку?


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 / @ відповідь Креста


Останній варіант чудово працює з python3 в Windows
Ian Ellis

1
@RudolfOlah сподіваюся, що ви це знаєте вже зараз, але лише для довідки, вам потрібно обернути заяву для друку для версій python3 + на зразок:python -c "exec(\"import sys\nfor r in range(10): print('rob')\")"
systrigger

@systrigger дякую, я пропустив те, що я думаю, що я поспішав і не зрозумів, що так

89

цей стиль можна використовувати і в makefiles (а насправді він використовується досить часто).

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 або тут ).


9
Приємно. Щоб також передавати аргументи , просто розмістіть їх після <<EOF. Однак зауважте, що краще процитувати роздільник - наприклад, <<'EOF'- щоб захистити вміст документа тут від розширення передньої оболонки .
mklement0

56

Проблема полягає не в заяві про імпорт, а в тому, що знаходиться перед циклом 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файл, який замість цього виконує ваш makefile?


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"

Дивіться нижче для обговорення плюсів і мінусів використання командного рядка (попередньо оброблений послідовність запуску) з подвійним цитуванням командного рядка із вбудованими посиланнями змінної оболонки.

Щоб безпечно працювати зі $'...'струнами:

  • Подвійні \ екземпляри у вихідному вихідному коді.
    • \<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.argvв Python; еквівалент вищевказаної команди:

        • 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 ( $'...'), з рядком here ( <<<...):

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'

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, я вважаю дуже корисним включати складені заяви. Єдиний простий спосіб зробити це надійно - це з гередоками (штукатурою з посікс).

Гередоки

Використовуйте 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

Додавання -after <<(the <<-) дозволяє використовувати вкладки для відступу (Stackoverflow перетворює вкладки в пробіли, тому я розрізав 8 пробілів, щоб підкреслити це). Провідні вкладки будуть зняті.

Ви можете це зробити без вкладок, просто <<:

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

Розміщення лапок EOFперешкоджає розширенню параметрів та арифметики . Це робить гередок більш міцним.

Баш багаторядкових рядків

Якщо ви використовуєте подвійні лапки, ви отримаєте розширення оболонки:

$ 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
++ для інформації про граматику; (нерозширюваний) тут-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
...

6
Серйозно, ти збираєшся щось викручувати, якщо продовжуєш це робити. python $(srcdir)/myscript.pyза велику справедливість.
Джейсон Орендорф

10

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

Добре працює. Використовуйте "[]" для вбудованого циклу.


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

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 листопада '10 о 19:48) Я насправді не великий пітонер - але я знайшов цей синтаксис один раз, забув звідки, тому подумав, що його документую:

якщо ви використовуєте 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()команду, щоб виконати команду, вона виконується відразу після призначення; Однак, через огляд показує , що aце None:

$ 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 :

Якщо ви пам’ятаєте, стандартний формат генератора однорядних рядків - це певний рядок 'для' циклу всередині дужок. Це призведе до отримання одноразового ітерабельного об'єкта, який є об'єктом, який ви можете повторити лише в одному напрямку і який ви не зможете повторно використовувати, як тільки досягнете мети.

"Зрозуміння списку" виглядає майже так само, як звичайний однорядковий генератор, за винятком того, що звичайні дужки - () - замінені квадратними дужками - []. Основний прогрес алістського розуміння полягає в тому, що створюється "список", а не одноразовий ітерабельний об'єкт, так що ви можете переходити назад і назад по ньому, додавати елементи, сортувати тощо.

І справді це список - це просто його перший елемент, який не стає жодним, як тільки він виконується:

$ 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тепер має два елементи, оскільки його цикл явно виконується двічі; проте результат 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 вбік), але в кінцевому підсумку немає єдиного, повністю надійного міжплатформенного синтаксису, оскільки необхідність використання подвійних лапок потім несе ризик небажаного розширення оболонки, Windows, що вимагають виходу з різних символів, ніж оболонки, схожі на POSIX. У PowerShell v3 або новішої версії ви можете змусити ваші командні рядки працювати, вставивши параметр "стоп-розбір" --%перед аргументом командного рядка.
mklement0

@ mklement0 " небажані розширення оболонок ": добре, вставлення розширень оболонок у щось на зразок print "path is %%PATH%%"респ. print "path is $PATH"Зазвичай це варіант, який хочеться в сценарії чи командному рядку - якщо тільки не уникнути речей, як зазвичай для платформи. Те саме з іншими мовами. (Сам синтаксис Python не рекомендує регулярно використовувати% і $ 's у конкуруючий спосіб.)
kxr

Якщо ви вставляєте посилання змінної оболонки безпосередньо у вихідний код 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, але я все ще радий, що ти додав цю відповідь. Він відповідає на питання , я мав , коли я Googled цю сторінку.
tbc0

1

Коли мені потрібно було це зробити, я використовую

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

Зверніть увагу на потрійний зворотний проріз для нового рядка в операторі sys.stdout.write.


Це працює, але оскільки ви використовуєте echo -e, що нестандартно і вимагає bash, kshабо zsh, можливо, ви також можете використовувати $'...'рядок безпосередньо, що також спрощує python -c $'import sys\nsys.stdout.write("Hello World!\\n")'
пробіг

Ця відповідь працює, інші відповіді не працюють для 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 "імпортувати sys; [рядок для рядка в sys.stdin, якщо sys.stdout.write (рядок * 2)]"


0

Якщо ви не хочете торкатися stdin і імітувати так, як ніби ви передали "python cmdfile.py", ви можете зробити наступне з оболонки bash:

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

Як бачите, це дозволяє використовувати stdin для читання вхідних даних. Внутрішня оболонка створює тимчасовий файл для вмісту вхідної команди.


++ за те, що не "використовує" stdin із самим сценарієм (хоча -c "$(...)"робить те саме, і сумісний з POSIX); дати <(...)конструкції назву: процес заміщення ; він також працює в kshі zsh.
mklement0
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.