Эхо расширенное PS1 - PullRequest
       3

Эхо расширенное PS1

21 голосов
/ 10 августа 2010

У меня есть сценарий оболочки, который выполняет одну и ту же команду в нескольких каталогах ( fgit ). Для каждого каталога я хотел бы показать текущее приглашение + команду, которая будет там выполняться. Как мне получить строку, которая соответствует декодированному (расширенному) PS1? Например, мой PS1 по умолчанию

${debian_chroot:+($debian_chroot)}\[\e[1;32m\]\u\[\e[0m\]@\[\e[1;32m\]\h\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]$(__git_ps1 ' (%s)')$

и я хотел бы повторить полученное приглашение username@hostname:/path$, желательно (но не обязательно) с хорошими цветами. Беглый взгляд на руководство по Bash не дал определенного ответа, и echo -e $PS1 оценивает только цвета.

Ответы [ 7 ]

18 голосов
/ 10 мая 2016

Начиная с Bash 4.4, вы можете использовать расширение @P:

Сначала я помещаю строку приглашения в переменную myprompt, используя read -r и цитируемый здесь документ:

read -r myprompt <<'EOF'
${debian_chroot:+($debian_chroot)}\[\e[1;32m\]\u\[\e[0m\]@\[\e[1;32m\]\h\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]$(__git_ps1 ' (%s)')$ 
EOF

Чтобы напечатать приглашение (как оно будет интерпретировано, если бы оно было PS1), используйте расширение ${myprompt@P}:

$ printf '%s\n' "${myprompt@P}"
gniourf@rainbow:~$
$

(На самом деле есть некоторые символы \001 и \002, пришедшие из \[ и \], которых вы не видите здесь, но вы можете увидеть их, если попытаетесь редактировать этот пост; Вы также увидите их в своем терминале, если наберете команды).


Чтобы избавиться от них, трюк, отправленный Деннисом Уильямсоном в список рассылки bash, должен использовать read -e -p, чтобы эти символы интерпретировались библиотекой readline:

read -e -p "${myprompt@P}"

В этом случае пользователю будет предложено правильно интерпретировать myprompt.

На этот пост Грег Вудледж ответил, что вы также можете просто убрать \001 и \002 из строки. Это может быть достигнуто так:

myprompt=${myprompt@P}
printf '%s\n' "${myprompt//[$'\001'$'\002']}"

На этот пост Чет Рами ответил, что вы также можете отключить редактирование строки вместе с set +o emacs +o vi. Так что это тоже будет делать:

( set +o emacs +o vi; printf '%s\n' "${myprompt@P}" )
11 голосов
/ 09 января 2013

Одним большим преимуществом программного обеспечения с открытым исходным кодом является то, что источник, ну, в общем, открыт: -)

Сам Bash не предоставляет эту функциональность, но есть несколько приемов, которые можно использовать для предоставления подмножества (например, замена \u на $USER и т. Д.). Однако это требует большого дублирования функций и обеспечения синхронизации кода с тем, что bash сделает в будущем.

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

Если вы загружаете код для bash (я смотрю на версию 4.2), есть файл y.tab.c, который содержит функцию decode_prompt_string():

char *decode_prompt_string (string) char *string; { ... }

Это функция, которая оценивает PSx переменные для запроса. Чтобы предоставить эту функциональность пользователям самой оболочки (а не просто использовать с помощью оболочки), вы можете выполнить следующие шаги, чтобы добавить внутреннюю команду evalps1.

Во-первых, измените support/mkversion.sh, чтобы не путать его с «настоящим» bash, и чтобы FSF мог отрицать все знания в целях гарантии :-) Просто измените одну строку (я добавил -pax бит):

echo "#define DISTVERSION \"${float_dist}-pax\""

Во-вторых, измените builtins/Makefile.in, чтобы добавить новый исходный файл. Это влечет за собой ряд шагов.

(a) Добавьте $(srcdir)/evalps1.def к концу DEFSRC.

(b) Добавьте evalps1.o к концу OFILES.

(c) Добавьте необходимые зависимости:

evalps1.o: evalps1.def $(topdir)/bashtypes.h $(topdir)/config.h \
           $(topdir)/bashintl.h $(topdir)/shell.h common.h

В-третьих, добавьте сам файл builtins/evalps1.def, это код, который выполняется при запуске команды evalps1:

This file is evalps1.def, from which is created evalps1.c.
It implements the builtin "evalps1" in Bash.

Copyright (C) 1987-2009 Free Software Foundation, Inc.

This file is part of GNU Bash, the Bourne Again SHell.

Bash is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Bash is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Bash.  If not, see <http://www.gnu.org/licenses/>.

$PRODUCES evalps1.c

$BUILTIN evalps1
$FUNCTION evalps1_builtin
$SHORT_DOC evalps1
Outputs the fully interpreted PS1 prompt.

Outputs the PS1 prompt, fully evaluated, for whatever nefarious purposes
you require.
$END

#include <config.h>
#include "../bashtypes.h"
#include <stdio.h>
#include "../bashintl.h"
#include "../shell.h"
#include "common.h"

int
evalps1_builtin (list)
     WORD_LIST *list;
{
  char *ps1 = get_string_value ("PS1");
  if (ps1 != 0)
  {
    ps1 = decode_prompt_string (ps1);
    if (ps1 != 0)
    {
      printf ("%s", ps1);
    }
  }
  return 0;
}

Основная часть этой лицензии - лицензия GPL (поскольку я изменил ее с exit.def) с очень простой функцией в конце для получения и декодирования PS1.

Наконец, просто создайте объект в каталоге верхнего уровня:

./configure
make

Появляющийся исполняемый файл bash можно переименовать в paxsh, хотя я сомневаюсь, что он когда-либо станет таким же распространенным, как и его предок: -)

И запустив его, вы можете увидеть его в действии:

pax> mv bash paxsh

pax> ./paxsh --version
GNU bash, version 4.2-pax.0(1)-release (i686-pc-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

pax> ./paxsh

pax> echo $BASH_VERSION
4.2-pax.0(1)-release

pax> echo "[$PS1]"
[pax> ]

pax> echo "[$(evalps1)]"
[pax> ]

pax> PS1="\h: "

paxbox01: echo "[$PS1]"
[\h: ]

paxbox01: echo "[$(evalps1)]"
[paxbox01: ]

Когда вы вводите в приглашение одну из PSx переменных, эхо-запрос $PS1 просто дает вам переменную, а команда evalps1 оценивает ее и выводит результат.

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

9 голосов
/ 11 августа 2010

Почему бы вам не обработать $PS1 escape-замены самостоятельно? Серия замен, таких как эти:

p="${PS1//\\u/$USER}"; p="${p//\\h/$HOSTNAME}"

Кстати, у zsh есть возможность интерпретации быстрых выходов.

print -P '%n@%m %d'

или

p=${(%%)PS1}
4 голосов
/ 03 июня 2014

Мне нравится идея исправить Bash, чтобы сделать его лучше, и я ценю подробный ответ paxdiablo о том, как исправить Bash.Я попробую когда-нибудь.

Однако, без исправления исходного кода Bash, у меня есть однострочный хак, который является переносимым и не дублирует функциональность, потому что обходной путь использует только Bash и его встроенные функции..

x="$(PS1=\"$PS1\" echo -n | bash --norc -i 2>&1)"; echo "'${x%exit}'"

Обратите внимание, что с tty и stdio происходит что-то странное, если посмотреть, как это тоже работает:

x="$(PS1=\"$PS1\" echo -n | bash --norc -i 2>&1 > /dev/null)"; echo "'${x%exit}'"

Так что, хотя я не понимаю, чтоПродолжая с stdio, мой взлом работает на Bash 4.2, NixOS GNU / Linux.Исправление исходного кода Bash определенно является более элегантным решением, и теперь, когда я использую Nix, это должно быть довольно легко и безопасно сделать.

2 голосов
/ 10 мая 2016

Два ответа: «Pure bash» и «bash + sed»

Поскольку это проще сделать с помощью sed, первый ответ будет использовать .

См. Ниже решение pure .

быстрое расширение, bash + sed

Вот мой хак:

ExpPS1="$(bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1 |
              sed ':;$!{N;b};s/^\(.*\n\)*\(.*\)\n\2exit$/\2/p;d')"

Объяснение:

Запуск bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1

Может возвращать что-то вроде:

To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

ubuntu@ubuntu:~$ 
ubuntu@ubuntu:~$ exit

Команда sed затем

  • объединит все строки в один буфер (:;$!{N;b};), а затем
  • заменит <everything, terminated by end-of-line><prompt>end-of-line<prompt>exit на <prompt>.(s/^\(.*\n\)*\(.*\)\n\2exit$/\2/).
    • , где <everything, terminated by end-of-line> становится \1
    • и <prompt> становится \2.
Контрольный пример:
while ExpPS1="$(bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1 |
          sed ':;$!{N;b};s/^\(.*\n\)*\(.*\)\n\2exit$/\2/p;d')"
    read -rp "$ExpPS1" && [ "$REPLY" != exit ] ;do
    eval "$REPLY"
  done

