Есть ли способ узнать, как пользователь вызывал программу из bash?

Вот проблема: у меня есть этот скрипт foo.py, и если пользователь вызывает его без --bar, я хотел бы отобразить следующее сообщение об ошибке:

Please add the --bar option to your command, like so:
    python foo.py --bar

Теперь самое сложное в том, что пользователь мог вызвать команду несколькими способами:

  • Возможно, они использовали python foo.py, как в примере
  • Возможно, они использовали /usr/bin/foo.py
  • Они могут иметь псевдоним оболочки frob='python foo.py' и фактически запускаются frob
  • Может быть, это даже псевдоним git flab=!/usr/bin/foo.py, и они использовали git flab

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

sys.argv всегда содержит foo.py, и /proc/$$/cmdline не знает об псевдонимах. Мне кажется, что единственным возможным источником этой информации был бы сам bash, но я не знаю, как это спросить.

Есть идеи?

ОБНОВЛЕНИЕ Как насчет того, чтобы ограничить возможные сценарии только перечисленными выше?

ОБНОВЛЕНИЕ 2 . Многие люди написали очень хорошее объяснение того, почему это невозможно в общем случае, поэтому я хотел бы ограничить свой вопрос этим:

При следующих допущениях:

  • Сценарий был запущен в интерактивном режиме из bash
  • Сценарий запускался одним из этих 3 способов :
    1. foo <args> где foo - символическая ссылка /usr /bin /foo -> foo.py
    2. git foo где alias.foo =! /usr /bin /foo в ~/.gitconfig
    3. git baz где alias.baz =! /usr /bin /foo в ~/.gitconfig

Есть ли способ отличить 1 и (2,3) от сценария? Есть ли способ отличить 2 и 3 от сценария?

Я знаю, что это длинный путь, поэтому я сейчас принимаю ответ Чарльза Даффи.

ОБНОВЛЕНИЕ 3 . До сих пор наиболее многообещающий взгляд был предложен Чарльзом Даффи в комментариях ниже. Если я могу заставить своих пользователей иметь

trap 'export LAST_BASH_COMMAND=$(history 1)' DEBUG

в их .bashrc, тогда я могу использовать что-то подобное в моем коде:

like_so = None
cmd = os.environ['LAST_BASH_COMMAND']
if cmd is not None:
    cmd = cmd[8:]  # Remove the history counter
    if cmd.startswith("foo "):
        like_so = "foo --bar " + cmd[4:]
    elif cmd.startswith(r"git foo "):
        like_so = "git foo --bar " + cmd[8:]
    elif cmd.startswith(r"git baz "):
        like_so = "git baz --bar " + cmd[8:]
if like_so is not None:
    print("Please add the --bar option to your command, like so:")
    print("    " + like_so)
else:
    print("Please add the --bar option to your command.")

Таким образом, я показываю общее сообщение, если мне не удается получить их метод вызова. Конечно, если я собираюсь положиться на изменение среды моих пользователей, я также могу гарантировать, что различные псевдонимы экспортируют свои собственные переменные среды, на которые я могу смотреть, но по крайней мере этот способ позволяет мне использовать ту же технику для другой сценарий, который я мог бы добавить позже.

14 голосов | спросил itsadok 12 J000000Thursday18 2018, 12:07:47

5 ответов


0

Нет, нет способа увидеть исходный текст (до псевдонимов /функций /и т. д.).

Запуск программы в UNIX выполняется следующим образом на базовом уровне системного вызова:

int execve(const char *path, char *const argv[], char *const envp[]);

В частности, есть три аргумента:

  • Путь к исполняемому файлу
  • Массив argv (первый элемент которого - argv[0] или $0 - передается в этот исполняемый файл для отображения имени, под которым он был запущен)
  • Список переменных среды

Здесь нигде нет строки, предоставляющей исходную введенную пользователем команду оболочки, из которой был запрошен вызов нового процесса. Это особенно верно, поскольку не все программы запускаются из оболочки вообще ; рассмотрим случай, когда ваша программа запускается из другого скрипта Python с shell=False.


В UNIX принято считать, что ваша программа была запущена с любого имени, указанного в argv[0]; это работает для символических ссылок.

Вы даже можете увидеть, как стандартные инструменты UNIX делают это:

$ ls '*.txt'         # sample command to generate an error message; note "ls:" at the front
ls: *.txt: No such file or directory
$ (exec -a foobar ls '*.txt')   # again, but tell it that its name is "foobar"
foobar: *.txt: No such file or directory
$ alias somesuch=ls             # this **doesn't** happen with an alias
$ somesuch '*.txt'              # ...the program still sees its real name, not the alias!
ls: *.txt: No such file 

