Красные глаза

  • Tutorial


Привет, представляю вашему вниманию небольшой туториал по оформлению приглашения командной строки с эффектом красных глаз.

Все кто работал в консоли какого-нибудь Линукса наверно отмечали удобную особенность отображать текущую папку, имя пользователя, имя сервера и что-то еще в зависимости от дистра в строке приглашения. Мне тоже это всегда нравилось. Но иногда получалось вот так:



Сложно «творить» в строке из трех символов, конечно курсор переходит на следующую строку но это скорей еще больше бесит чем помогает. И вот в какой-то момент я сказал себе:



Я решил вернуть себе все пространство командной строки и больше не отдавать никому и никогда. Встал вопрос, как изменить текст приглашения командной строки? Оказалось, очень просто, достаточно изменить специальную системную переменную `PS1`.



Да, прямо в терминале можно задать новый текст приглашения. Но как сохранить изменения? Да и без информации о текущем каталоге как-то неуютно себя чувствуешь, постоянно задаешь себе вопрос: «Где я?» Поможет файл ~/.bashrc, в нём можно сохранить изменение PS1, а чтобы информация о текущей директории не занимала рабочее пространство, я решил разместить её не В а НАД командной строкой. Добавим в файл ~/.bashrc строку:

PS1='$PWD\n# '

Обратите внимания на одинарные кавычки, если мы используем двойные, то вместо указателя на переменную $PWD (системная переменная, в которой хранится полный путь текущей папки, аналог команда pwd) в строку приглашения запишется ее значение(текущий каталог) и меняться при переходе из папки в папку не будет. Выглядит это так:



Командная строка полностью свободна, но имя папки сливается с содержимым если выполнить команду ls. Придется отделить мух от котлет имя папки от содержимого. Я решил добавить «рамки» для $PWD, добавив сверху и снизу строки из символов "-". Как узнать количество символов в строке? Для этого тоже есть системная переменная $COLUMNS. А чтобы быстро сформировать строку с необходимым количеством символов "-" воспользуемся командой printf:

printf -v line "%${COLUMNS}s"

Эта команда создаст переменную $line и заполнит её пробелами в количестве $COLUMNS но нам нужны не пробелы а "-", для этого используем следующий трюк:

