Разноцветные терминалы



В этой публикации я расскажу о некоторых трюках, которые украсят будни любого системного администратора Linux (и не только). Все они связаны с переменной PS1 оболочки bash. Переменная PS1 определяет, как будет выглядеть приглашение для ввода новых команд. И каждый пользователь может переопределять её как пожелает, например, в файле ~/.bashrc (который выполняется при запуске bash и используется для в том числе для конфигурации).

Для начала рассмотрим простой вариант, мой любимый формат командной строки.

PS1='\t j\j \u@\h:\w\n\$ '

Результат будет вот такой:
17:42:46 j0 olleg@petrel:~
$

Это обычное использование переменной PS1, но если бы я не начал с этого — рассказ был бы неполным. Обычно в переменной PS1 с помощью специальных последовательностей символов определяют формат приглашения для ввода команд. Подробный список этих последовательностей можно почитать в документации к bash, в данном примере:

  • \t — вывод «текущего времени», на самом деле это получается время завершения выполнения предыдущей команды, удобно когда перед глазами.
  • j\j — выводит символ j и после него количество запущенных job, т.е. процессов в фоне. Это тоже удобно иметь перед глазами чтобы случаем про них не забыть когда соберешься закрыть терминал.
  • \u@\h — имя пользователя и название сервера. Если работаете с несколькими серверами через удаленные терминалы — чтобы не путаться.
  • \w — после двоеточия — рабочая директория.
  • \n — поскольку строка получилась хоть и информативной (что-то вроде статус бара), но длинной, то приглашаем вводить команды с новой строки, а эта верхняя строка будет наглядно отделять от результата работы предыдущей команды.
  • \$ — на новой строке будет выводится символ либо $ для обычного пользователя либо # для root'а и отделив его пробелом можно приглашать вводить новую команду.

Казалось бы, чего еще желать… Но дальше будет интереснее. Дело в том, что с помощью специальных управляющих символов можно задавать цвет выводимого текста, цвет курсора и даже переопределять title bar у таких графических терминалов, как Gnome2. И, на мой взгляд, довольно удобно когда цветом отделяются терминалы запущенные на различных серверах. Для меня каждый сервер ассоциируется с каким-то цветом и в этот цвет мы будем красить командную строку и курсор на каждом сервере.

У меня .bashrc разделен на два файла, в самом .bashrc содержится общий код для всех серверов, а в .bash_local — уникальные для этого сервера настройки командной строки. .bash_local я буду вставлять в .bashrc специальной директивой. Начнем с .bash_local. В контексте данной статьи там у меня будут две строчки, которые определяют цвет этого сервера:


# .bash_local
# change cursor and prompt color
cursor_color='#0087FF'
prompt_color='33'

Просто заношу коды цвета в переменные. Но, как вы заметили, что способ задания цвета для курсора и для текста командной строки — разный. Почему-то так исторический получилось. Чтобы понять, какой цвет каким кодом кодируется, есть подходящая картинка.

image

Посредине — обозначение цвета для цвета курсора, снизу — обозначение цвета для текста. Как вы можете увидеть, что я для текста и курсора использую цвет морской волны. Т.к. название сервера petrel («буревестник»), то он ассоциируется у меня с этим цветом.

Теперь .bashrc, тоже показываю его не полностью, а только то что имеет отношение к теме:


# .bashrc
# local stuff
[[ -f ~/.bash_local ]] && . ~/.bash_local

Тут я вставляю код из .bash_local в общий файл. Таким образом определяться ранее описанные переменные с цветом сервера.


# set to red
root_cursor_color='#FF0000'
root_prompt_color='196'

Еще две переменные определяю с чисто красным цветом, он будет использоваться для маркировки терминалов привелигированного пользователя (root'а).


#my favorite PS1
case "$TERM" in
xterm*|rxvt*)
   PS1='\[\e[38;5;'$prompt_color'm\]\t j\j \u@\h:\w\n'
   [[ $UID == 0 ]] && { prompt_color=$root_prompt_color;cursor_color=$root_cursor_color; }
   PS1="$PS1"'\[\e[m\e]12;'$cursor_color'\a\e[38;5;'$prompt_color'm\]\$ \[\e[m\]'
   ;;
