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
оценивает только цвета.
Ответы
Ответ 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
, проще, первый ответ будет использовать sed.
См. ниже для чистого bash.
bash расширение подсказок, 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
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
...