line=${line// /-} # заменить все пробелы на -

Добавим этот код в ~/.bashrc

printf -v line "%${COLUMNS}s"
line=${line// /-}
PS1='\n$line\n$PWD\n$line\n# '



Отлично, но если сейчас изменить размер окна терминала, размер «линий» не изменится и красота пропадет:



Чтобы поправить ситуацию перенесем новый код в функцию info и добавим её в PS1:

info () {
    printf -v line "%${COLUMNS}s"
    line=${line// /-}
    printf "\n$line\n$PWD\n$line\n# "
}
PS1='$(info)'

Можно совместить приятное с полезным, добавив в верхний ограничитель «вставку» с именем хоста. Вставка с именем будет посередине, для этого нам необходимо вычислить центр линии(с учетом длинны названия хоста и доп. символов):

info () {
    name_length="{ $HOSTNAME }"
    name_length=${#name_length}
    top_line_left=$[(COLUMNS-name_length)/2]
    top_line_right=$[COLUMNS-(top_line_left+name_length)]
    printf -v top_line "%${top_line_left}s{_S_${HOSTNAME}_S_}%${top_line_right}s"
    printf -v bot_line "%${COLUMNS}s"
    bot_line=${bot_line// /-}
    top_line=${top_line// /-}
    top_line=${top_line//_S_/ }
    printf "\n$top_line\n$PWD\n$bot_line\n# "
}
PS1='$(info)'



Я заключил имя хоста в фигурные скобки с пробелами, но вместо пробелов вокруг $HOSTNAME используются символы "_S_" которые затем меняются на пробелы. Это необходимо потому, что в итоговой строке все пробелы заменяются на "-", а внутри вставки должны остаться пробелы. Добавим красок, для этого подготовим переменные с кодами изменения цвета текста в терминале, я использовал такие цвета:

RED='\e[31m' # красный
GRN='\e[32m' # зелёный
YLW='\e[33m' # жёлтый
BLU='\e[34m' # синий
MGN='\e[35m' # пурпурный
DEF='\e[0m'  # вернуть значения по умолчанию
BLD='\e[1m'  # жирно
DIM='\e[2m'  # тускло

Добавим эти переменные в наш код:

info () {
    name_length="{ $HOSTNAME }"
    name_length=${#name_length}
    top_line_left=$[(COLUMNS-name_length)/2]
    top_line_right=$[COLUMNS-(top_line_left+name_length)]
    printf -v top_line "%${top_line_left}s{_S_$DEF$BLD$HOSTNAME${DEF}_S_$GRN}%${top_line_right}s"
    printf -v bot_line "%${COLUMNS}s"
    bot_line=$GRN${bot_line// /-}$DEF
    top_line=${top_line// /-}
    top_line=$GRN${top_line//_S_/ }$DEF
    printf "\n$top_line\n$BLD$BLU$PWD$DEF\n$bot_line\n# "
}
PS1='$(info)'



Идем дальше, правая часть выглядит пусто, можно разместить там дату, и время:

printf -v date "%(%a %d %b %T)T"

Чтобы разместить это справа необходимо добавить какое-то количество пробелов после $PWD, какое, давайте посчитаем:

center_space=$[COLUMNS-${#date}-${#PWD}]
((center_space<0)) && center_space=1
...
printf "\n$top_line\n$BLD$BLU$PWD$DEF%${center_space}s$DIM$date\n$bot_line\n# "



Можно ли сделать лучше? Конечно, добавим вывод git status если находимся в папке с git проектом:

    git_tst= git_clr=
    [[ -d .git ]] && {
        git_tst=($(git status -c color.ui=never -sb))
        git_tst="GIT ${git_tst[*]} " # простой для теста
        git_clr=(GIT $(git -c color.ui=always status -sb))
        git_clr="GIT ${git_clr[*]} " # цветной для вывода
    }
    ...
    center_space=$[COLUMNS-${#date}-${#PWD}-${#git_tst}]
    ...
    printf "\n$top_line\n$BLD$BLU$PWD$DEF%${center_space}s$git_clr$DIM$date\n$bot_line\n\$ "



Обратите внимания что git_clr и git_tst сначала записываются массивом, а затем преобразуются в переменные. Это необходимо для того чтобы удалить переходы строки из вывода git status. Но где же глаза? Сейчас будут глаза О_о, создадим массив с базовым набором глаз:

eyes=(O o ∘ ◦ ⍤ ⍥)

И посчитаем их количество:

en=${#eyes[@]}

Добавим символ рта:

mouth='_'

И сделаем генератор случайных морд:

face () {
    printf "$YLW${eyes[$[RANDOM%en]]}$mouth${eyes[$[RANDOM%en]]}$DEF"
}

Глаза будут находиться по краям информационного поля, необходимо учесть их при расчете количества пробелов в середине, подготовим для этого отдельную переменную:

face_tst='O_o  o_O'
...
center_space=$[COLUMNS-${#date}-${#PWD}-${#git_tst}-${#face_tst}]
printf "\n$top_line\n$(face) $BLD$BLU$PWD$DEF%${center_space}s$git_clr$DIM$date $(face)\n$bot_line\n\$ "



Как заставить глаза покраснеть? Долго сидеть за компом, не спать На stackoverflow.com мне попался интересный вопрос, автор спрашивает: «как менять цвет в приглашении командной строки на красный если последняя команда завершилась неудачно?» Это и натолкнуло меня на идею красных глаз. Добавим в функцию info запоминание статуса завершения последней команды:

info () {
    error=$?
    ...
}

И изменим функцию face таким образом, чтобы она проверяла переменную $error и в зависимости от её значения красила глаза в красный или жёлтый:

face () {
    [[ $error -gt 0 ]] && ecolor=$RED || ecolor=$YLW
    printf "$ecolor${eyes[$[RANDOM%en]]}$YLW$mouth$ecolor${eyes[$[RANDOM%en]]}$DEF"
}



Ну вот у меня глаза покраснели, но можно добавить еще кое-что. Добавим проверку переменной $debian_chroot:

[[ $debian_chroot ]] && chrt="($debian_chroot)" || chrt=
...
name_length="{ $HOSTNAME$chrt }"
...
printf -v top_line "%${top_line_left}s{_S_$DEF$BLD$HOSTNAME$chrt${DEF}_S_$GRN}%${top_line_right}s"

И изменим текст в заголовке окна терминала:

PS1='$(info)'; case "$TERM" in xterm*|rxvt*) PS1="\[\e]0;$(face 1) \w\a\]$PS1";; esac

В заголовок будет выводится морда и текущий каталог, но придется немного доработать функцию face чтобы она рисовала морду без цветовых кодов, они в заголовке бутут отображаться просто текстом, передадим функции face какой-нибудь параметр (например «1»), внутри функции добавим проверку, если задан 1-й аргумент, выдать морду без разукрашивания:

face () {
    [[ $error -gt 0 ]] && ecolor=$RED || ecolor=$YLW
    [[ $1 ]] && printf "${eyes[$[RANDOM%en]]}$mouth${eyes[$[RANDOM%en]]}" \
             || printf "$ecolor${eyes[$[RANDOM%en]]}$YLW$mouth$ecolor${eyes[$[RANDOM%en]]}$DEF"
}



Итоговый скрипт:

RED='\e[31m' # красный
GRN='\e[32m' # зелёный
YLW='\e[33m' # жёлтый
BLU='\e[34m' # синий
MGN='\e[35m' # пурпурный
DEF='\e[0m'  # вернуть значения по умолчанию
BLD='\e[1m'  # жирно
DIM='\e[2m'  # тускло
eyes=(O o ∘ ◦ ⍤ ⍥) en=${#eyes[@]} mouth='_'
face () {
    [[ $error -gt 0 ]] && ecolor=$RED || ecolor=$YLW
    [[ $1 ]] && printf "${eyes[$[RANDOM%en]]}$mouth${eyes[$[RANDOM%en]]}" \
             || printf "$ecolor${eyes[$[RANDOM%en]]}$YLW$mouth$ecolor${eyes[$[RANDOM%en]]}$DEF"
}
info () { error=$? git_tst= git_clr=
    [[ -d .git ]] && {
        git_tst=($(git -c color.ui=never status -sb))
        git_tst="GIT ${git_tst[*]} " # простой для теста
        git_clr=($(git -c color.ui=always status -sb))
        git_clr="GIT ${git_clr[*]} " # цветной для вывода
    }
    [[ $debian_chroot ]] && chrt="($debian_chroot)" || chrt=
    name_length="{ $HOSTNAME$chrt }"
    name_length=${#name_length}
    face_tst='O_o  o_O'
    top_line_left=$[(COLUMNS-name_length)/2]
    top_line_right=$[COLUMNS-(top_line_left+name_length)]
    printf -v top_line "%${top_line_left}s{_S_$DEF$BLD$HOSTNAME$chrt${DEF}_S_$GRN}%${top_line_right}s"
    printf -v bot_line "%${COLUMNS}s"
    printf -v date  "%(%a %d %b %T)T"
    top_line=${top_line// /-}
    top_line=$GRN${top_line//_S_/ }$DEF
    bot_line=$GRN${bot_line// /-}$DEF
    center_space=$[COLUMNS-${#date}-${#PWD}-${#git_tst}-${#face_tst}]
    ((center_space<0)) && center_space=1
    printf "\n$top_line\n$(face) $BLD$BLU$PWD$DEF%${center_space}s$git_clr$DIM$date $(face)\n$bot_line\n\$ "
}
PS1='$(info)'; case "$TERM" in xterm*|rxvt*) PS1="\[\e]0;$(face 1) \w\a\]$PS1";; esac

На этом все, спасибо за внимание!) Подписывайтесь, ставьте лайки, вот это вот все, проект есть в гитхабе info-bar Творите, выдумывайте, пробуйте!)

проверка внимательности
Вы заметили в какой момент символ "#" в строке приглашения поменялся на "$"?)

(*) (*)
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 32

    +3

    А вы не пробовали starship? Кажется, он реализует всё описанное здесь + много больше.

      0
      Не пробовал, красные глаза умеет?
        0

        Есть модуль character, который может менять символ/его цвет в зависимости от результата выполнения последней команды.
        И есть модуль custom-commands, который позволяет выводить в prompt результат выполнения произвольных команд.

          +2
          Прикольно, я и не настаиваю что моя поделка лучше всех. Это какбэ туториал про то, как сделать самому а не как установить программу)
      +6

      Как-то поставил себе красную стрелку в начало строки ввода. Через пару суток она стала трёхмерной.


      Вообще, за идею что я не до конца эффективно использую верхнюю строчку — спасибо. Хотя, имхо — 4 строки на промпт, это слегка перебор, вот две — в самый раз, ну только если у вас не 20к дисплей размером со стену.


      И алыверды — давненько такой годноты не попадалось на хабре, всё какая-то реклама и нытьё, хватало бы кармы — влепил бы вам жирный плюс. Но, т.к. я в основном тут ною какой хабр не торт, примите просто спасибо.

        +1
        Спасибо за спасибо)
        0
        Хабр торт.
          0
          Настраиваешь разные цвета в PS1, несколько строк… А потом запускаешь mc, тыц-тыц, Ctrl+O, а там непотребство какое-то :(
          Наверно, это всё потому, что я нуб, всю жизнь проведший на винде с Total Commander)
          (GUIшные ФМ не канают, т.к. в 99% случаев в Линукс хожу по ssh)
            0

            Неповерю что explorer.exe неумеет в sshfs, что из коробки пока не умеет — допускаю, но уже 100% кто-то сделал адекватный плагин.

            0
            За что Вы так со словом «length»? Ни разу оно не написано правильно.
            А так статью прочитал с удовольствием, спасибо.
              0
              Очепятка, поправлю, спасибо.
              +3

              Очень похоже на powerlevel, только без правильного шрифта, без схлопывания отработанных промптов (чтобы не засорять экран) — и на античном баше.


               ~/Proyectos/Elixir/ex_pain    master !3 ?1


              Авторы powerlevel еще озаботились красивым пошаговым конфигуратором, который избавляет от необходимости ковыряться в исходниках.

                +2
                лучшая вещь на свете это выход powerlevel10k, 9 был медленным и с плагинами очень даже тормозил, сейчас летает. Всем советую)
                  0

                  Спасибо, а у них и правда интереснее настройки.

                  +1

                  O_o
                  3683 коммита на расскрашивание командной строки :)

                  0
                  Результат прикольный, теперь скучный терминал пестрит красными жёлтыми мордочками :D
                    +2
                    Для борьбы с длинными путями использую PROMPT_DIRTRIM в ~/.bashrc

                    image

                    А сам PROMPT формируется так
                    export PROMPT_DIRTRIM=5
                    export PS1='\n\[\033[0;90m\][\t \D{%Z}, \D{%d.%m.%Y}] on \[\033[0;31m\]\u\[\033[1;33m\]@\[\033[0;36m\]\H:\n\[\033[0;32m\][\w]\[\033[1;33m\]\$ \[\033[00m\]'
                    
                      +1
                      Обратите внимания что git_clr и git_tst сначала записываются массивом

                              git_clr=(GIT $(git -c color.ui=always status -sb))
                              git_clr="${git_clr[*]} "
                          printf ... ${git_clr[*]} ...

                      Мне кажется Вы сделали сложно, а в третьей из процитированных строк допустили неточность. Там требуется только первый элемент массива, но вы используете весь массив. Вот пример:


                      $ echo $BASH_VERSION
                      4.4.12(3)-release
                      
                      $ x=( $( printf '%s\n' qwe asd ) ) ; x="${x[*]}" ; printf '%s\n' "${x[*]}"
                      qwe asd asd

                      Можно было бы чуть проще сделать, например:


                              git_clr=( $( git -c color.ui=always status -sb ) )
                          printf ... GIT ${git_clr[*]} ...

                      И для тестовой строки лучше явно выключить расцветку:


                      git_tst=(GIT $(git -c color.ui=never status -sb))
                        +1
                        Вы определенно прошли проверку на внимательность) git_clr/tst сначала формируются массивом а потом преобразуются в переменные, вот переменная git_clr и должна быть на выходе а я по ошибке туда массив засунул(в итоговом скрипте было правильно). Переделал так:
                        git_tst=($(git -c color.ui=never  status -sb)) git_tst="GIT ${git_tst[*]} "
                        git_clr=($(git -c color.ui=always status -sb)) git_clr="GIT ${git_clr[*]} "

                        Обе переменные формирую одинаково чтобы было красиво) Про color.ui=never хорошее замечание, добавил. Спасибо!
                          +1

                          Я сам часто пишу шелловские скрипты разной степени замысловатости и считаю, что при правильном подходе можно писать весьма сложные и надежные скрипты.


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


                          $ x=( 123 456 ) ; echo $x ; echo ${x[0]}
                          123
                          123
                            +1
                            Да, и если сказать:
                            x=789

                            Получим:
                            $ echo $x ; echo ${x[0]} ; echo ${x[1]}
                            789
                            789
                            456

                        –1
                        Все кто работал в консоли какого-нибудь Линукса наверно отмечали удобную особенность отображать текущую папку, имя пользователя, имя сервера и что-то еще в зависимости от дистра в строке приглашения. Мне тоже это всегда нравилось.

                        Я работал и отмечал, что это удобно, но мне это никогда не нравилось. В тру юниксе должен писаться не полный путь, а только название директории. Для полного пути есть команда pwd. Наличие полного пути превращает линукс из почти-юникса в почти-дос. Вот единственное вменяемое приглашение: export PROMP='[%n@%m %1~]%# '. (И да, разумеется это не bash. В идеале bash вообще не должен быть установлен, но в Linux этого довольно сложно достичь.)

                          0
                          Наличие полного пути превращает линукс из почти-юникса в почти-дос.

                          В почти VAX/VMS. Там не было понятия «дерево каталогов» и «на уровень вверх».

                            +1
                            В тру юниксе должен писаться не полный путь, а только название директории. Для полного пути есть команда pwd.

                            Вот у меня параллельно с полдесятка задач над одним компонентом на трёх его ветках. Соответственно есть >=5 рабочих копий в разных каталогах. И у всех последний компонент пути, очевидно, одинаковый — имя компонента. Как мне отличить, где именно я сейчас работаю? Или тру-юниксоид не может работать в таком стиле, ибо грех великий? ;)

                              0

                              Ну, работа — это работа. Если это настоящая работа за деньги, то там в принципе нет места сопливым рассуждениям о "тру-юниксе" и тому подобному.


                              А отличить можно много как. Если консоли в иксах, можно сделать, чтобы полный путь в заголовке окна писался. Если консоли — виртуальные неиксовые, можно их логично распределить по номерам. Правда, номер тоже можно забыть… но это уже другая проблема. Если если используется что-то вроде tmux, то там тоже заголовки есть.

                                0
                                > А отличить можно много как.

                                Всё так. Но мне проще не надеяться на это и просить полный путь в PS1.

                                И да, он у меня двухстрочный уже лет 10 как.
                                  0

                                  Понятия «настоящая работа» и «за деньги» — вообще никак не связаны. У волонтеров в каком-нибудь Красном кресте — работа такая настоящая, что настоящее не придумаешь.


                                  А формочки клепать, или джейсончик перекладывать — хоть и за стотыщмильёнов — настоящей работой даже язык не поворачивается назвать.


                                  netch


                                  тру-юниксоид не может работать в таком стиле, ибо грех великий?

                                  Конечно, не может. Тру сделает git worktree и будет в PS1 смотреть не на имя папки, а на имя ветки. Как дети, чесслово :)

                                    0
                                    На имя ветки я могу смотреть и с liquidprompt, и он удобнее потому, что показывает в том числе отношение к upstream, количество изменений, режим (обычная работа, merge, rebase, am и т.п.) и ещё толпу параметров. И для Git это достаточно часто полезно. И другие альтернативы для того же есть.

                                    Но это не отменяет необходимость полного пути.

                                    > Как дети, чесслово :)

                                    Это про вашу оценку собеседника :)
                                      0
                                      Мнэээ… я запутался в дереве ответов, простите.
                                      Но всё равно worktree это ой не всегда лучший выход.
                                        0
                                        worktree это ой не всегда лучший выход

                                        worktree— примерно никогда не лучший выход. Я не всерьез же.

                                  0
                                  Например, использовать «правильный» и удобный терминал

                                  image

                                  P.S. MacOS/iTerm2
                                    0
                                    Чтобы что-то появилось в заголовке терминала, туда надо его передать. Шеллы это делают через задание последовательностей в том же PS1. И что, если опять же с точки зрения того комментатора я не должен подавать в него полный путь, а должен только последнюю компоненту? Что вы этим сказать-то хотели?

                                    > P.S. MacOS/iTerm2

                                    Да сейчас больше проблема, как отключить терминалам эту функциональность — а то её абьюзить стали.

                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                              Самое читаемое