В этой публикации я расскажу о некоторых трюках, которые украсят будни любого системного администратора 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'
Просто заношу коды цвета в переменные. Но, как вы заметили, что способ задания цвета для курсора и для текста командной строки — разный. Почему-то так исторический получилось. Чтобы понять, какой цвет каким кодом кодируется, есть подходящая картинка.
Посредине — обозначение цвета для цвета курсора, снизу — обозначение цвета для текста. Как вы можете увидеть, что я для текста и курсора использую цвет морской волны. Т.к. название сервера 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)
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'
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 "$@" }
uscr
02.11.2015 14:00+2Двустрочное приглашение — очень удобная штука! Можно впихнуть много информации и при этом останется место для длинной команды без переноса. Я пару лет использую трёхстрочное приглашение:
1 имя пользователя, hostname, la, текущее время
2 текущий каталог
3 для ввода команды
Сначала кажется слишком громоздко, но быстро привык и чувствую дискомфорт при использовании однострочных приглашений.
splarv
02.11.2015 14:04+3Ну да, у меня практический тоже самое, только без load, одинаково мыслим. :) Но все это, в принципе, в одну строчку помещается, так что двухстрочного для меня достаточно. Да и удобно еще тем что наглядно отделяет вывод одной команды от другой.
Sild
02.11.2015 14:07Во всем этом деле с датой расстраивает то, что время пишется на момент приглашения. Получить бы время выполнения))
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:~ $
Sild
02.11.2015 14:20Как-то я не подумал про следующее приглашение. Действительно, задачи как правило выполняются меньше секунды, редко больше 5 секунд. Этой оценки вполне хватит.
Пойду подпиливать .bashrc
SabMakc
02.11.2015 15:10Можно и время выполнения получить:
jakemccrary.com/blog/2015/05/03/put-the-last-commands-run-time-in-your-bash-promptSild
02.11.2015 15:12Извините, я всех запутал с терминологией, меня интересовало именно время начала исполнения. Но длительность выполнения тоже стоит взять на вооружения.
xenohunter
02.11.2015 14:41Спасибо за идею, попробую. И splarv тоже спасибо за статью!
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.
d7s2di
03.11.2015 10:58А по-моему, двухстрочное приглашение — монстроузное приглашение, а вся выведенная информация избыточна и выводится либо средствами оконного менеджера (часы), либо не шибко полезна, чтобы постоянно висеть перед глазами (loadavg).
Для себя я подобрал оптимальную конфигурацию: hostname, текущий каталог с сокращением до половины ширины терминала и информация о свободном месте на текущей файловой системе.Terranz
03.11.2015 11:01+1а если оконного менеджера нет?
у меня выводится свободная память, время, текущий каталог и количество задач в бекграунде
но это, конечно, вкусовщинаd7s2di
03.11.2015 11:13>а если оконного менеджера нет?
Когда я подключаюсь по ssh, всегда использую tmux или screen (на совсем уж древних системах). Отложенные и возобновленные сессии — это очень удобно, плюс мультиплексор имеет свой персональный статусбар, куда можно вывести часики и имя хоста.
Но если уж вот прям никак: мультиплексор нельзя, оконного менеджера нет, кругом только чОрная консоль, то проще набрать три буковки команды top или четыре команды date, чем постоянно держать перед глазами мусор, который, к слову, не будет автоматически обновляться.
Помнится, раньше было очень модно городить conky и выводить туда такую «полезную» информацию, как версия ядра, хостнейм своего локалхоста, несколько штук часов, календарик… Это вот все напоминает.
splarv
03.11.2015 11:42+1Даже такой минимум как хостнейм и текущий каталог (а это по умолчанию) порой занимают порядочную ширину и для набора команд остается недостаточно места. Да и неудобно это. Гораздо приятнее набирать команды с абсолютно пустой строки. :) Это и было основной причиной двухстрочного приглашения. Плюс оно удобно отделяет вывод предыдущей команды. А вывод времени в основном используется не для того чтобы знать текущее время, а для того чтобы понимать насколько какая программа «подвисла», сколько выполняется длительная компиляция и т.д. Я понимаю что time удобнее. Но подобный интерес зачастую возникает не заранее, а после того как программа неожиданно долго работает. :) Так что у меня почти тоже что и у вас, разве что вывода свободного места нет, т.к. набрать df всегда просто.
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}'
Вообще, люблю подобные обсуждения: всегда можно подсмотреть что-то полезное или поделиться своим.
chill84
02.11.2015 15:22+1а еще можно эмодзи добавитьsplarv
02.11.2015 15:32А вот это интересно. Про вывод картинок в терминале я не слышал. :)
Ascott
02.11.2015 20:19Потому что это не картинки, а unicode символы. Другое дело, нужно, что бы баш их умел правильно «рисовать».
splarv
03.11.2015 11:52+1Не баш, а терминал. Должна быть или специальная поддержка со стороны терминала, чтобы он вставлял туда картинки. Либо их должен поддерживать системный фонт. Оказалось что в dejavu есть много смайликов и даже смайлики-котята. Можно использовать в терминале. :)
sav13
02.11.2015 15:50Да.
А во времена UNIXов, когда еще на было BASH и LINUX украшали экран ESC-последовательносями
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
Gendalph
02.11.2015 18:42$(ERR="$?"; if [[ "$ERR" != "0" ]]; then printf "\033[01;37m(%.*s)\033[00m " $ERR $ERR; fi) $PS1
добавляет код ошибки, белым в скобках, в начале PS1
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'
felicast
02.11.2015 20:39Добавлю свои 5 копеек. Добавляет текущую ветку для репозитория. Для git и hg репозиториев.
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
xvilka
03.11.2015 10:24256 цветов в терминале — прошлый век. Большинство современных терминалов имеют поддержку 16 миллионов цветов (True Color). Вот здесь я собрал список терминалов, которые их поддерживают, и не поддерживают. Для большинства тех, которые не поддерживают — есть сторонние патчи или форки. Ведется работа по интеграции их в мэйнстрим. Есть даже Pull Request в tmux github.com/tmux/tmux/pull/112
Meklon
03.11.2015 10:47+1Вроде оно логично, но нафига вам оттенки цвета «бедра испуганной нимфы»? По большому счету тут будут использоваться только чистые сатурированные тона, как правило. Красный, синий, желтый, зеленый… Человеческий мозг не в состоянии удержать даже 256 категорий одновременно. Что там может быть? Продакшен, тестовый нестабильный, тестовый предпродакшен и т.п. Ну 10 категорий. Дальше смысл теряется, на мой взгляд.
splarv
03.11.2015 11:49Вот это интересно, если оно так. У вас значится что Gnome Terminal тоже поддерживает. Напишите пример с echo который бы позволил задать цвет текста в 24хбитном RGB. Ваш пример по ссылке пробовал — не работает.
xvilka
03.11.2015 13:01В gist-е упомянуто, что это применимо только к Gnome Terminal скомиленным Gtk+3, и libvte старше 0.36 версии.
kstep
03.11.2015 11:54+2Раз пошла такая пьянка, моя строка выглядит так:
Доступно в моём zsh-config репе: prompt.zsh
Выводит:
1) текущий каталог, если слишком длинный, то последние 5 каталогов,
2) текущая git-ветка, если я в репозитории (иначе не выводит этот компонент), вопросительный знак если есть незакоммиченные изменения, восклицательный — если есть изменения в стейджинге,
3) текущий терминал (удобно различать в каком терминале находишься и какой шелл убивать если что),
4) если команда выполнялась дольше 5 секунд, выводит сколько времени она выполнялась,
5) если команда неудачно завершилась, то выводит код ошибки.
Итого всё очень минималистично, выводит только ту информацию, которая важна по контексту, что позволяет держать строку очень короткой, но при этом информативной.
И да, для bash придётся переписать, но тут главное идея.
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'
splarv
03.11.2015 14:36Да я тоже за развернутый синтаксис. :) Но создать переменные для всех 255 цветов… (причем и для обозначений цвета для букв и для курсора, т.е. 510). Там даже уникальные имена цветам замучаешься придумывать, а когда придумаешь — забудешь какой конкретный цвет они обозначают. :)
Meklon
03.11.2015 15:47А нафига вам 256 цветов? Я вообще смысла не вижу больше 10-15 использовать. Как писал уже выше, вы категории не запомните. Ну не может человеческий мозг разбивать множество на 100-200 категорий. Сводим к 3-5 обычно.
splarv
03.11.2015 16:48+1Вы правы, я использую ровно 5. Но выбирал то я тх из 255. :) Я за свободу выбора.
Crandel
Все равно fish намного приятнее bash и в плане подсветки и автодополнения и написания скриптов
splarv
Дело не в bash. Эти ESC последовательности команд зашитые в PS1 читаются терминалом и терминал, а не bash их расцвечивает. Так что будут работать с любым шелом. И чем еще удобен так то что при этом на сам терминал не завязано. Будешь в одном терминале переходить по ssh с сервера на сервер и цвета у приглашения будут меняться соответственно. Ну и цвет курсора, когда открыты конфиги в редакторе, например, беглово взгляда на курсор достаточно чтобы не забывать — на каком сервере.
Crandel
Ничего подобного не увидел. У меня тоже PS1 в bash разукрашен. перехожу с bash по ssh на другой сервер, а там стандартное приветствие. По моему, вы тут какую-то ерунду написали. У меня fish даже в tty работает нормально, без графического терминала. PS1 — bash переменная, и регулирует ее вывод.
splarv
Разумеется конфиги (.bashrc или что там у вас) должны быть на каждом сервере, каждый в свой цвет. Сами по себе цвета не появятся. :) Аналог PS1 у fish устроен по другому, там это функция fish_prompt. Но суть та же самая, если будите там выдывать ESC последовательности управляющие цветом — они будут интерпретироваться терминалом. Шелл только принимает символы и выдает их, в том числе ESC последовательности и прочие управляющие символы, а цвета воспроизводит всегда терминал.
Bash тоже в линукс консоле работает. Но цвета линукс консоле цвета по беднее и их по другому надо будет задавать, цвет курсора там задать не получилось и набор цветов там будет упрощенный.
Crandel
Ага, теперь понятнее, я думал без конфига на сервере тоже работать будет