Если вы делаете хотите создать командную строку UNIX, используйте pipes.quote() (Python 2) или ---- +: = 7 =: + ---- (Python 3), чтобы сделать это безопасно.

shlex.quote()

Опять же, это не «не раскрывает» псевдонимы, не возвращает код, который был вызван для вызова функции, которая вызвала вашу команду и т. д .; нет звонка этого звонка.


То, что можно использовать для поиска экземпляра git в родительском дереве процессов и обнаружения его списка аргументов:

try:
    from pipes import quote # Python 2
except ImportError:
    from shlex import quote # Python 3

cmd = ' '.join(quote(s) for s in open('/proc/self/cmdline', 'r').read().split('\0')[:-1])
print("We were called as: {}".format(cmd))
ответил Charles Duffy 19 J000000Thursday18 2018, 01:36:42
0

См. примечание внизу относительно первоначально предложенного сценария оболочки.

Новый более гибкий подход заключается в том, чтобы скрипт python предоставил новую опцию командной строки, позволяющую пользователям указывать пользовательскую строку, которую они предпочли бы видеть в сообщениях об ошибках.

Например, если пользователь предпочитает вызывать скрипт python 'myPyScript.py' через псевдоним, он может изменить определение псевдонима с это:

  alias myAlias='myPyScript.py [email protected]'

на это:

  alias myAlias='myPyScript.py --caller=myAlias [email protected]'

Если они предпочитают вызывать скрипт python из скрипта оболочки, он может использовать дополнительную опцию командной строки, например, так:

  #!/bin/bash
  exec myPyScript.py "[email protected]" --caller=${0##*/}

Другие возможные применения этого подхода:

  bash -c myPyScript.py --caller="bash -c myPyScript.py"

  myPyScript.py --caller=myPyScript.py

Для просмотра расширенных командных строк, вот скрипт 'pyTest.py', основанный на отзывах @CharlesDuffy, который перечисляет cmdline для запуска скрипт Python, а также родительский процесс, который его породил. Если используется аргумент -caller, он появится в командной строке, хотя псевдонимы будут расширены и т. Д.

#!/usr/bin/env python

import os, re

with open ("/proc/self/stat", "r") as myfile:
  data = [x.strip() for x in str.split(myfile.readlines()[0],' ')]

pid = data[0]
ppid = data[3]

def commandLine(pid):
  with open ("/proc/"+pid+"/cmdline", "r") as myfile:
    return [x.strip() for x in str.split(myfile.readlines()[0],'\x00')][0:-1]

pid_cmdline = commandLine(pid)
ppid_cmdline = commandLine(ppid)

print "%r" % pid_cmdline
print "%r" % ppid_cmdline

После сохранения этого файла в файле с именем «pytest.py» и последующего вызова из скрипта bash с именем «pytest.sh 'с различными аргументами, вот вывод:

$ ./pytest.sh a b "c d" e
['python', './pytest.py']
['/bin/bash', './pytest.sh', 'a', 'b', 'c d', 'e']

ПРИМЕЧАНИЕ. Критика исходного сценария-оболочки aliasTest.sh была допустимой. Хотя наличие предварительно определенного псевдонима является частью спецификации вопроса, и можно предположить, что он существует в пользовательской среде, предложение определило псевдоним (создавая ложное впечатление, что он был частью рекомендации, а не указанным часть среды пользователя), и он не показывает, как оболочка будет взаимодействовать с вызываемым скриптом python. На практике пользователь должен был бы либо указать источник оболочки, либо определить псевдоним внутри оболочки, а сценарий python должен был бы делегировать печать сообщений об ошибках нескольким настраиваемым сценариям вызова (где находилась информация вызова), а клиенты вызывать сценарии оболочки. Решение этих проблем привело к более простому подходу, который можно распространить на любое количество дополнительных вариантов использования.

Ниже приведена менее запутанная версия исходного сценария.

#!/bin/bash
shopt -s expand_aliases
alias myAlias='myPyScript.py'

# called like this:
set -o history
myAlias [email protected]
_EXITCODE=$?
CALL_HISTORY=( `history` )
_CALLING_MODE=${CALL_HISTORY[1]}

case "$_EXITCODE" in
0) # no error message required
  ;;
1)
  echo "customized error message #1 [$_CALLING_MODE]" 1>&2
  ;;
2)
  echo "customized error message #2 [$_CALLING_MODE]" 1>&2
  ;;
esac

Вот вывод:

$ aliasTest.sh 1 2 3
['./myPyScript.py', '1', '2', '3']
customized error message #2 [myAlias]
ответил philwalk 18 J000000Wednesday18 2018, 23:53:26
0

