-
Notifications
You must be signed in to change notification settings - Fork 4
/
bashrc
451 lines (381 loc) · 16.1 KB
/
bashrc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
# ~/.bashrc for Arch Linux and Ubuntu
# This file is sourced by login shells and interactive non-login shells. On at least
# Debian and Arch Linux, Bash is compiled with the `-DSYS_BASHRC="/etc/bash.bashrc"`
# option, which makes it source /etc/bash.bashrc first for interactive non-login shells.
# See <https://wiki.archlinux.org/title/Bash#Configuration_files> and
# <https://unix.stackexchange.com/q/187369>.
# FIXME: ShellCheck warnings.
# If not running interactively, don't do anything.
[[ $- != *i* ]] && return
# The GNU Privacy Guard Manual and gpg-agent(1) suggest setting and exporting GPG_TTY like
# this [1][2]. It tells gpg-agent the file name of the terminal connected to stdin, which
# is used by `pinentry-curses` (though [2] also recommends having GPG_TTY set for the
# GUI-based `pinentry` programs). See tty(1).
# [1]: https://www.gnupg.org/documentation/manuals/gnupg/Invoking-GPG_002dAGENT.html
# [2]: https://www.gnupg.org/documentation/manuals/gnupg/Common-Problems.html
export GPG_TTY=$(tty)
# Clear PROMPT_COMMAND. It's set from /etc/bash.bashrc on Arch Linux.
unset PROMPT_COMMAND
# Set up fzf's Bash keybinds if fzf is installed (see https://stackoverflow.com/a/677212).
if hash fzf 2>/dev/null; then
# Arch's fzf package installs this file. Source it if it exists.
if [[ -f /usr/share/fzf/key-bindings.bash ]]; then
source /usr/share/fzf/key-bindings.bash
# Ubuntu's fzf package installs this file.
elif [[ -f /usr/share/doc/fzf/examples/key-bindings.bash ]]; then
source /usr/share/doc/fzf/examples/key-bindings.bash
# Maybe fzf was installed directly from the Git repository. If this involved running
# the bundled install script, there should be a `~/.fzf.bash` file.
elif [[ -f ~/.fzf.bash ]]; then
source ~/.fzf.bash
fi
fi
# Source z.sh (https://github.com/rupa/z); z(1) jumps to frecent directories and it's
# faster than autojump (I haven't tried fasd, which is similar too). This appends a
# command to PROMPT_COMMAND that's terminated with a semicolon.
if [[ -f /usr/share/z/z.sh ]]; then
. /usr/share/z/z.sh
unalias z # I use j for z.
fi
# See <https://mywiki.wooledge.org/glob> and <https://stackoverflow.com/q/17191622>.
shopt -s extglob
# Perform history expansion on the current line and insert a space, with space. See
# bash(1). For example, typing !!<Space> will replace the !! with the previous command.
bind Space:magic-space
# Disable XON/XOFF flow control. See stty(1), <https://unix.stackexchange.com/q/12107>,
# and <https://en.wikipedia.org/wiki/Software_flow_control>.
stty -ixon
# Send ASCII DEL (0x7F) for the backspace key instead of ASCII BS (0x08). This is some
# sort of de-facto standard and xterm doesn't follow it by default on my system. Update:
# I'm fixing it from Xresources instead of here now.
# stty erase
# Horrible hacks are happening here.
if [[ "$TERM" == screen* ]]; then
# Fix Control+H in xterms that are immediately (on startup) attached to a screen
# session started in detached mode. Control+H erases all characters before the cursor
# instead of just one without this.
stty kill
# Fix Control+H in those xterms again. GNU Readline's key bindings are different in
# them for some reason: in particular, Control+H (C-h in Emacs-style notation) is bound
# to `unix-line-discard` instead of `backward-delete-char`. That means it kills all
# text from the cursor to the beginning of the line instead of acting like backspace.
# I swear it was working without this before... maybe some upgrade broke stuff again.
# See `help bind`, readline(3) and `info readline`.
bind '\C-h: backward-delete-char'
fi
alias ts='date -u +%Y%m%dT%H%M%SZ'
# Aliases and functions for interactive use
alias ..='cd ..'
alias ...='cd ../..'
alias ....='cd ../../..'
alias .....='cd ../../../..'
alias ......='cd ../../../../..'
alias -- -='cd - >/dev/null'
# See <https://wiki.archlinux.org/title/Sudo>.
alias sudo='sudo '
alias ls=elles
alias l=elles
alias la='elles -A'
alias ll='elles -l'
alias trl='elles -trl'
alias thrl='\ls -thrl'
# List subdirectories (https://stackoverflow.com/a/171938).
alias lsd='elles -d */'
alias spinach='>/dev/null trans -speak en:'
# See <https://stackoverflow.com/q/592620>.
if hash dragon-drag-and-drop 2>/dev/null; then
alias dragon=dragon-drag-and-drop
complete -F _minimal dragon
fi
# Create a new directory and change into it. Conceptually like `mkdir -p "$1" && cd "$1"`
# but handles many subtle edge cases. Taken from <https://unix.stackexchange.com/a/9124>.
mkcd() {
case "$1" in
*/..|*/../) cd -- "$1";; # Doesn't make sense unless the directory already exists.
/*/../*) (cd "${1%/../*}/.." && mkdir -p "./${1##*/../}") && cd -- "$1";;
/*) mkdir -p "$1" && cd "$1";;
*/../*) (cd "./${1%/../*}/.." && mkdir -p "./${1##*/../}") && cd "./$1";;
../*) (cd .. && mkdir -p "${1#.}") && cd "$1";;
*) mkdir -p "./$1" && cd "./$1";;
esac
}
# Jump into the most "frecent" directory matching all arguments. Normally like z(1), but
# uses fzf to select a directory when invoked without arguments. Adapted from
# <https://github.com/junegunn/fzf/wiki/Examples#z>.
j() {
if [[ $# == 0 ]]; then
d=$(_z |& sed "s/^[0-9.]* *//; s:$HOME:~:" | fzf --tac --no-sort) || return
# shellcheck disable=SC2164
cd "${d/#\~/$HOME}"
return
fi
case $1 in
d)
# shellcheck disable=SC2164
cd ~/dotfiles;;
p)
# shellcheck disable=SC2164
cd ~/projects;;
w)
# shellcheck disable=SC2164
cd ~/wiki;;
r)
# shellcheck disable=SC2164
cd ~/projects/resume;;
m)
# shellcheck disable=SC2164
cd ~/projects/meribold.org;;
*)
_z "$*";;
esac
}
alias jd='cd ~/dotfiles'
alias jp='cd ~/projects'
alias jw='cd ~/wiki'
alias jr='cd ~/projects/resume'
alias jm='cd ~/projects/meribold.org'
xrn() {
if (( $? )); then
notify-send - 'Something went wrong…'
else
sleep 1
exit
fi
}
if [[ $TERM == screen* ]]; then
k() {
screen -X screen && screen -X stuff "j $*"$'\n'
}
fi
# `filch` is a portmanteau of `file` and `which`.
filch() {
path=$(which "$1") && file "$path"
}
# `catch` is a portmanteau of `cat` and `which`.
catch() {
path=$(which "$1") && [[ $(file -L "$path") == *text* ]] && bat "$path"
}
instagit() {
cd ~/tmp || { mkdir ~/tmp && cd ~/tmp; } || return
local d
d=$(qualified-noun) && mkdir "$d" || { d=$(qualified-noun) && mkdir "$d"; } || return
cd "$d" && git init --quiet
}
# Select a line from oneliners.bash with fzf and paste the selection to stdin. This code
# is copied and adapted from the `__fzf_history__` function in
# /usr/share/fzf/key-bindings.bash (as of fzf 0.16.8-1 from Arch's community repo,
# <https://www.archlinux.org/packages/community/x86_64/fzf/>).
__fzf_oneliners__() {
if [[ -e ~/dotfiles/misc/oneliners.$HOSTNAME.bash ]]; then
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS" $(__fzfcmd) \
< <(cat ~/dotfiles/misc/oneliners{,".$HOSTNAME"}.bash)
else
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS" $(__fzfcmd) \
< ~/dotfiles/misc/oneliners.bash
fi
}
# Invoke __fzf_oneliners__ with Control+I (xterm is configured to send ÿ when Control+I is
# pressed, so it can be distinguished from Tab). I just copied and adapted the code used
# in /usr/share/fzf/key-bindings.bash to set up the Control+R readline key binding.
bind '"ÿ": " \C-e\C-u$(__fzf_oneliners__)\e\C-e\e^\er"'
# Aliases for humans.
alias df='df -h'
alias du='du -h'
alias free='free -h'
alias info='info --vi-keys'
alias v='$VISUAL'
# `alert` alias for use with long-running commands. For example, `sleep 10; alert` will
# send a desktop notification after `sleep 10` is done (if a notification daemon like
# "Notify OSD" or "dunst" is running). Adapted from Ubuntu's .bashrc. See
# notify-send(1).
alias alert='notify-send -i "$([[ $? == 0 ]] && echo terminal || echo error)" '\
'"$(history | tail -1 | sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"'
# Get the default gateway's (router's) IP address. See <https://serverfault.com/q/31170>.
gatewayip() {
ip route | awk '/^def/{print $3}'
}
alias gateip=gatewayip
# The name is short for "persist history".
ph() {
history -a && git -C ~/bash-history commit -m 'Update history' current
}
# Shortcut for `git.` Runs `git status -s` when called with no arguments, otherwise acts
# like `git`. From https://github.com/thoughtbot/dotfiles/blob/master/zsh/functions/g.
g() {
if [[ $# -gt 0 ]]; then
git "$@"
else
git status -s
fi
}
alias c='git commit'
alias ci='git commit'
alias ff='git merge --ff-only'
alias ga='git add'
alias gd='git diff'
alias gg='git graph'
alias gl='git log'
alias gp='git push'
alias gap='git add -p'
alias gau='git add -u'
alias gds='git diff --staged'
alias gcm='git commit -m'
alias gin='git init --quiet'
alias game='git commit --amend --quiet'
alias gish='git show'
alias gist='git status'
alias tag='tig --all'
alias nx='git-annex'
alias pg='pass git'
alias pgl='pass git log'
alias pgs='pass git show'
alias feh='feh -B black -.'
alias msa='mpc search any'
d() { dict "$@" |& less -FX; }
# Stuff concerning Bash's command history
shopt -s histverify
# Append new history lines from each Bash session to $HISTFILE when the shell exits,
# rather than overwriting the file with the history list. When histappend is unset,
# running several instances of Bash at the same time will result in the new history lines
# of all but the last instance that exits to be lost. Having histappend unset also may
# pose an additional risk of losing (part of) the history if something goes wrong while
# overwriting the file. See bash(1) and <https://unix.stackexchange.com/a/6509>.
shopt -s histappend
HISTTIMEFORMAT='%F %T '
# Bash sets HISTFILESIZE to this value as well and "if HISTFILESIZE is [...] a numeric
# value less than zero, the history file is not truncated" (bash(1)).
HISTSIZE=-1
# Set HISTFILESIZE explicitly anyway.
HISTFILESIZE=-1
# Don't save lines starting with a space character in the history.
HISTCONTROL=ignorespace
HISTIGNORE='k:k *:dnks:ph'
HISTFILE=~/bash-history/current
# Bash appears to append history before running this, so there's no need for `history -a`.
# XXX: when running e.g. `exec htop` Bash does not seem to run this. The new history
# isn't committed. It is still appended, though.
commit_history() {
if ! err=$(git -C ~/bash-history commit -qm 'Update history' current 2>&1); then
# If the reason for `git commit` failing is just that there's nothing to commit,
# don't log an error.
git -C ~/bash-history diff --quiet current && return
\ts <<< "$err" >> ~/bash-history/error.txt
fi &
}
trap commit_history EXIT
# Using this function (`PS1='$(ps1)'`) results in a marginally nicer PS1 than `PS1='\$ '`.
# The default PS1 is '[\u@\h \W]\$ ' (TODO: is this set by Bash or by Arch?).
ps1() {
# Save the value of $? before doing anything else.
local exit_code=$?
# Using \[ and \] for surrounding non-printable characters doesn't work here. See
# <https://wiki.archlinux.org/title/Bash/Prompt_customization>. We have to use \001
# and \002 instead.
local gray=$'\001\e[38;5;246m\002'
local bold=$'\001\e[1m\002'
local reset=$'\001\e[m\002'
local stuff_in_brackets=
local prompt=
# Show the last command's exit code when it's nonzero.
if [[ $exit_code != 0 ]]; then
# When we add nothing else to `prompt` and `\n` is the very last thing, the newline
# gets swallowed for some reason. Apparently we can work around this by appending
# something inconsequential, for example `\001\002`.
prompt+=$'\001\e[31m\002'"$exit_code"$'\001\e[m\002\n\001\002'
fi
# Show the username and the hostname if the current user isn't my normal user or the
# hostname isn't "smial".
if [[ $EUID != 1000 || $HOSTNAME != smial ]]; then
stuff_in_brackets+='\u'$gray@$reset'\h'
fi
# Show the working directory if it isn't $HOME.
if [[ $PWD != "$HOME" ]]; then
stuff_in_brackets+="${stuff_in_brackets:+ }${PWD/$HOME/\~}"
fi
if [[ $stuff_in_brackets ]]; then
prompt+="$gray[$reset$stuff_in_brackets$gray]$reset "
fi
# This "@P" thing is called "parameter transformation". See bash(1) and
# <https://stackoverflow.com/a/54265211>. Without it, we would probably have to start
# a new (`whoami`, `logname`, or `id`) process.
printf %s "${prompt@P}"
}
# [1]: https://wiki.archlinux.org/title/Bash/Prompt_customization
# [2]: https://en.wikipedia.org/wiki/ANSI_escape_code
# [3]: https://commons.wikimedia.org/wiki/File:Xterm_color_chart.png
# [4]: https://www.calmar.ws/vim/256-xterm-24bit-rgb-color-chart.html
bold=$'\001\e[1m\002'
reset=$'\001\e[m\002'
PS1='$(ps1)'"$bold"'\$'"$reset "
PS2='\[\e[1m\]>\[\e[m\] '
unset bold reset
# Rules for setting the terminal title
case "$TERM" in
xterm*) # It's an xterm.
# Try to remove a semicolon from the end of PROMPT_COMMAND. Then append '; ' unless
# PROMPT_COMMAND is unset or empty. See <https://wiki.bash-hackers.org/syntax/pe>.
PROMPT_COMMAND="${PROMPT_COMMAND%;}"
PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND; }"
# We can safely append new commands to PROMPT_COMMAND now.
PROMPT_COMMAND+='printf "\033]0;%s@%s %s\007" "$USER" "${HOSTNAME%%.*}"'
PROMPT_COMMAND+=' "${PWD/#$HOME/\~}"'
# https://stackoverflow.com/q/7316107
# On Arch Linux and for most terminals, /etc/bash.bashrc sets PROMPT_COMMAND to:
# printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/~}"
# I'm unsetting PROMPT_COMMAND at the top of this file, but we could just keep it
# (TODO?)
# TODO: explain this better. Isn't this redundant?
show_command_in_title() # Show the currently running command in the terminal title.
{
case "$BASH_COMMAND" in
*033]0*)
# The command is trying to set the title bar as well; this is most likely
# the execution of $PROMPT_COMMAND. In any case nested escapes confuse the
# terminal, so don't output them.
;;
*)
printf "\033]0;%s@%s: %s\007" "$USER" "${HOSTNAME%%.*}" "$BASH_COMMAND"
;;
esac
}
;;
screen*) # It's a GNU Screen terminal. I get 'screen.linux' when running screen from
# inside a virtual console.
# We could do this (http://aperiodic.net/screen/title_examples) to get information
# about the running command in the title:
# PS1='\[\033k\033\\\][\u@\h \W]\$ '
# ...but this is nicer:
PROMPT_COMMAND="${PROMPT_COMMAND%;}"
PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND; }"
PROMPT_COMMAND+='printf "\033k%s@%s %s\033\\" "$USER" "${HOSTNAME%%.*}"'
PROMPT_COMMAND+=' "${PWD/#$HOME/\~}"'
show_command_in_title()
{
case "$BASH_COMMAND" in
*033]0*)
;;
*)
printf "\033k%s@%s: %s\033\\" "$USER" "${HOSTNAME%%.*}" "$BASH_COMMAND"
;;
esac
}
;;
*)
# It's something else.
;;
esac # More web links:
# https://mg.pov.lt/blog/bash-prompt.html
# https://tldp.org/HOWTO/Xterm-Title-3.html#ss3.1
# https://tldp.org/HOWTO/Xterm-Title-4.html#ss4.3
# When exactly does Bash receive the DEBUG signal and execute the command? "If a sigspec
# is DEBUG, the command arg is executed after every simple command, for command, case
# command, select command, every arithmetic for command, and before the first command
# executes in a shell function" (from bash(1)). I don't care if these commands weren't
# defined: redirect to /dev/null. This should be the last command in ~/.bashrc. If there
# are more, the terminal title will be changed for each one, increasing the startup time.
trap 'show_command_in_title 2>/dev/null' DEBUG
# [1]: https://linux.die.net/Bash-Beginners-Guide/sect_12_02.html
# [2]: https://stackoverflow.com/q/16636007
# [3]: https://stackoverflow.com/q/9268836
# [4]: https://stackoverflow.com/q/3338030
# [5]: https://wiki.archlinux.org/title/Color_Bash_Prompt#Different_colors_for_text_entry_and_console_output