Nástroje používateľa

Nástoje správy stránok


blog:odborny:2018-02-24-tweaking_terminal_on_os_x

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:

  1. Change and colorize Terminal prompt
  2. Make Terminal history remember only last instance of the same command
  3. Tweak history, so it will search for the command part already entered into prompt
  4. 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 !<history_number> 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 previoustolast) 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 16colors mode is

  • \[\033[<COLOR_CODE>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 darkgreen color. This line contains previous command with its return code (colored to red if it was nonzero) 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 colonseparated 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 multiline 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 previouslymentioned verbatim input. So in nano, you have to press Esc+V and then UP or DOWNkey, 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

Comments

blog/odborny/2018-02-24-tweaking_terminal_on_os_x.txt · Posledná úprava: 2019/10/08 23:03 od Róbert Toth