Необъяснимое поведение команды оболочки, не экранирующее поведение в os.system и subprocess.check_output в Ubuntu 18.04 - PullRequest
0 голосов
/ 02 ноября 2018

Я озадачен тем, что Python не экранирует обратную косую черту в командах, переданных в os.system в Ubuntu 18.04 (все работает нормально в CentOS). Рассмотрим эту программу:

#!/usr/bin/env python
import os
import sys
import subprocess

def get_command(n):
    return "echo 'Should be %d backslashes: %s'" % (n, "\\" * n)

print("")
print("Using os.system directly:")
print("")
for n in range(1, 5):
    os.system(get_command(n))

print("")
print("Using subprocess.check_output:")
print("")
for n in range(1, 5):
    sys.stdout.write(subprocess.check_output(get_command(n), shell=True).decode('utf-8'))

print("")
print("Writing the bash code to a script and using os.system on the script:")
print("")
for n in range(1, 5):
    with open('/tmp/script.sh', 'w') as f:
        f.write(get_command(n))
    os.system('/bin/bash /tmp/script.sh')

Когда я запускаю его в Ubuntu 18.04, я получаю следующее:

Using os.system directly:

Should be 1 backslashes: \
Should be 2 backslashes: \
Should be 3 backslashes: \\
Should be 4 backslashes: \\

Using subprocess.check_output:

Should be 1 backslashes: \
Should be 2 backslashes: \
Should be 3 backslashes: \\
Should be 4 backslashes: \\

Writing the bash code to a script and using os.system on the script:

Should be 1 backslashes: \
Should be 2 backslashes: \\
Should be 3 backslashes: \\\
Should be 4 backslashes: \\\\

Обратите внимание, что он выводит один обратный слеш, где он должен выводить два, и выводит два обратного слеша, где он должен выводить три или четыре!

Однако на моем устройстве CentOS 7 все работает как положено. На обеих машинах оболочка /bin/bash. Вот случайный вывод вызова сценария python2.7 на всякий случай: https://gist.githubusercontent.com/mbautin/a97cfb6f880860f5fe6ce1474b248cfd/raw

Полагаю, самое безопасное поведение для вызова команд оболочки из Python - записать их во временный файл сценария!

1 Ответ

0 голосов
/ 03 ноября 2018

Хотя я могу согласиться с тем, что поведение странное, оно необъяснимо. Есть причина для поведения, которое не имеет ничего общего с Python или subprocess. Точно такое же поведение наблюдается в программе на C, использующей вызов system для ОС (Linux), как и в вашей программе на Python.

Причина связана с вашей оболочкой, но не совсем с bash. Причина скорее в том, что при звонке os.system() или семье subprocess.Popen() (включая subprocess.check_output()) с shell=True. Документация гласит, что "В POSIX с shell = True оболочкой по умолчанию является /bin/sh." Таким образом, оболочка, которая вызывает вашу команду echo, не является bash даже если это ваша оболочка по умолчанию и оболочка, из которой вы запускаете свой скрипт / запускаете Python.

Вместо этого ваша команда выполняется /bin/sh вашей системы. В течение долгого времени это указывало на /bin/bash (работает в режиме совместимости с POSIX) почти во всех версиях Linux, однако, в последнее время это изменилось в некоторых дистрибутивах, среди них Ubuntu (но, очевидно, не CentOS, поскольку вы не видите там то же самое поведение), которое теперь имеет /bin/sh точку вместо bin/dash:

$ ll /bin/sh
lrwxrwxrwx 1 root root 4 sep 23 12:53 /bin/sh -> dash*

Таким образом, ваш скрипт фактически выполняется dash вместо bash. И «для эффективности» (см. man dash в приглашении) dash выбрал внутреннюю реализацию echo вместо использования /bin/echo (используется bash). К сожалению, dash echo не так мощен, как /bin/echo и имеет другую интерпретацию строковых входов, а именно dash echo выполняет экранирование ряда команд обратной косой черты, что фактически означает, что он "глотает" " одна дополнительная обратная косая черта для вас.

Можно заставить /bin/echo вести себя таким же образом, указав параметр -e (см. man echo), но, к сожалению, невозможно встроить dash встроенный echo в не избежать обратной косой черты.

Так вот, это причина того, что вы видите. Хороший способ избежать этой проблемы - , а не полагаться на вызов системной оболочки. Если это одна команда, такая как echo, то лучше вообще не вызывать оболочку, удаляя флаг shell=True. Или, если вам нужна определенная функциональность оболочки, контролируйте ее запуск самостоятельно. И третий способ, в данном конкретном случае, это явно указать /bin/echo во время выполнения, поскольку это гарантирует, что используется «стандартный» echo:

#!/usr/bin/env python3
import sys
import subprocess
import shlex

def get_command(n):
    return "echo 'Should be {} backslahes: {}'".format(n, "\\"*n)

print("")
print("Using subprocess.check_output:")
print("")
for n in range(1, 5):

    # Direct invocation:
    cmd = get_command(n)
    sys.stdout.write(subprocess.check_output(shlex.split(cmd)).decode())

    # Controlling invocation shell:
    bash_cmd = ['/bin/bash', '-c'] + [cmd]
    sys.stdout.write(subprocess.check_output(bash_cmd).decode())

    # Using shell=True but point to /bin/echo
    echo_cmd = '/bin/' + cmd
    sys.stdout.write(subprocess.check_output(echo_cmd, shell=True).decode())

Обратите внимание, что при использовании без shell=True команда должна быть list, а не строкой. Это может быть shlex.split () , как показано.

Из этих подходов предпочтителен первый (прямой echo вызов) из-за соображений безопасности , если есть вероятность того, что некоторые параметры поступят из ненадежных источников. В этом случае, однако, shlex.split() также не следует использовать, поскольку это открывает те же уязвимости безопасности.

...