Подтвердить что ты не робот

Echo расширенный PS1

У меня есть оболочка script, которая выполняет ту же самую команду в нескольких каталогах (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)')$

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

4b9b3361

Ответ 1

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

Сам 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.

(б) Добавьте 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 файл 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

paxsh исполняемый файл 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 в приглашение одну из переменных PSx $PS1 просто дает вам переменную, а команда evalps1 оценивает ее и выводит результат.

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

Ответ 2

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

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

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), используйте расширение ${[email protected]}:

$ printf '%s\n' "${[email protected]}"
[email protected]:~$
$

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


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

read -e -p "${[email protected]}"

Это позволит пользователю правильно интерпретировать myprompt.

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

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

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

( set +o emacs +o vi; printf '%s\n' "${[email protected]}" )

Ответ 3

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

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

Кстати, zsh имеет возможность интерпретировать подсказки.

print -P '%[email protected]%m %d'

или

p=${(%%)PS1}

Ответ 4

Мне нравится идея фиксации 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.

Ответ 5

Два ответа: "Pure bash" и "bash + ​​sed"

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

См. ниже для чистого .

расширение подсказок, 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.

[email protected]:~$ 
[email protected]:~$ 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

Оттуда вы находитесь в какой-то псевдо-интерактивной оболочке (без средств readline, но это не имеет значения)...

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

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

[email protected]:/tmp$ exit
[email protected]:/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

Pure

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
[email protected]:~$ 

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

Ответ 6

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

Ответ 7

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

$ TEST_PS1="\e[31;1m\[email protected]\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 генерирует указанный файл, а вывод также показан на stdout. Если имя файла опущено, оно генерирует файл с именем typescript.

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

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