Вопрос

У меня есть сценарий оболочки, который запускает одну и ту же команду в нескольких каталогах (фгит).Для каждого каталога я бы хотел, чтобы он показывал текущее приглашение + команду, которая будет там запущена.Как мне получить строку, соответствующую расшифрованный (расширенный)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 для дальнейшей обработки.

  1. Весь код также может быть инкапсулирован в функцию.
  2. Кроме того, выходная подсказка также может быть назначена переменной.
  3. Этот подход также поддерживает анализ PROMPT_COMMAND...
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top