Вопрос
У меня есть сценарий оболочки, который запускает одну и ту же команду в нескольких каталогах (фгит).Для каждого каталога я бы хотел, чтобы он показывал текущее приглашение + команду, которая будет там запущена.Как мне получить строку, соответствующую расшифрованный (расширенный)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
оценивает только цвета.
Решение
Одним из больших преимуществ программного обеспечения с открытым исходным кодом является то, что исходный код, ну, в общем, открытый :-)
Сам 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
, это, безусловно, вариант.
Другие советы
С момента 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}" )
Почему бы тебе не просто обрабатывать $PS1
Бежать подстановки самостоятельно? Серия заменений, таких как эти:
p="${PS1//\\u/$USER}"; p="${p//\\h/$HOSTNAME}"
Кстати, ZSH имеет возможность интерпретировать подсказки скидок.
print -P '%n@%m %d'
или
p=${(%%)PS1}
Мне нравится идея исправить Bash, чтобы сделать его лучше, и я ценю Paxdiablo's Verbose ответ Как исправить 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.
Два ответа: «Чистый 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. 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
Возможно, вам придется написать небольшую программу C, которая использует тот же код Code Bash. Упрашивается, это не очень портативно, поскольку вам придется компилировать его на каждой платформе, но это возможное решение.
Еще одна вероятность: без редактирования исходного кода 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
Команда генерирует файл, указанный, и вывод также отображается на STDOUT. Если имя файла опущено, он генерирует файл, называемый Teamscript.
Поскольку мы не заинтересованы в файле журнала в этом случае, имя файла указано как /dev/null
. Отказ Вместо этого STDOUT команды скрипта передается на awk для дальнейшей обработки.
- Весь код также может быть инкапсулирован в функцию.
- Кроме того, выходная подсказка также может быть назначена переменной.
- Этот подход также поддерживает анализ
PROMPT_COMMAND
...