*)
   PS1='\t j\j \u@\h:\w\n\$ '
   ;;
esac

Тут проверяется какой используется терминал. Для любого неизвестного или неподдерживающего цвета будет использоваться приглашение без цвета (PS1='\t j\j \u@\h:\w\n\$ ') так, как я это описал в начале статьи. Но если имя терминала начинается на xterm или rxvt, например, так себя позиционирует терминал Gnome, начинаем кудесить с цветом. Первая строчка — задаем цвет текста — цвет сервера и выводим первую строку приглашения ввода команд. Она всегда будет окрашена в цвет сервера. Вторая строчка — проверяем, работаем ли мы под непривелигированным или привелигированным пользователем (root'ом). Если root — то переопределяем цвета на красный. Третья строчка — формируем вторую строчку приглашения и определяем цвет курсора в терминале. Т.е. там у нас получится либо $ и через пробел курсор, оба покрашенные в цвет сервера, если пользователь обычный. Либо красный # и через пробел красный курсор, если это root.


# If this is an xterm set the title to user@host:dir
case "$TERM" in
xterm*|rxvt*)
    PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1"
    ;;
*)
    ;;
esac

А это, если честно, один в один скопированно из первоначального .bashrc от Дебиана. Знаю, что этот код видоизменяет title bar у окна, размещает там информацию об пользователе, сервере и домашней директории. Но поскольку этот код придумал не я, комментировать его не буду.

В результате у нас должно получится так, как на картинке в самом начале публикации.
Поделиться публикацией

