В этой публикации я расскажу о некоторых трюках, которые украсят будни любого системного администратора 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. Crandel
    02.11.2015 12:52
    -1

    Все равно fish намного приятнее bash и в плане подсветки и автодополнения и написания скриптов


    1. splarv
      02.11.2015 14:01

      Дело не в bash. Эти ESC последовательности команд зашитые в PS1 читаются терминалом и терминал, а не bash их расцвечивает. Так что будут работать с любым шелом. И чем еще удобен так то что при этом на сам терминал не завязано. Будешь в одном терминале переходить по ssh с сервера на сервер и цвета у приглашения будут меняться соответственно. Ну и цвет курсора, когда открыты конфиги в редакторе, например, беглово взгляда на курсор достаточно чтобы не забывать — на каком сервере.


      1. Crandel
        02.11.2015 14:40
        -7

        Ничего подобного не увидел. У меня тоже PS1 в bash разукрашен. перехожу с bash по ssh на другой сервер, а там стандартное приветствие. По моему, вы тут какую-то ерунду написали. У меня fish даже в tty работает нормально, без графического терминала. PS1 — bash переменная, и регулирует ее вывод.


        1. splarv
          02.11.2015 14:59
          +3

          Разумеется конфиги (.bashrc или что там у вас) должны быть на каждом сервере, каждый в свой цвет. Сами по себе цвета не появятся. :) Аналог PS1 у fish устроен по другому, там это функция fish_prompt. Но суть та же самая, если будите там выдывать ESC последовательности управляющие цветом — они будут интерпретироваться терминалом. Шелл только принимает символы и выдает их, в том числе ESC последовательности и прочие управляющие символы, а цвета воспроизводит всегда терминал.

          Bash тоже в линукс консоле работает. Но цвета линукс консоле цвета по беднее и их по другому надо будет задавать, цвет курсора там задать не получилось и набор цветов там будет упрощенный.


          1. Crandel
            02.11.2015 15:13
            -4

            Ага, теперь понятнее, я думал без конфига на сервере тоже работать будет


  1. dark_ruby
    02.11.2015 13:04

    в плане автодополнения путей вот еще удобная штука github.com/rupa/z



  1. kvaps
    02.11.2015 13:19
    +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'
    


    1. kvaps
      02.11.2015 13:27
      +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 "$@"
      }
      


  1. uscr
    02.11.2015 14:00
    +2

    Двустрочное приглашение — очень удобная штука! Можно впихнуть много информации и при этом останется место для длинной команды без переноса. Я пару лет использую трёхстрочное приглашение:
    1 имя пользователя, hostname, la, текущее время
    2 текущий каталог
    3 для ввода команды

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


    1. splarv
      02.11.2015 14:04
      +3

      Ну да, у меня практический тоже самое, только без load, одинаково мыслим. :) Но все это, в принципе, в одну строчку помещается, так что двухстрочного для меня достаточно. Да и удобно еще тем что наглядно отделяет вывод одной команды от другой.


    1. Sild
      02.11.2015 14:07

      Во всем этом деле с датой расстраивает то, что время пишется на момент приглашения. Получить бы время выполнения))


      1. splarv
        02.11.2015 14:12

        Ну так если работаешь непрерывно, то по разнице времени на «момент приглашения» (т.е. на момент завершения задачи) с предыдущим промптом можно приблизительно оценить время выполнения. А для более точной оценки есть команда 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:~
        $ 
        


        1. Sild
          02.11.2015 14:20

          Как-то я не подумал про следующее приглашение. Действительно, задачи как правило выполняются меньше секунды, редко больше 5 секунд. Этой оценки вполне хватит.
          Пойду подпиливать .bashrc


      1. SabMakc
        02.11.2015 15:10

        Можно и время выполнения получить:
        jakemccrary.com/blog/2015/05/03/put-the-last-commands-run-time-in-your-bash-prompt


        1. Sild
          02.11.2015 15:12

          Извините, я всех запутал с терминологией, меня интересовало именно время начала исполнения. Но длительность выполнения тоже стоит взять на вооружения.


    1. xenohunter
      02.11.2015 14:41

      Спасибо за идею, попробую. И splarv тоже спасибо за статью!


      1. uscr
        02.11.2015 14:49
        +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$txtcynL>$txtrst "
        


        Цвета заданы так же в отдельном файлике переменными. $txtrst отменяет все модификации цвета (txtrst='\[\e[0m\]'). $(load_average) — функция. Парсит вывод uptime выдирая LA.


        1. Gendalph
          03.11.2015 17:51

          cat /proc/loadavg | awk '{print $2}' — LA5


          1. uscr
            03.11.2015 17:52

            Оно там у меня ещё красненьким подкрашивает, если la больше 2. Поэтому функцию нагородил.


    1. d7s2di
      03.11.2015 10:58

      А по-моему, двухстрочное приглашение — монстроузное приглашение, а вся выведенная информация избыточна и выводится либо средствами оконного менеджера (часы), либо не шибко полезна, чтобы постоянно висеть перед глазами (loadavg).

      Для себя я подобрал оптимальную конфигурацию: hostname, текущий каталог с сокращением до половины ширины терминала и информация о свободном месте на текущей файловой системе.


      1. Terranz
        03.11.2015 11:01
        +1

        а если оконного менеджера нет?
        у меня выводится свободная память, время, текущий каталог и количество задач в бекграунде
        но это, конечно, вкусовщина


        1. d7s2di
          03.11.2015 11:13

          >а если оконного менеджера нет?

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

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

          Помнится, раньше было очень модно городить conky и выводить туда такую «полезную» информацию, как версия ядра, хостнейм своего локалхоста, несколько штук часов, календарик… Это вот все напоминает.


      1. splarv
        03.11.2015 11:42
        +1

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


        1. d7s2di
          03.11.2015 12:23
          -1

          >Даже такой минимум как хостнейм и текущий каталог (а это по умолчанию) порой занимают порядочную ширину

          Потому, я обычно делаю вот так:

          local DIRWIDTH
          (( DIRWIDTH = ${COLUMNS} / 3 ))
          local CUR="[ %$DIRWIDTH<..<%~%<< ]"
          PROMPT="$DARK_GREEN$CUR$DEFAULT ->"
          


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

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


  1. chill84
    02.11.2015 15:22
    +1

    а еще можно эмодзи добавить
    image


    1. splarv
      02.11.2015 15:32

      А вот это интересно. Про вывод картинок в терминале я не слышал. :)


      1. Ascott
        02.11.2015 20:19

        Потому что это не картинки, а unicode символы. Другое дело, нужно, что бы баш их умел правильно «рисовать».


        1. splarv
          03.11.2015 11:52
          +1

          Не баш, а терминал. Должна быть или специальная поддержка со стороны терминала, чтобы он вставлял туда картинки. Либо их должен поддерживать системный фонт. Оказалось что в dejavu есть много смайликов и даже смайлики-котята. Можно использовать в терминале. :)


  1. sav13
    02.11.2015 15:50

    Да.
    А во времена UNIXов, когда еще на было BASH и LINUX украшали экран ESC-последовательносями


  1. Terranz
    02.11.2015 17:22
    +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


    1. Meklon
      02.11.2015 18:13
      +3

      Проиллюстрирую, раз вы с отрицательной кармой:

      image


  1. Gendalph
    02.11.2015 18:42

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


  1. kt97679
    02.11.2015 19:43

    Я сторонник минимализма в отношении подсказки, но у меня сохраняется расширенная история. Вот фрагмент .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'


  1. felicast
    02.11.2015 20:39

    Добавлю свои 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


  1. xvilka
    03.11.2015 10:24

    256 цветов в терминале — прошлый век. Большинство современных терминалов имеют поддержку 16 миллионов цветов (True Color). Вот здесь я собрал список терминалов, которые их поддерживают, и не поддерживают. Для большинства тех, которые не поддерживают — есть сторонние патчи или форки. Ведется работа по интеграции их в мэйнстрим. Есть даже Pull Request в tmux github.com/tmux/tmux/pull/112


    1. Meklon
      03.11.2015 10:47
      +1

      Вроде оно логично, но нафига вам оттенки цвета «бедра испуганной нимфы»? По большому счету тут будут использоваться только чистые сатурированные тона, как правило. Красный, синий, желтый, зеленый… Человеческий мозг не в состоянии удержать даже 256 категорий одновременно. Что там может быть? Продакшен, тестовый нестабильный, тестовый предпродакшен и т.п. Ну 10 категорий. Дальше смысл теряется, на мой взгляд.


    1. splarv
      03.11.2015 11:49

      Вот это интересно, если оно так. У вас значится что Gnome Terminal тоже поддерживает. Напишите пример с echo который бы позволил задать цвет текста в 24хбитном RGB. Ваш пример по ссылке пробовал — не работает.


      1. xvilka
        03.11.2015 13:01

        В gist-е упомянуто, что это применимо только к Gnome Terminal скомиленным Gtk+3, и libvte старше 0.36 версии.


  1. kstep
    03.11.2015 11:54
    +2

    Раз пошла такая пьянка, моя строка выглядит так:



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

    Выводит:

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

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

    И да, для bash придётся переписать, но тут главное идея.


  1. Meklon
    03.11.2015 11:55
    +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'
    


    1. splarv
      03.11.2015 14:36

      Да я тоже за развернутый синтаксис. :) Но создать переменные для всех 255 цветов… (причем и для обозначений цвета для букв и для курсора, т.е. 510). Там даже уникальные имена цветам замучаешься придумывать, а когда придумаешь — забудешь какой конкретный цвет они обозначают. :)


      1. Meklon
        03.11.2015 15:47

        А нафига вам 256 цветов? Я вообще смысла не вижу больше 10-15 использовать. Как писал уже выше, вы категории не запомните. Ну не может человеческий мозг разбивать множество на 100-200 категорий. Сводим к 3-5 обычно.


        1. splarv
          03.11.2015 16:48
          +1

          Вы правы, я использую ровно 5. Но выбирал то я тх из 255. :) Я за свободу выбора.