Оттуда вы попадаете в своего рода псевдоинтерактивную оболочку (без средств чтения строки, но это не имеет значения) ...

ubuntu@ubuntu:~$ cd /tmp
ubuntu@ubuntu:/tmp$ PS1="${debian_chroot:+($debian_chroot)}\[\e[1;32m\]\u\[\e[0m\]@\[\e[1;32m\]\h\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]$ "
ubuntu@ubuntu:/tmp$ 

(Последняя строка печатает оба ubuntu зеленым цветом, @, : и $ в черном и пути (/tmp) в синем)

ubuntu@ubuntu:/tmp$ exit
ubuntu@ubuntu:/tmp$ od -A n -t c <<< $ExpPS1 
 033   [   1   ;   3   2   m   u   b   u   n   t   u 033   [   0
   m   @ 033   [   1   ;   3   2   m   u   b   u   n   t   u 033
   [   0   m   : 033   [   1   ;   3   4   m   ~ 033   [   0   m
   $  \n

Чистый

ExpPS1="$(bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1)"
ExpPS1_W="${ExpPS1%exit}"
ExpPS1="${ExpPS1_W##*$'\n'}"
ExpPS1_L=${ExpPS1_W%$'\n'$ExpPS1}
while [ "${ExpPS1_W%$'\n'$ExpPS1}" = "$ExpPS1_W" ] ||
      [ "${ExpPS1_L%$'\n'$ExpPS1}" = "$ExpPS1_L" ] ;do
    ExpPS1_P="${ExpPS1_L##*$'\n'}"
    ExpPS1_L=${ExpPS1_L%$'\n'$ExpPS1_P}
    ExpPS1="$ExpPS1_P"$'\n'"$ExpPS1"
  done

whileЦикл необходим для правильной обработки многострочных запросов:

замените 1-ю строку на:

ExpPS1="$(bash --rcfile <(echo "PS1='${debian_chroot:+($debian_chroot)}\[\e[1;32m\]\u\[\e[0m\]@\[\e[1;32m\]\h\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]$ '") -i <<<'' 2>&1)"

или

ExpPS1="$(bash --rcfile <(echo "PS1='Test string\n$(date)\n$PS1'") -i <<<'' 2>&1)";

Последний многострочный напечатает:

echo "$ExpPS1"
Test string
Tue May 10 11:04:54 UTC 2016
ubuntu@ubuntu:~$ 

od -A n -t c  <<<${ExpPS1}
   T   e   s   t       s   t   r   i   n   g  \r       T   u   e
       M   a   y       1   0       1   1   :   0   4   :   5   4
       U   T   C       2   0   1   6  \r     033   ]   0   ;   u
   b   u   n   t   u   @   u   b   u   n   t   u   :       ~  \a
   u   b   u   n   t   u   @   u   b   u   n   t   u   :   ~   $
  \n
1 голос
/ 11 марта 2014

Еще одна возможность: без редактирования исходного кода bash, используя утилиту script (входит в пакет bsdutils в Ubuntu):

$ TEST_PS1="\e[31;1m\u@\h:\n\e[0;1m\$ \e[0m"
$ RANDOM_STRING=some_random_string_here_that_is_not_part_of_PS1
$ script /dev/null <<-EOF | awk 'NR==2' RS=$RANDOM_STRING
PS1="$TEST_PS1"; HISTFILE=/dev/null
echo -n $RANDOM_STRING
echo -n $RANDOM_STRING
exit
EOF
<prints the prompt properly here>

script команда генерирует указанный файл, и вывод также отображается на стандартный вывод. Если имя файла не указано, генерируется файл с именем typescript.

Так как в этом случае мы не заинтересованы в файле журнала, имя файла указано как /dev/null. Вместо этого stdout команды script передается в awk для дальнейшей обработки.

  1. Весь код также может быть заключен в функцию.
  2. Кроме того, выводной запрос также может быть назначен переменной.
  3. Этот подход также поддерживает синтаксический анализ PROMPT_COMMAND ...
1 голос
/ 11 августа 2010

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

...