Похожие публикации

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

    –1
    Все равно fish намного приятнее bash и в плане подсветки и автодополнения и написания скриптов
      0
      Дело не в bash. Эти ESC последовательности команд зашитые в PS1 читаются терминалом и терминал, а не bash их расцвечивает. Так что будут работать с любым шелом. И чем еще удобен так то что при этом на сам терминал не завязано. Будешь в одном терминале переходить по ssh с сервера на сервер и цвета у приглашения будут меняться соответственно. Ну и цвет курсора, когда открыты конфиги в редакторе, например, беглово взгляда на курсор достаточно чтобы не забывать — на каком сервере.
        –7
        Ничего подобного не увидел. У меня тоже PS1 в bash разукрашен. перехожу с bash по ssh на другой сервер, а там стандартное приветствие. По моему, вы тут какую-то ерунду написали. У меня fish даже в tty работает нормально, без графического терминала. PS1 — bash переменная, и регулирует ее вывод.
          +3
          Разумеется конфиги (.bashrc или что там у вас) должны быть на каждом сервере, каждый в свой цвет. Сами по себе цвета не появятся. :) Аналог PS1 у fish устроен по другому, там это функция fish_prompt. Но суть та же самая, если будите там выдывать ESC последовательности управляющие цветом — они будут интерпретироваться терминалом. Шелл только принимает символы и выдает их, в том числе ESC последовательности и прочие управляющие символы, а цвета воспроизводит всегда терминал.

          Bash тоже в линукс консоле работает. Но цвета линукс консоле цвета по беднее и их по другому надо будет задавать, цвет курсора там задать не получилось и набор цветов там будет упрощенный.
            –4
            Ага, теперь понятнее, я думал без конфига на сервере тоже работать будет
      0
      в плане автодополнения путей вот еще удобная штука github.com/rupa/z
      +1
      А так же больше цветов, еще больше!
      git config --global color.ui true
      alias ls='ls --color=auto'
      alias dmesg='dmesg --color=always'
      alias grep='grep --color=always'
      alias fgrep='fgrep --color=always'
      alias egrep='egrep --color=always'
      alias gcc='gcc -fdiagnostics-color=always'
      alias pacman='pacman --color=always'
      
        +2
        А, еще можно man раскрасить:

        man() {
            env LESS_TERMCAP_mb=$'\E[01;31m' \
            LESS_TERMCAP_md=$'\E[01;38;5;74m' \
            LESS_TERMCAP_me=$'\E[0m' \
            LESS_TERMCAP_se=$'\E[0m' \
            LESS_TERMCAP_so=$'\E[38;5;246m' \
            LESS_TERMCAP_ue=$'\E[0m' \
            LESS_TERMCAP_us=$'\E[04;38;5;146m' \
            man "$@"
        }
        
        +2
        Двустрочное приглашение — очень удобная штука! Можно впихнуть много информации и при этом останется место для длинной команды без переноса. Я пару лет использую трёхстрочное приглашение:
        1 имя пользователя, hostname, la, текущее время
        2 текущий каталог
        3 для ввода команды

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

          +3
          Ну да, у меня практический тоже самое, только без load, одинаково мыслим. :) Но все это, в принципе, в одну строчку помещается, так что двухстрочного для меня достаточно. Да и удобно еще тем что наглядно отделяет вывод одной команды от другой.
            0
            Во всем этом деле с датой расстраивает то, что время пишется на момент приглашения. Получить бы время выполнения))
              0
              Ну так если работаешь непрерывно, то по разнице времени на «момент приглашения» (т.е. на момент завершения задачи) с предыдущим промптом можно приблизительно оценить время выполнения. А для более точной оценки есть команда time:
              14:09:35 j0 olleg@petrel:~
              $ time sleep 10
              
              real	0m10.009s
              user	0m0.000s
              sys	0m0.000s
              14:09:51 j0 olleg@petrel:~
              $ 
              
                0
                Как-то я не подумал про следующее приглашение. Действительно, задачи как правило выполняются меньше секунды, редко больше 5 секунд. Этой оценки вполне хватит.
                Пойду подпиливать .bashrc
                0
                Можно и время выполнения получить:
                jakemccrary.com/blog/2015/05/03/put-the-last-commands-run-time-in-your-bash-prompt
                  0
                  Извините, я всех запутал с терминологией, меня интересовало именно время начала исполнения. Но длительность выполнения тоже стоит взять на вооружения.
                0
                Спасибо за идею, попробую. И splarv тоже спасибо за статью!
                  +2
                  То, что у меня, делается вот так:

                  PS1="\[\e[0;36m\]┌──\[\e[0m\][ \[\e[0;33m\]\u\[\e[0m\]$txtgrn@$txtcyn\h$txtrst ] [$txtylw\$(load_average)$txtrst] [ $txtcyn\t$txtrst ]\n$txtcyn├── $txtgrn\w$txtcyn\n$txtcyn└>$txtrst "
                  


                  Цвета заданы так же в отдельном файлике переменными. $txtrst отменяет все модификации цвета (txtrst='\[\e[0m\]'). $(load_average) — функция. Парсит вывод uptime выдирая LA.
                    0
                    cat /proc/loadavg | awk '{print $2}' — LA5
                      0
                      Оно там у меня ещё красненьким подкрашивает, если la больше 2. Поэтому функцию нагородил.
                  0
                  А по-моему, двухстрочное приглашение — монстроузное приглашение, а вся выведенная информация избыточна и выводится либо средствами оконного менеджера (часы), либо не шибко полезна, чтобы постоянно висеть перед глазами (loadavg).

                  Для себя я подобрал оптимальную конфигурацию: hostname, текущий каталог с сокращением до половины ширины терминала и информация о свободном месте на текущей файловой системе.
                    +1
                    а если оконного менеджера нет?
                    у меня выводится свободная память, время, текущий каталог и количество задач в бекграунде
                    но это, конечно, вкусовщина
                      0
                      >а если оконного менеджера нет?

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

                      Но если уж вот прям никак: мультиплексор нельзя, оконного менеджера нет, кругом только чОрная консоль, то проще набрать три буковки команды top или четыре команды date, чем постоянно держать перед глазами мусор, который, к слову, не будет автоматически обновляться.

                      Помнится, раньше было очень модно городить conky и выводить туда такую «полезную» информацию, как версия ядра, хостнейм своего локалхоста, несколько штук часов, календарик… Это вот все напоминает.
                      +1
                      Даже такой минимум как хостнейм и текущий каталог (а это по умолчанию) порой занимают порядочную ширину и для набора команд остается недостаточно места. Да и неудобно это. Гораздо приятнее набирать команды с абсолютно пустой строки. :) Это и было основной причиной двухстрочного приглашения. Плюс оно удобно отделяет вывод предыдущей команды. А вывод времени в основном используется не для того чтобы знать текущее время, а для того чтобы понимать насколько какая программа «подвисла», сколько выполняется длительная компиляция и т.д. Я понимаю что time удобнее. Но подобный интерес зачастую возникает не заранее, а после того как программа неожиданно долго работает. :) Так что у меня почти тоже что и у вас, разве что вывода свободного места нет, т.к. набрать df всегда просто.
                        –1
                        >Даже такой минимум как хостнейм и текущий каталог (а это по умолчанию) порой занимают порядочную ширину

                        Потому, я обычно делаю вот так:
                        local DIRWIDTH
                        (( DIRWIDTH = ${COLUMNS} / 3 ))
                        local CUR="[ %$DIRWIDTH<..<%~%<< ]"
                        PROMPT="$DARK_GREEN$CUR$DEFAULT ->"
                        


                        Путь срезается до трети ширины терминала. Вот двустороннее приглашение я люблю: справа у меня и выводится
                        df -hP .|sed -n '2p'|awk -F' ' '{print $4}' 

                        Вообще, люблю подобные обсуждения: всегда можно подсмотреть что-то полезное или поделиться своим.
                    +1
                    а еще можно эмодзи добавить
                    image

                    удобно, чтобы отличать один проект от другого
                      0
                      А вот это интересно. Про вывод картинок в терминале я не слышал. :)
                        0
                        Потому что это не картинки, а unicode символы. Другое дело, нужно, что бы баш их умел правильно «рисовать».
                          +1
                          Не баш, а терминал. Должна быть или специальная поддержка со стороны терминала, чтобы он вставлял туда картинки. Либо их должен поддерживать системный фонт. Оказалось что в dejavu есть много смайликов и даже смайлики-котята. Можно использовать в терминале. :)
                      0
                      Да.
                      А во времена UNIXов, когда еще на было BASH и LINUX украшали экран ESC-последовательносями
                        +3
                        больше десяти лет назад в хакере нашёл себе хороший, годный вариант оформления командной строки
                        get_freemem ()
                        {
                        echo -n `free | grep Mem | awk '{print $4}'`
                        }

                        export PS1="=(\w)=(\t)=("'`get_freemem`'")=(\j:\$?)=\n=>"

                        https://habrastorage.org/files/5b1/973/fb9/5b1973fb906a459492e70cab0c397b97.png
                          +3
                          Проиллюстрирую, раз вы с отрицательной кармой:

                          image
                          0
                          $(ERR="$?"; if [[ "$ERR" != "0" ]]; then printf "\033[01;37m(%.*s)\033[00m " $ERR $ERR; fi) $PS1
                          добавляет код ошибки, белым в скобках, в начале PS1
                            0
                            Я сторонник минимализма в отношении подсказки, но у меня сохраняется расширенная история. Вот фрагмент .bashrc:

                            HISTTIMEFORMAT='%t%F %T%t'
                            echo $PROMPT_COMMAND|grep -q bash_eternal_history || export PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND; } history -a; "'echo -e $?\\t$USER\\t$HOSTNAME\\t$PWD\\t"$(history 1)" >> ~/.bash_eternal_history'
                              0
                              Добавлю свои 5 копеек. Добавляет текущую ветку для репозитория. Для git и hg репозиториев.
                              image

                              hg_branch() {
                                  BRANCH=`hg branch 2> /dev/null | awk '{print "(hg:"$1")"}'`
                                  if [[ $BRANCH == *release* ]]
                                  then
                                      BRANCH="\e[31m$BRANCH"
                                  fi
                                  if [[ $BRANCH == *default* ]]
                                  then
                                      BRANCH="\e[31m$BRANCH"
                                  fi
                                  echo -e $BRANCH
                              }
                              

                              в PS1:
                              $(__git_ps1 "(git:%s)")$(hg_branch)
                              


                              ps: условия в hg_branch() раскрашивают блок в красный цвет, чтобы видно было, что не надо ничего коммитить в default и release
                                0
                                256 цветов в терминале — прошлый век. Большинство современных терминалов имеют поддержку 16 миллионов цветов (True Color). Вот здесь я собрал список терминалов, которые их поддерживают, и не поддерживают. Для большинства тех, которые не поддерживают — есть сторонние патчи или форки. Ведется работа по интеграции их в мэйнстрим. Есть даже Pull Request в tmux github.com/tmux/tmux/pull/112
                                  +1
                                  Вроде оно логично, но нафига вам оттенки цвета «бедра испуганной нимфы»? По большому счету тут будут использоваться только чистые сатурированные тона, как правило. Красный, синий, желтый, зеленый… Человеческий мозг не в состоянии удержать даже 256 категорий одновременно. Что там может быть? Продакшен, тестовый нестабильный, тестовый предпродакшен и т.п. Ну 10 категорий. Дальше смысл теряется, на мой взгляд.
                                    0
                                    Вот это интересно, если оно так. У вас значится что Gnome Terminal тоже поддерживает. Напишите пример с echo который бы позволил задать цвет текста в 24хбитном RGB. Ваш пример по ссылке пробовал — не работает.
                                      0
                                      В gist-е упомянуто, что это применимо только к Gnome Terminal скомиленным Gtk+3, и libvte старше 0.36 версии.
                                    +2
                                    Раз пошла такая пьянка, моя строка выглядит так:



                                    Доступно в моём zsh-config репе: prompt.zsh

                                    Выводит:

                                    1) текущий каталог, если слишком длинный, то последние 5 каталогов,
                                    2) текущая git-ветка, если я в репозитории (иначе не выводит этот компонент), вопросительный знак если есть незакоммиченные изменения, восклицательный — если есть изменения в стейджинге,
                                    3) текущий терминал (удобно различать в каком терминале находишься и какой шелл убивать если что),
                                    4) если команда выполнялась дольше 5 секунд, выводит сколько времени она выполнялась,
                                    5) если команда неудачно завершилась, то выводит код ошибки.

                                    Итого всё очень минималистично, выводит только ту информацию, которая важна по контексту, что позволяет держать строку очень короткой, но при этом информативной.

                                    И да, для bash придётся переписать, но тут главное идея.
                                      +3
                                      Вообще, мне больше нравится разыернутый синтаксис. Ненавижу перлообразные нагромождения, для меня они нечитаемы. Пример из Арчевики:
                                      set_prompt () {
                                          Last_Command=$? # Must come first!
                                          Blue='\[\e[01;34m\]'
                                          White='\[\e[01;37m\]'
                                          Red='\[\e[01;31m\]'
                                          Green='\[\e[01;32m\]'
                                          Reset='\[\e[00m\]'
                                          FancyX='\342\234\227'
                                          Checkmark='\342\234\223'
                                      
                                          # Add a bright white exit status for the last command
                                          PS1="$White\$? "
                                          # If it was successful, print a green check mark. Otherwise, print
                                          # a red X.
                                          if [[ $Last_Command == 0 ]]; then
                                              PS1+="$Green$Checkmark "
                                          else
                                              PS1+="$Red$FancyX "
                                          fi
                                          # If root, just print the host in red. Otherwise, print the current user
                                          # and host in green.
                                          if [[ $EUID == 0 ]]; then
                                              PS1+="$Red\\h "
                                          else
                                              PS1+="$Green\\u@\\h "
                                          fi
                                          # Print the working directory and prompt marker in blue, and reset
                                          # the text color to the default.
                                          PS1+="$Blue\\w \\\$$Reset "
                                      }
                                      PROMPT_COMMAND='set_prompt'
                                      
                                        0
                                        Да я тоже за развернутый синтаксис. :) Но создать переменные для всех 255 цветов… (причем и для обозначений цвета для букв и для курсора, т.е. 510). Там даже уникальные имена цветам замучаешься придумывать, а когда придумаешь — забудешь какой конкретный цвет они обозначают. :)
                                          0
                                          А нафига вам 256 цветов? Я вообще смысла не вижу больше 10-15 использовать. Как писал уже выше, вы категории не запомните. Ну не может человеческий мозг разбивать множество на 100-200 категорий. Сводим к 3-5 обычно.
                                            +1
                                            Вы правы, я использую ровно 5. Но выбирал то я тх из 255. :) Я за свободу выбора.

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

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