Настройте оболочку, чтобы всегда печатать приглашение на новой строке, например zsh

Если вывод команды не заканчивается на \n, следующее приглашение появляется неловко сразу после:

$ echo -n hai
hai$

Я только что заметил коллегу, чья оболочка (zsh, для чего она стоит) настроена на печать % (с фоном и передними цветами, инвертированными для выделения), а затем \n в таких случаях:

$ echo -n hai
hai%
$

Я хотел бы сделать то же самое. Я использую Bash. Это возможно? Если да, то что бы я добавил в свой файл ~/.bashrc?


UPDATE

Я потратил несколько часов на то, чтобы понять, как работает gniourf_gniourf. Я поделюсь своими выводами здесь, если они будут полезны другим.

  • ESC[6n - это интродуктор управляющей последовательности для доступа к позиции курсора./li >

  • \e не является допустимым представлением ESC при использовании echo в OS X (https://superuser.com/q/33914/176942). \033 можно использовать вместо этого.

  • IFS является Bash внутренним разделителем полей (http://tldp.org/LDP/abs/html/internalvariables.html#IFSREF).

  • read -sdR выглядит как сокращение для read -s -d -R, но на самом деле "R" не является флагом, это значение параметра -d (разделитель). Я решил написать read -s -d R, чтобы избежать путаницы.

  • Конструкция с двойными скобками (( ... )) позволяет арифметическое расширение и оценку (http://tldp.org/LDP/abs/html/dblparens.html).

Вот соответствующий фрагмент из моего .bashrc:

set_prompt() {
  # CSI 6n reports the cursor position as ESC[n;mR, where n is the row
  # and m is the column. Issue this control sequence and silently read
  # the resulting report until reaching the "R". By setting IFS to ";"
  # in conjunction with read -a flag, fields are placed in an array.
  local curpos
  echo -en '\033[6n'
  IFS=';' read -s -d R -a curpos
  curpos[0]="${curpos[0]:2}"  # strip leading ESC[
  (( curpos[1] > 1 )) && echo -e '\033[7m%\033[0m'

  # set PS1...
}

export PROMPT_COMMAND=set_prompt

Примечание. Строка curpos[0]="${curpos[0]:2}" не нужна. Я включил его, чтобы этот код можно было использовать в контексте, в котором строка также актуальна.

Ответы

Ответ 1

Небольшой трюк с использованием PROMPT_COMMAND:

Значение переменной PROMPT_COMMAND проверяется непосредственно перед тем, как Bash печатает каждое основное приглашение. Если PROMPT_COMMAND установлено и имеет ненулевое значение, тогда значение выполняется так же, как если бы оно было введено в командной строке.

Следовательно, если вы поместите это в свой .bashrc:

_my_prompt_command() {
    local curpos
    echo -en "\E[6n"
    IFS=";" read -sdR -a curpos
    ((curpos[1]!=1)) && echo -e '\E[1m\E[41m\E[33m%\E[0m'
}
PROMPT_COMMAND=_my_prompt_command

ты будешь очень хорош. Не стесняйтесь использовать другие причудливые цвета в части echo "%". Вы можете даже поместить содержимое этого в переменную, чтобы вы могли изменять его на лету.

Трюк: получите столбец курсора (с echo -en "\E[6n", за которым следует команда read), прежде чем распечатать приглашение, а если оно не 1, напечатайте % и новую строку.

Плюсы:

  • чистый Bash (без внешних команд),
  • нет подоболочек,
  • оставляет ваш PS1 все приятным и чистым: если вы иногда меняете свой PS1 (я делаю это, когда работаю в глубоко вложенном каталоге - мне не нравятся подсказки, которые выполняются на нескольких милях), это будет работать.

В качестве тройных комментариев вы можете использовать stty вместо эхо-сигнала с жестко запрограммированной управляющей последовательностью. Но это использует внешнюю команду и не является чистым Bash. Адаптируйте к вашим потребностям.


Что касается вашей проблемы с уродливыми кодами символов, которые получают случайную печать: это может быть потому, что в буфере tty все еще есть вещи. Может быть несколько исправлений:

  • Выключите, а затем echo терминала, используя stty.

    set_prompt() {
        local curpos
        stty -echo
        echo -en '\033[6n'
        IFS=';' read -d R -a curpos
        stty echo
        (( curpos[1] > 1 )) && echo -e '\033[7m%\033[0m'
    }
    PROMPT_COMMAND=set_prompt
    

    Основное отличие состоит в том, что комбо echo/read было обернуто с помощью stty -echo/stty echo, которое соответственно отключает и разрешает эхо-сигнал на терминале (что почему опция -s для read теперь бесполезный). В этом случае вы не получите правильную позицию курсора, и это может привести к появлению странных сообщений об ошибках или % не будет выводиться вообще.

  • Явно очистить буфер tty:

    set_prompt() {
        local curpos
        while read -t 0; do :; done
        echo -en '\033[6n'
        IFS=';' read -s -d R -a curpos
        (( curpos[1] > 1 )) && echo -e '\033[7m%\033[0m'
    }
    PROMPT_COMMAND=set_prompt
    
  • Просто сдайте, если буфер tty не может быть очищен:

    set_prompt() {
        local curpos
        if ! read -t 0; then
            echo -en '\033[6n'
            IFS=';' read -s -d R -a curpos
            (( curpos[1] > 1 )) && echo -e '\033[7m%\033[0m'
        # else
        #     here there was still stuff in the tty buffer, so I couldn't query the cursor position
        fi
    }
    PROMPT_COMMAND=set_prompt
    

В качестве примечания: вместо read ing в массиве curpos вы можете напрямую получить положение курсора в переменных, например curx и cury, как показано ниже:

IFS='[;' read -d R _ curx cury

Если вам нужна только позиция y cury:

IFS='[;' read -d R _ _ cury

Ответ 2

Благодаря Gilles на unix.stackexchange:

Вы можете сделать bash отображение приглашения на следующей строке, если предыдущая команда оставила курсор где-то, кроме последнего поля. Поместите это в свой .bashrc(вариант GetFree предложения Деннис Уильямсон)

Из двух связанных ответов я дистиллировал это решение:

PS1='\[\e[7m%\e[m\]$(printf "%$((COLUMNS-1))s")\r$ '

Пояснение:

  • \[\e[7m%\e[m\] - знак обратного видео процента
  • printf "%$((COLUMNS-1))s" - COLUMNS-1. Переменная COLUMNS сохраняет ширину вашего терминала, если установлены параметры checkwinsize. Поскольку printf находится внутри под-оболочки $(), вместо того, чтобы печатать на экране, его вывод будет добавлен в PS1
  • \r символ возврата каретки

Итак, в основном это знак %, длинная последовательность пробелов, за которой следует ключ возврата. Это работает, но, честно говоря, я не понимаю, почему это имеет желаемый эффект. В частности, почему это выглядит так, что добавляет разрыв строки только тогда, когда это необходимо, иначе нет лишней строки? Почему существуют необходимые пространства?

Ответ 3

если вы сделаете echo $PS1, вы увидите текущий код своего приглашения следующим образом:
\[\e]0;\[email protected]\h: \w\a\]${debian_chroot:+($debian_chroot)}\[email protected]\h:\w\$

теперь добавьте его с помощью \n следующим образом:

PS1="\n\[\e]0;\[email protected]\h: \w\a\]${debian_chroot:+($debian_chroot)}\[email protected]\h:\w\$"

теперь ваше приглашение всегда будет начинаться с новой строки.