В чем разница между PS1 и PROMPT_COMMAND

109

Просматривая эту замечательную ветку, я заметил, что в некоторых примерах используется

PS1="Blah Blah Blah"

и некоторые используют

PROMPT_COMMAND="Blah Blah Blah"

(а некоторые используют и то, и другое) при настройке приглашения в оболочке bash. Какая разница между двумя? Поиск SO и даже немного более широкий поиск в Google не дают мне результатов, поэтому даже ссылка на нужное место для поиска ответа будет оценена.

Джед Дэниелс
источник

Ответы:

59

Со страницы документации GNU Bash: http://www.gnu.org/software/bash/manual/bashref.html

PROMPT_COMMAND
    If set, the value is interpreted as a command to execute before
    the printing of each primary prompt ($PS1).

Я никогда не использовал его, но я мог бы использовать его обратно, когда у меня был только sh.

Скотт Томсон
источник
67

PROMPT_COMMAND может содержать обычные операторы bash, тогда как переменная PS1 может также содержать в переменной специальные символы, такие как '\ h' для имени хоста.

Например, вот мое приглашение bash, в котором используются как PROMPT_COMMAND, так и PS1. Код bash в PROMPT_COMMAND определяет, в какой ветке git вы можете быть, и отображает это в приглашении вместе со статусом выхода последнего запущенного процесса, именем хоста и базовым именем pwd. Переменная RET хранит возвращаемое значение последней выполненной программы. Это удобно, чтобы увидеть, была ли ошибка, и код ошибки последней программы, которую я запускал в терминале. Обратите внимание на внешнее окружение всего выражения PROMPT_COMMAND. Он включает PS1, поэтому эта переменная повторно оценивается каждый раз, когда оценивается переменная PROMPT_COMMAND.

PROMPT_COMMAND='RET=$?;\
  BRANCH="";\
  ERRMSG="";\
  if [[ $RET != 0 ]]; then\
    ERRMSG=" $RET";\
  fi;\
  if git branch &>/dev/null; then\
    BRANCH=$(git branch 2>/dev/null | grep \* |  cut -d " " -f 2);\
  fi;
PS1="$GREEN\u@\h $BLUE\W $CYAN$BRANCH$RED$ERRMSG \$ $LIGHT_GRAY";'

Пример вывода в каталоге, отличном от git, выглядит так:

sashan@dhcp-au-122 Documents  $ false
sashan@dhcp-au-122 Documents  1 $ 

а в каталоге git вы видите имя ветки:

sashan@dhcp-au-122 rework mybranch $ 

Обновить

После прочтения комментариев и ответа Боба я думаю, что лучше писать так, как он описывает. Он более удобен в обслуживании, чем то, что я изначально написал выше, где переменная PS1 устанавливается внутри PROMPT_COMMAND, которая сама по себе является сверхсложной строкой, которая оценивается во время выполнения с помощью bash. Это работает, но это сложнее, чем должно быть. Честно говоря, я написал эту PROMPT_COMMAND для себя около 10 лет назад, и она сработала, и я не особо об этом задумывался.

Для тех, кому интересно, как я изменил свои вещи, я в основном поместил код для PROMPT_COMMAND в отдельный файл (как описал Боб), а затем повторил строку, которую я собираюсь использовать как PS1:

GREEN="\[\033[0;32m\]"
CYAN="\[\033[0;36m\]"
RED="\[\033[0;31m\]"
PURPLE="\[\033[0;35m\]"
BROWN="\[\033[0;33m\]"
LIGHT_GRAY="\[\033[0;37m\]"
LIGHT_BLUE="\[\033[1;34m\]"
LIGHT_GREEN="\[\033[1;32m\]"
LIGHT_CYAN="\[\033[1;36m\]"
LIGHT_RED="\[\033[1;31m\]"
LIGHT_PURPLE="\[\033[1;35m\]"
YELLOW="\[\033[1;33m\]"
WHITE="\[\033[1;37m\]"
RESTORE="\[\033[0m\]" #0m restores to the terminal's default colour