Невозможно провести различие между тем, когда интерпретатор сценария явно указан в командной строке, и тем, когда он выводится ОС из строки hashbang.

Доказательство:

$ cat test.sh 
#!/usr/bin/env bash

ps -o command $$

$ bash ./test.sh 
COMMAND
bash ./test.sh

$ ./test.sh 
COMMAND
bash ./test.sh

Это не позволит вам обнаружить разницу между первыми двумя случаями в вашем списке.

Я также уверен, что не существует разумного способа определения других (опосредованных) способов вызова команды.

ответил Leon 18 J000000Wednesday18 2018, 18:31:02
0

Я вижу два способа сделать это:

  • Самое простое, как предлагает 3sky, - это проанализировать командную строку из скрипта Python. argparse может быть использован для этого надежным способом. Это работает, только если вы можете изменить этот скрипт.
  • Более сложный, немного более общий и сложный способ - изменить исполняемый файл python в вашей системе.

Поскольку первый вариант хорошо задокументирован, вот еще немного подробностей о втором:

Независимо от того, как называется ваш скрипт, выполняется python. Цель здесь - заменить исполняемый файл python сценарием, который проверяет, является ли foo.py входит в число аргументов, и если это так, проверьте, что --bar также. Если нет, распечатайте сообщение и вернитесь.

В любом другом случае выполните настоящий исполняемый файл Python.

Теперь, надеюсь, запуск python выполняется с помощью следующего шебанга: #!/usr/bin/env python3 или через python foo.py, а не вариант #!/usr/bin/python или /usr/bin/python foo.py. Таким образом, вы можете изменить переменную $PATH и добавить в начало каталог, в котором ложно python постоянно.

В другом случае вы можете заменить /usr/bin/python executable, рискуя не поиграть с обновлениями.

Более сложный способ сделать это, вероятно, будет с использованием пространств имен и монтирования, но, вероятно, вышеупомянутого вполне достаточно, особенно если у вас есть права администратора.


Пример того, что может работать как скрипт:

#!/usr/bin/env bash

function checkbar
{
    for i in "[email protected]"
    do
            if [ "$i" = "--bar" ]
            then
                    echo "Well done, you added --bar!"
                    return 0
            fi
    done
    return 1
}

command=$(basename ${1:-none})
if [ $command = "foo.py" ]
then
    if ! checkbar "[email protected]"
    then
        echo "Please add --bar to the command line, like so:"
        printf "%q " $0
        printf "%q " "[email protected]"
        printf -- "--bar\n"
        exit 1
    fi
fi
/path/to/real/python "[email protected]"

Однако, перечитав ваш вопрос, очевидно, что я его неправильно понял. На мой взгляд, это нормально, просто напечатать либо «foo.py должен называться как foo.py --bar», «пожалуйста, добавьте bar к вашим аргументам», либо «пожалуйста, попробуйте (вместо)», независимо от того, что пользователь ввел:

  • Если это псевдоним (git), то это однократная ошибка, и пользователь попробует свой псевдоним после его создания, чтобы он знал, куда поместить --bar часть
  • с помощью /usr/bin/foo.py или python foo.py:
    • Если пользователь не очень разбирается в командной строке, он может просто вставить отображаемую рабочую команду, даже если он не знает разницы
    • Если это так, они должны без проблем понять сообщение и настроить свою командную строку.
ответил MayeulC 18 J000000Wednesday18 2018, 18:03:15
0

Я знаю, что это bash, но я думаю, что самый простой способ - это изменить 'foo.py'. Конечно, это зависит от уровня сложности сценария, но, возможно, он подойдет. Вот пример кода:

#!/usr/bin/python

import sys

if len(sys.argv) > 1 and sys.argv[1] == '--bar':
    print 'make magic'
else:
    print 'Please add the --bar option to your command, like so:'
    print '    python foo.py --bar'

В этом случае не имеет значения, как пользователь запускает этот код.

$ ./a.py
Please add the --bar option to your command, like so:
    python foo.py --bar

$ ./a.py -dua
Please add the --bar option to your command, like so:
    python foo.py --bar

$ ./a.py --bar
make magic

$ python a.py --t
Please add the --bar option to your command, like so:
    python foo.py --bar

$ /home/3sky/test/a.py
Please add the --bar option to your command, like so:
    python foo.py --bar

$ alias a='python a.py'
$ a
Please add the --bar option to your command, like so:
    python foo.py --bar

$ a --bar
make magic
ответил 3sky 18 J000000Wednesday18 2018, 14:34:55

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132