====== Tweaking Terminal on OS X ====== There are several things that annoy me on default Terminal behavior, so these are the things that I am going to solve in this article: - Change and colorize Terminal prompt - Make Terminal history remember only last instance of the same command - Tweak history, so it will search for the command part already entered into prompt - Add aliases for common commands All these changes can be achieved by editing ''~/.bash_profile'' file. ===== Customizing Terminal prompt ===== *Note:* I will be using ''nano'' for all the editing of ''~/.bash_profile'' file. The default terminal prompt looks like this: * '' 9acbf218:Downloads FurloSK$'' That really doesn't help at all to understand what's going on, right? Both the contents and formatting of command prompt are controlled by ''PS1'' variable, which is by default set to: * '' \h:\W \u$'' ==== Escape sequences for special content ===== The escape sequences that can be used as a part of command prompt are those: \a # an ASCII bell character (07) \d # the date in "Weekday Month Date" format (e.g., "Tue May 26") \D{format} # the format is passed to strftime(3) and the result # is inserted into the prompt string an empty format # results in a locale-specific time representation. # The braces are required. \e # an ASCII escape character (033) \h # the hostname up to the first '.' \H # the hostname \j # the number of jobs currently managed by the shell \l # the basename of the shell's terminal device name \n # newline \r # carriage return \s # the name of the shell, the basename of $0 (the portion following # the final slash) \t # the current time in 24-hour HH:MM:SS format \T # the current time in 12-hour HH:MM:SS format \@ # the current time in 12-hour am/pm format \A # the current time in 24-hour HH:MM format \u # the username of the current user \v # the version of bash (e.g., 2.00) \V # the release of bash, version + patch level (e.g., 2.00.0) \w # the current working directory, with $HOME abbreviated with a tilde \W # the basename of the current working directory, with $HOME # abbreviated with a tilde \! # the history number of this command \# # the command number of this command \$ # if the effective UID is 0, a #, otherwise a $ \nnn # the character corresponding to the octal number nnn \\ # a backslash \[ # begin a sequence of non-printing characters, which could be used # to embed a terminal control sequence into the prompt \] # end a sequence of non-printing characters Both ''\h'' and ''\u'' are basically unusable for me, so I normally strip them from ''PS1''. However, there are some more things worth mentioning. ==== Accessing previous command ==== What I usually want is to see actual (or previous) command number, so I can recall it easily. This is possible by several ways. * Escape sequence ''\!'' is interesting, because it allows recalling the command with particular history number by typing ''!'' and pressing ''Enter''. Therefore, adding it to ''PS1'' like this: ''(\!) \w'' might be very wise idea, because I always see which command number I am entering right now. * Similarly, typing ''!!'' will execute the very last command. However, it cannot be used in command prompt (i.e., in ''PS1''), only directly as an entered command. * In case I do not want to execute the previous command, just to print it, it is possible to use '':p'' suffix (i.e., ''!:p'' or ''!!:p''), which does exactly that. * Another ways is to use ''fc -ln -1'', which will display previously entered command. * It is also possible to use it in ''PS1'' by entering it as ''$(fc -ln -1)''. **However**, this will **not** display the very last command, but the command before the last one. * Last possible way is to use ''history 1'', which will display both last command and its number, e.g. '' 421 ls -l''. To access only command part, it can be ''cut'' like this: ''$(history 1 | cut -d " " -f 5-)''. The winner for showing the last command in ''PS1'' is ''history 1'', because only this one can really show you the last entered command (and not the previous-to-last) in ''PS1'' (which also means that, when entered directly as a shell command, it will mirror exactly the same command you just entered). **Return code** of the last command can be accessed with ''$?''. ==== Escape sequences for colors ==== Escape sequence for changing colors in basic 16-colors mode is * ''\[\033[m\]''. Color codes are different for foreground and for background: 0 # Reset all formatting # Regular Colors 30 # Black 31 # Red 32 # Green 33 # Yellow 34 # Blue 35 # Purple 36 # Cyan 37 # "Dark" White (i.e. Light Gray) # High Intensity Colors 90 # "Light" Black (i.e. Dark Gray) 91 # Light Red 92 # Light Green 93 # Light Yellow 94 # Light Blue 95 # Light Purple 96 # Light Cyan 97 # White # Background 40 # Black 41 # Red 42 # Green 43 # Yellow 44 # Blue 45 # Purple 46 # Cyan 47 # "Dark" White (i.e. Light Gray) # High Intensity Backgrounds 100 # "Light" Black (i.e. Dark Gray) 101 # Light Red 102 # Light Green 103 # Light Yellow 104 # Light Blue 105 # Light Purple 106 # Light Cyan 107 # White Setting foreground and background colors at the same time is possible by concatenating them with '';''. Changing text variant is possible by prepending special number before color: 1; # bold text 4; # underlined text So writing red underlined text on green background would look like * ''\[\033[4;31;42m\]Red underlined text on green background\[\033[0m\]''. ==== Escape sequences for positioning ==== One last thing I was interested in was the possibility to delimit commands by whole line painted with some background color, so I can easily see where each new command begins during scrolling through some lengthy outputs. This is possible by ANSI/VT100 control codes, ''ESC[K "Clear to end of line"'' in particular. These, however, can not be normally entered. You need to enter them in "direct input" mode. In ''nano'', this is called "Verbatim input" and it can be entered by pressing ''Esc+V''. However, there is much better way to achieve the same goal without going deep into direct input modes. The system utility used for this is ''tput'', and our particular command can be entered with ''$(tput el)''. This will move to the end of line, "filling" the whole remainder of it with previously set background color. ==== My final prompt ==== After all these adjustments, my ''PS1'' variable looks like this: export PS1='\n\[\033[48;5;22m\]\[\033[38;5;220m\]\ # green background, yellow text $([ $? == 0 ] && echo "✅ " || echo "⚠️ :$?\[\033[38;5;9m\]") \ # show last return code $(history 1 | cut -d " " -f 5-)\ # print previous command $(tput el)\[\033[0m\]\n\ # fill the remainder of line \[\033[0;32m\]\w \[\033[0m\]' # show working directory My prompt color is set to green and displays only working directory. Before each prompt, I am displaying an empty line filled with dark-green color. This line contains previous command with its return code (colored to red if it was non-zero) and appropriate (text) "icon". ===== Ignoring duplicates in history ===== Controlling terminal history behavior is done via setting ''HISTCONTROL'' variable. According to manual pages, ''HISTCONTROL'' is * ''A colon-separated list of values controlling how commands are saved on the history list. If the list of values includes ‘ignorespace’, lines which begin with a space character are not saved in the history list. A value of ‘ignoredups’ causes lines which match the previous history entry to not be saved. A value of ‘ignoreboth’ is shorthand for ‘ignorespace’ and ‘ignoredups’. A value of ‘erasedups’ causes all previous lines matching the current line to be removed from the history list before that line is saved. Any value not in the above list is ignored. If HISTCONTROL is unset, or does not include a valid value, all lines read by the shell parser are saved on the history list, subject to the value of HISTIGNORE. The second and subsequent lines of a multi-line compound command are not tested, and are added to the history regardless of the value of HISTCONTROL.'' So all what is really needed is * ''export HISTCONTROL="ignoredups:erasedups"'' However, what I really *wanted* was that my history would be "shared" across terminals. This is somehow harder to achieve, but definitely possible: export HISTCONTROL="ignoredups:erasedups" shopt -s histappend export PROMPT_COMMAND="history -n; history -w; history -c; history -r; $PROMPT_COMMAND" ===== Searching in history by entered prefix ===== I wanted to use my arrows to search history with respect to command prefix already entered. This is possible by binding ''UP'' and ''DOWN'' keys to specific commands: bind '"":history-search-backward' bind '"":history-search-forward' ''[A'' and ''[B'' in here are UP and DOWN keys, entered in previously-mentioned verbatim input. So in ''nano'', you have to press ''Esc+V'' and then UP- or DOWN-key, respectively. ===== My final ~./bash_profile ===== # customize prompt export PS1='\n\[\033[48;5;22m\]\[\033[38;5;220m\]\ $([ $? == 0 ] && echo "✅ " || echo "⚠️ :$?\[\033[38;5;9m\]") \ $(history 1 | cut -d " " -f 5-)\ $(tput el)\[\033[0m\]\n\ \[\033[0;32m\]\w \[\033[0m\]' # ignore history duplicates and make searching more user-friendly export HISTSIZE=10000 export HISTFILESIZE=1000000 # default 500 export HISTCONTROL="ignoredups:erasedups" shopt -s histappend export PROMPT_COMMAND="history -n; history -w; history -c; history -r; $PROMPT_COMMAND" bind '"":history-search-backward' bind '"":history-search-forward' # up 'n' folders alias ..='cd ..' alias ...='cd ../..' alias ....='cd ../../..' alias .....='cd ../../../..' # grep with color alias grep='grep --color=auto' # refresh shell alias reload='source ~/.bash_profile' # better ls alias ls='ls -AGFh' ===== References ===== * [[http://blog.taylormcgann.com/tag/prompt-color/|Customize Your Shell & Command Prompt]] * [[https://www.macworld.com/article/1146015/os-x/termhistory.html|Better command history browsing in Terminal ]] * [[https://unix.stackexchange.com/questions/18212/bash-history-ignoredups-and-erasedups-setting-conflict-with-common-history|Bash history: “ignoredups” and “erasedups” setting conflict with common history across sessions ]] * [[https://stackoverflow.com/questions/16715103/bash-prompt-with-last-exit-code|Bash Prompt with Last Exit Code]] * [[https://misc.flogisoft.com/bash/tip_colors_and_formatting|Bash tips: Colors and formatting (ANSI/VT100 Control sequences)]] * [[https://stackoverflow.com/questions/19915208/extending-terminal-colors-to-the-end-of-line|Extending terminal colors to the end of line]] * [[http://wiki.bash-hackers.org/scripting/terminalcodes|Terminal codes (ANSI/VT100) introduction ]] * [[http://www.isthe.com/chongo/tech/comp/ansi_escapes.html|ANSI Escape Sequences]] ~~socialite~~ {{tag>os-x fresh-install howto terminal}} ===== Comments ===== ~~DISQUS~~