if [ -z $SCHROOT_CHROOT_NAME ]; then
    SCHROOT_CHROOT_NAME=" "
fi
BRANCH=""
ERRMSG=""
RET=$1
if [[ $RET != 0 ]]; then
    ERRMSG=" $RET"
fi
if which git &>/dev/null; then
    BRANCH=$(git branch 2>/dev/null | grep \* |  cut -d " " -f 2)
else
    BRANCH="(git not installed)"
fi
echo "${GREEN}\u@\h${SCHROOT_CHROOT_NAME}${BLUE}\w \
${CYAN}${BRANCH}${RED}${ERRMSG} \$ $RESTORE"

и в моем .bashrc

function prompt_command {
    RET=$?
    export PS1=$(~/.bash_prompt_command $RET)
}
PROMPT_DIRTRIM=3
export PROMPT_COMMAND=prompt_command
сашанг
источник
1
Вы могли бы сократить одну из ваших линий: if git branch &>/dev/null ; then\ . Он перенаправляет как stdout, так и stderr в / dev / null. tldp.org/LDP/abs/html/io-redirection.html
3
Нет необходимости экспортировать PROMPT_COMMAND .
дольмен
2
Я думаю, что комментарий ceving в значительной степени верен и для этого ответа:Don't set PS1 in PROMPT_COMMAND! Set variables in PROMPT_COMMAND and use them in PS1
phil294
2
Я не вижу причин, почему менять PS1онлайн внутри PROMPT_COMMANDневыгодно. Совершенно полезный код. В отличие от ответа Боба, PS1переменная была построена правильно. Это позволяет использовать гораздо более сложные приглашения bash в зависимости от вашей реальной ситуации.
Christian Wolf
2
@ChristianWolf строительство PS1внутри PROMPT_COMMANDне имеет смысла. это пример того, как этого не делать. построить PS1один раз .bash_profile, просто используйте одинарные кавычки вместо двойных, чтобы подстановки переменных оценивались во время каждого запроса.
приятель
46

Разница в том, что PS1 - это фактическая используемая строка приглашения, а PROMPT_COMMAND - это команда, которая выполняется непосредственно перед приглашением. Если вам нужен самый простой и гибкий способ создания подсказки, попробуйте следующее:

Поместите это в свой .bashrc:

function prompt_command {
  export PS1=$(~/bin/bash_prompt)
}
export PROMPT_COMMAND=prompt_command

Затем напишите сценарий (bash, perl, ruby: на ваш выбор) и поместите его в ~ / bin / bash_prompt.

Сценарий может использовать любую информацию для создания приглашения. Это намного проще IMO, потому что вам не нужно изучать несколько причудливый язык подстановки, который был разработан только для переменной PS1.

Вы можете подумать, что можете сделать то же самое, просто установив PROMPT_COMMAND непосредственно на ~ / bin / bash_prompt и установив PS1 на пустую строку. Поначалу кажется, что это работает, но вскоре вы обнаружите, что код строки чтения ожидает, что PS1 будет установлен на фактическую подсказку, и когда вы прокручиваете обратные слова в истории, все в результате запутывается. Этот обходной путь приводит к тому, что PS1 всегда отражает последнее приглашение (поскольку функция устанавливает фактический PS1, используемый вызывающим экземпляром оболочки), и это позволяет нормально работать со строкой чтения и историей команд.

