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

gusbekevfjtlba2ljb8yttbtrsa.png

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

fifttysaxmarznnpc3zdaat-4vk.png

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

mqv5m25ocpjxr9wbmzmekop8pba.png

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

iw7dcpzfvd1hbhnkmuqb7_zceha.png

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

PS1='$PWD\n# '


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

a-i5lyqdra1chdjqve4xuibti-0.png

Командная строка полностью свободна, но имя папки сливается с содержимым если выполнить команду 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# '


xcx4r6iyifeqovl2woyxqyo4jkq.png

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

egyhvdsln3omfyddgkhgp9g6js4.png

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

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


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

info () {
    name_lenght="{ $HOSTNAME }"
    name_lenght=${#name_lenght}
    top_line_left=$[(COLUMNS-name_lenght)/2]
    top_line_right=$[COLUMNS-(top_line_left+name_lenght)]
    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)'


rouptzovfl1esxa7q_mlamwxvkq.png

Я заключил имя хоста в фигурные скобки с пробелами, но вместо пробелов вокруг $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_lenght="{ $HOSTNAME }"
    name_lenght=${#name_lenght}
    top_line_left=$[(COLUMNS-name_lenght)/2]
    top_line_right=$[COLUMNS-(top_line_left+name_lenght)]
    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)'


1gzxihs5l0ml42zy1-pqmsadttw.png

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

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# "


kfpsnhfmr9zgnbjhi5a65ikktu0.png

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

    git_tst= git_clr=
    [[ -d .git ]] && {
        git_tst=(GIT $(git status -sb))
        git_tst="${git_tst[*]} " # простой для теста
        git_clr=(GIT $(git -c color.ui=always status -sb))
        git_clr="${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\$ "


-ud1ssfinaszv-ldrcg3srlfbg0.png

Обратите внимания что 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\$ "


kq38v0jhhvff5hwd1_asx9o-26y.png

Как заставить глаза покраснеть? Долго сидеть за компом, не спать На 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"
}


ucorse5avrby6b1gx6ff8jjrbuc.png

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

[[ $debian_chroot ]] && chrt="($debian_chroot)" || chrt=
...
name_lenght="{ $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"
}


a-k6bwswfljnnwatjpiyvaybl7q.png

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

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 $(git status -sb))
        git_tst="${git_tst[*]} " # простой для теста
        git_clr=(GIT $(git -c color.ui=always status -sb))
        git_clr="${git_clr[*]} " # цветной для вывода
    }
    [[ $debian_chroot ]] && chrt="($debian_chroot)" || chrt=
    name_lenght="{ $HOSTNAME$chrt }"
    name_lenght=${#name_lenght}
    face_tst='O_o  o_O'
    top_line_left=$[(COLUMNS-name_lenght)/2]
    top_line_right=$[COLUMNS-(top_line_left+name_lenght)]
    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 Творите, выдумывайте, пробуйте!)

проверка внимательности

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

© Habrahabr.ru