Боб
источник
17
Не устанавливайте PS1в PROMPT_COMMAND! Установите переменные PROMPT_COMMANDи используйте их в PS1. В противном случае вы потеряете возможность использовать PS1escape-последовательности, такие как \uили \h. Вы должны изобрести их заново PROMPT_COMMAND. Это могло бы быть возможно, но невозможно обойти потерю \[и \]которые отмечают начало и конец непечатаемых символов. Это означает, что вы не можете использовать цвета, не запутывая терминал длиной приглашения. И это сбивает с толку readlineпри редактировании команды, порождающей две строки. В итоге на экране большой беспорядок.
Ceving
1
@ceving Это правда! Можно использовать PROMPT_COMMAND, чтобы изменить формат вашего PS1 и получить лучшее из обоих миров
2grit
3
PROMPT_COMMANDвыполняется перед печатью PS1. Я не вижу проблем с настройкой PS1изнутри PROMPT_COMMAND, потому что после PROMPT_COMMANDзавершения будет печататься оболочка PS1, которая была изменена из PROMPT_COMMAND(или, в данном случае, внутри prompt_command)?
Фелипе Альварес
3
Предупреждение: PROMPT_COMMAND обычно не следует использовать для печати символов непосредственно в приглашении. Символы, напечатанные вне PS1, не учитываются Bash, что приведет к неправильному размещению курсора и удалению символов. Либо используйте PROMPT_COMMAND, чтобы установить PS1, либо посмотрите на команды внедрения. ( Arch Wiki Source )
meffect
3
Я не понимаю, почему все пытаются проделать какие-то трюки в PROMPT_COMMAND вместо того, чтобы просто использовать подстановку команд в PS1, export PS1='$(~/bin/bash_prompt)'делает то же самое, ошибка выглядит нормально
приятель
11

Откуда man bash:

PROMPT_COMMAND

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

PS1

Значение этого параметра раскрывается (см. ПОДПИСАНИЕ ниже) и используется в качестве основной строки приглашения. Значение по умолчанию - \ s- \ v \ $.

Если вы просто хотите установить строку приглашения, используя PS1 одного:

PS1='user \u on host \h$ '

Если вы хотите сделать что-то еще перед печатью приглашения, используйте PROMPT_COMMAND. Например, если вы хотите синхронизировать кэшированные записи на диск, вы можете написать:

PROMPT_COMMAND='sync'
Cyker
источник
1
Вы также можете установить заголовок терминала PS1без необходимости PROMPT_COMMAND, поскольку последовательность, которая устанавливает заголовок, может быть включена в PS1оболочку с помощью \[и \].
дольмен
1
@dolmen Хорошо. Затем давайте сделаем что-нибудь еще, например, динамически установим переменную среды.
Cyker
@Cyker вы можете динамически устанавливать переменную среды PS1, она просто будет установлена ​​в подоболочке, поэтому вы не сможете вернуть ее значение. но твой пример баналенPS1='$(sync)user \u on host \h$ '
приятель
1

разница в том, что

  • если вы выведете неполную строку из PROMPT_COMMAND, она испортит ваше приглашение bash
  • PS1заменители \Hи друзья
  • PROMPT_COMMANDзапускает его содержимое, PS1использует его как подсказку.

PS1выполняет расширение переменных и подстановку команд в каждом запросе, нет необходимости использовать PROMPT_COMMANDдля присвоения значения PS1или для запуска произвольного кода. вы можете легко сделать export PS1='$(uuidgen) $RANDOM'один раз .bash_profile, просто используйте одинарные кавычки

приятель
источник
0

Да, так что, чтобы попытаться по-настоящему закрепить это:

  • PROMPT_COMMAND- это удобная переменная / функция bash , но, строго говоря, нет ничего, что нельзя было бы сделать, используя в PS1одиночку, правильно?

Я имею в виду, если кто-то хочет установить другую переменную с областью действия за пределами подсказки: в зависимости от оболочки, эту переменную, вероятно, нужно будет объявить сначала снаружи $PS1или (в худшем случае), возможно, придется пофантазировать с чем-то, ожидающим в FIFO до звонит $PS1(и снова вооружается в конце $PS1); \u \hможет вызвать некоторые проблемы, особенно если вы используете некоторые фантазии регулярные выражения; но в остальном: можно добиться всего PROMPT_COMMAND, используя подстановку команд внутри$PS1 (и, может быть, в крайних случаях, явные подоболочки)?

Правильно?

Джефф Никсон
источник