-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathbash
executable file
·785 lines (622 loc) · 19.5 KB
/
bash
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
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
#!/bin/bash
# This file is meant to be included from the main Bash configuration file
# Reload configuration
alias reload=". ~/conf/bash"
# Misc. aliases
alias -- -="cd - >/dev/null"
# autocd would also take care of '..', but the alias avoids the annoying
# "cd .." printout
alias ..="cd .."
alias ...="cd ../.."
alias ....="cd ../../.."
alias .....="cd ../../../.."
alias ll="ls -l"
# Lists most recently modified files last
alias new="ls -lrt"
# Preference variables
export EDITOR=vim
# Default but with system calls before commands
export MANSECT=2:n:l:8:3:1:3posix:3pm:3perl:5:4:9:6:7
# General shell options
if (( ${BASH_VERSINFO[0]} >= 4 )); then
shopt -s autocd
shopt -s globstar
fi
# Prints an error together with the name of the calling function. Second
# argument (defaults to 1) is how far to look up the call stack for the name.
_err() { echo "${FUNCNAME[${2-1}]}: $1" >&2; }
# Helper function for printing a usage string. Second argument like for _err().
_usage() { echo "usage: ${FUNCNAME[${2-1}]} $1" >&2; }
# Helper function. Checks if a program exists in the search path.
_prg_exists() { type -P -- "$1" >/dev/null; }
# Safe rm command with trash directory.
#
# usage: rm <file> [<file> ...]
#
# Removes directories as well as files. Removed files are stored in $trash_dir,
# which is emptied by running
#
# $ empty_trash
#
# Handles filenames and paths that contain spaces and/or start with '-'
# gracefully. Creates backups when deleting identically-named files so that no
# files are permanently removed until 'empty_trash' is run.
trash_dir=/tmp/trash
alias rm=safe_rm
# Helper function. Checks that trash_dir can be safely used.
_trash_dir_is_ok() {
if [[ -z $trash_dir ]]; then
_err "trash_dir is not set" 2
return 1
fi
if [[ $trash_dir != /* ]]; then
_err "trash_dir (set to '$trash_dir') is not an absolute path" 2
return 1
fi
if [[ -e $trash_dir && ! -d $trash_dir ]]; then
_err "trash_dir (set to '$trash_dir') is not a directory" 2
return 1
fi
return 0
}
safe_rm() {
# The usage and error helpers will say "safe_rm" instead of "rm", but
# that's probably ok since it reminds us that we're dealing with a function
local error
# Use the system's rm if we're running from an interactively source'd
# script or from another function
if [[ ${#FUNCNAME[@]} -gt 1 ]]; then
command rm "$@"
return $?
fi
if [[ $# -eq 0 ]]; then
_usage "<file> [<file> ...]"
return 1
fi
_trash_dir_is_ok || return 1
for f in "$@"; do
# -e is false for broken symbolic links; hence the -h test
if [[ ! -e $f && ! -h $f ]]; then
_err "'$f' does not exist"
return 1
fi
if [[ ! -w $(dirname -- "$f") ]]; then
_err "cannot remove '$f': No write permissions for containing directory"
return 1
fi
done
mkdir -p -- "$trash_dir"
if [[ ! -e $trash_dir ]]; then
_err "failed to create trash_dir (set to '$trash_dir')"
return 1
fi
mv -f --backup=numbered -- "$@" "$trash_dir"
error=$?
if [[ $error -ne 0 ]]; then
_err "failed to move files into trash_dir (set to '$trash_dir')"
return $error
fi
}
empty_trash() {
local error
if [[ $# -gt 0 ]]; then
_usage "(no arguments)"
return 1
fi
_trash_dir_is_ok || return 1
[[ -e $trash_dir ]] || return 0
if [[ ! -w $(dirname -- "$trash_dir") ]]; then
_err "cannot remove '$trash_dir': No write permissions for containing directory"
return 1
fi
command rm -rf -- "$trash_dir"
error=$?
if [[ $error -ne 0 ]]; then
_err "failed to remove '$trash_dir'"
return $error
fi
}
# Helper function. For files, cd's to the directory. For ordinary files, cd's
# to the containing directory.
_cd_to() {
if [[ -d $1 ]]; then
cd -- "$1"
else
cd -- "$(dirname -- "$1")"
fi
}
# Helper function. Expands a list of strings such as 'foo', 'bar', 'baz' into
# the glob pattern **/*foo*/**/*bar*/**/*baz*, meaning it would match e.g.
# afoo/d/bara/baz, and stores the matching file names in the array g_files. If
# no files match, g_files becomes empty. If no arguments are supplied, **/* is
# used as the pattern.
_super_glob() {
local pattern
if [[ $# -eq 0 ]]; then
pattern=**/*
else
pattern=**/*$1*
shift
for p in "$@"; do
pattern=$pattern/**/*$p*
done
fi
IFS=
shopt -s nocaseglob nullglob
g_files=($pattern)
shopt -u nocaseglob nullglob
unset IFS
}
# Helper function. Passes its second to last argument to _super_glob() and lets
# the user pick a file with a 'select' if many files match (otherwise, picks
# the single matching file). The first argument is the select prompt to use.
# The chosen file is returned in g_selected_file, which is unset if no files
# match.
_super_glob_select_file() {
local prompt=$1
shift
unset g_selected_file
_super_glob "$@"
[[ ${#g_files[@]} -eq 0 ]] && return
if [[ ${#g_files[@]} -eq 1 ]]; then
g_selected_file=${g_files[0]}
else
PS3=$prompt
# The while loop keeps us going while the user presses enter or Ctrl-D,
# making sure we get a selection.
while [[ -z $g_selected_file ]]; do
select g_selected_file in "${g_files[@]}"; do
[[ -n $g_selected_file ]] && break
echo "invalid choice" >&2
done
done
unset PS3
fi
}
# Edits the first found file matching a filename (locate) pattern
syse() {
if [[ $# -eq 0 ]]; then
_usage "<file>"
return 1
fi
file=$(locate -b -l1 -- "$1")
if [[ $? -ne 0 ]]; then
_err "could not find '$1'"
return 1
fi
"$EDITOR" -- "$file"
}
# Searches for files matching a _super_glob() pattern and opens the selected
# file for editing.
e() {
_super_glob_select_file "File to edit: " "$@"
if [[ -z $g_selected_file ]]; then
_err "no files found"
return 1
fi
"$EDITOR" -- "$g_selected_file"
}
# Lists files matching a _super_glob() pattern.
f() {
_super_glob "$@"
if [[ ${#g_files[@]} -eq 0 ]]; then
_err "no files found"
return 1
fi
printf "%s\n" "${g_files[@]}"
}
# Searches recursively in files for lines that match a given pattern and lists
# them. Optionally limits the search to files whose names match any of a number
# of given patterns. Excludes .git folders. With -c, does a case-sensitive
# search.
g() {
local pattern includes ignore_case=true
if [[ $# -eq 0 ]]; then
_usage "<pattern> [-c] [<file pattern> ...] "
return 1
fi
if [[ $1 = -c ]]; then
ignore_case=false
shift
if [[ $# -eq 0 ]]; then
_err "missing pattern"
return 1
fi
fi
pattern=$1
shift
if _prg_exists ag; then
includes=("${@/#/-G}")
ag --silent -U $(if ! $ignore_case; then echo -s; fi) "${includes[@]}" \
-- "$pattern"
else
includes=("${@/#/--include=}")
grep $(if $ignore_case; then echo -i; fi) -Inr --exclude-dir=.git \
"${includes[@]}" -- "$pattern" .
fi
}
# Jumps to a file matching a _super_glob() pattern. For directories, cd's to
# the directory. For ordinary files, cd's to the containing directory.
j() {
_super_glob_select_file "Where to jump: " "$@"
if [[ -z $g_selected_file ]]; then
_err "no files found"
return 1
fi
_cd_to "$g_selected_file"
}
# Searches for files matching a _super_glob() pattern, jumps to the directory
# (like for j()), and then opens the file for editing.
je() {
_super_glob_select_file "File to jump to and edit: " "$@"
if [[ -z $g_selected_file ]]; then
_err "no files found"
return 1
fi
_cd_to "$g_selected_file"
"$EDITOR" -- "$(basename -- "$g_selected_file")"
}
# Creates a directory if it does not exist and jumps to it.
mcd() {
if [[ $# -ne 1 ]]; then
_usage "<dir>"
return 1
fi
mkdir -p -- "$1" && cd -- "$1"
}
# Creates a timestamped tar.bz2 archive.
make_archive() {
if [[ $# -lt 2 ]]; then
_usage "<archive basename> <file> [<file> ...]"
return 1
fi
tar cj --force-local --file="$1-$(date +%F-%H-%M-%S).tar.bz2" -- "${@:2}"
}
# Prints files and directories (non-recursively) in 'directory' (default '.')
# sorted by size.
big() {
if [[ $# -gt 1 ]]; then
_usage "[<directory>]"
return 1
fi
shopt -s dotglob
du -chs -- "${1-.}"/* | sort -h
shopt -u dotglob
}
# Compiles a C/++ program from a single file with -Wall. Produces a debugging
# executable with no suffix and an optimized -O3 executable with an _opt
# suffix.
# TODO: Factor out compiler determination.
c() {
local compiler file warn_opts
if [[ $# -ne 1 ]]; then
_usage "<source file>"
return 1
fi
file=$1
if [[ ! -e $file ]]; then
_err "'$file' does not exist"
return 1
fi
case $file in
*.cpp) compiler=g++ ;;
*.c ) compiler=gcc ;;
* )
_err "unknown language for '$file'"
return 1
;;
esac
warn_opts="-Wall -Wno-unused-variable -Wno-unused-but-set-variable"
"$compiler" -o "${file%.*}" -ggdb3 $warn_opts "$file" && \
"$compiler" -o "${file%.*}_opt" -O3 $warn_opts "$file"
}
# Compiles a C/++ program debugging executable from a single file with -Wall
# and runs it.
r() {
local compiler file
if [[ $# -eq 0 ]]; then
_usage "<source file> [<argument> ...]"
return 1
fi
file=$1
if [[ ! -e $file ]]; then
_err "'$file' does not exist"
return 1
fi
case $file in
*.cpp) compiler=g++ ;;
*.c ) compiler=gcc ;;
* )
_err "unknown language for '$file'"
return 1
;;
esac
"$compiler" -o "${file%.*}" -ggdb3 -Wall -Wno-unused-variable "$file" && \
./"${file%.*}" "${@:2}"
}
# Suppress the GDB introductory and copyright message
alias gdb="gdb -q"
alias cgdb="cgdb -q"
# Configures (-c) a Linux kernel using the distribution defconfig minus
# non-loaded modules, builds (-b), and/or installs (-i) it
k() {
local OPTIND opt
while getopts "cbi" opt; do
case $opt in
c)
dist_config=/boot/config-$(uname -r)
if [[ ! -f $dist_config ]]; then
_err "'$dist_config' not found or not a regular file"
return 1
fi
[[ -f .config ]] && cp .config .oldconfig # Make a backup
cp "$dist_config" .config
# Disable modules not currently loaded. Cuts down on build time
# and initramfs size.
make localmodconfig
;;
b)
make -j$(($(getconf _NPROCESSORS_ONLN) + 1))
;;
i)
sudo make modules_install install
;;
esac
done
}
# Creates a tags file in the current directory for the standard include
# directories
make_include_tags() {
if [[ $# -gt 0 ]]; then
_usage "(no arguments)"
return 1
fi
# Include filenames, qualified names, and declarations. Treat filenames
# with no extension or a .tcc extension as C++ headers. Exclude Qt stuff
# and C++ stuff from old libstdc++ versions. Use line numbers (-n) as
# system headers seldom change.
ctags --langmap=c++:+..tcc -h+..tcc \
--extra=fq --c++-kinds=+p \
-I_GLIBCXX_VISIBILITY+ \
-n --links=no \
--exclude='*c++/4.5*' --exclude='*/qt4/*' \
-R /usr/include /usr/local/include
}
# Share history between sessions
# (http://stackoverflow.com/questions/103944/real-time-history-export-amongst-bash-terminal-windows)
export HISTCONTROL=ignoredups:erasedups # no duplicate entries
export HISTSIZE=100000 # big big history
export HISTFILESIZE=100000 # big big history
shopt -s histappend # append to history, don't overwrite it
# Save and reload the history after each command finishes
export PROMPT_COMMAND="history -a; history -c; history -r; $PROMPT_COMMAND"
LESS=-
LESS+=F # Do not use pager if output fits on screen
LESS+=i # Case-insensitive search when only lowercase characters are used
LESS+=M # Be verbose (show percentage, etc.)
LESS+=R # Show ANSI colors
# Do not send termcap initialization/deinitialization codes. Fixes background
# screwiness in 'less' after using 256-color color schemes in Vim for some
# reason.
LESS+=X
LESS+=x4 # Display tabs as 4 spaces
export LESS
# Functions for inspecting compiler output
_asm() {
local compiler
if [[ $# -ne 3 ]]; then
_usage "<filename>" 2
return 1
fi
local prefix=$1
local options=$2
local file=$3
local ofile=/tmp/${file%.cpp}.o
if [[ ! -e $file ]]; then
_err "'$file' does not exist" 2
return 1
fi
case $file in
*.cpp ) compiler=g++ ;;
*.c ) compiler=gcc ;;
* )
_err "unknown language for '$file'" 2
return 1
;;
esac
"$prefix$compiler" -o "$ofile" -c -ggdb3 $options "$file" || return $?
if [[ ! -e $ofile ]]; then
_err "no '$ofile' generated by $compiler. Aborting." 2
return 1
fi
"$prefix"objdump -M intel-mnemonic -Cd "$ofile"
command rm -- "$ofile"
}
asm() {
_asm "" "-O3 -fomit-frame-pointer" "$@"
}
asm_noopt() {
_asm "" "-O0" "$@"
}
armasm() {
_asm "arm-none-eabi-" "-O3 -mthumb -mcpu=cortex-a8 -fomit-frame-pointer" "$@"
}
armasmnothumb() {
_asm "arm-none-eabi-" "-O3 -mcpu=cortex-a8 -fomit-frame-pointer" "$@"
}
# Trace file access by a program. Prints the trace separately after the
# program's output once it has terminated.
files() {
local tracefile
if [[ $# -eq 0 ]]; then
_usage "<program> [<argument> ...]"
return 1
fi
if ! _prg_exists "$1"; then
_err "found no program called '$1'"
return 1
fi
tracefile=$(mktemp)
strace -e trace=file -f -o "$tracefile" -- "$@"
# Print the trace output after the regular output and remove some common
# clutter
echo -e "\nTrace:\n"
egrep -v -e /usr/share/locale -e 'statfs.*selinux' < "$tracefile"
command rm -- "$tracefile"
}
# Show where a shell function is defined. 'declare -F' can be used to list
# functions.
floc() {
local error
if [[ $# -ne 1 ]]; then
_usage "<function name>"
return 1
fi
shopt -s extdebug
declare -F -- "$1"
error=$?
[[ $error -ne 0 ]] && _err "no function called '$1' defined"
shopt -u extdebug
return $error
}
complete -A function floc
# Git {{{
alias ga="git add -u"
alias gc="git commit -v"
alias gd="git diff"
alias gds="git diff --stat"
alias gdc="git diff --cached"
alias gdcs="git diff --cached --stat"
alias gl="git log"
alias glp="git log -p"
alias gls="git log --stat"
alias gp="git pull --rebase"
alias gs="git status"
alias discard="git reset --hard HEAD"
alias amend="git commit --amend"
# Adds changes to tracked files to the branch's tip commit
alias fixup="git commit -a --amend -C HEAD"
# Checks if a file is in the repository
i() {
if [[ $# -ne 1 ]]; then
_usage "<filename>"
return 1
fi
if git ls-files --error-unmatch -- "$1" &>/dev/null; then
echo -n yes
else
echo -n no
fi
[[ -e $1 ]] || echo -n " (non-existent)"
echo
}
# Prints the branch tracked by a branch together with the remote and its URL.
# Without arguments, defaults to the current branch.
upstream() {
local branch remote_branch remote remote_url
if [[ $# -gt 1 ]]; then
_usage "[<branch>] (defaults to current branch)"
return 1
fi
if ! git rev-parse --git-dir &>/dev/null; then
_err "not inside a Git repository"
return 1
fi
if [[ -z $1 ]]; then
if ! branch=$(git symbolic-ref HEAD 2>/dev/null); then
_err "failed to get branch for HEAD (detached?)"
return 1
fi
else
branch=$1
fi
branch=${branch#refs/heads/}
if ! git show-ref --verify -q "refs/heads/$branch"; then
_err "no branch named '$branch'"
return 1
fi
remote_branch=$(git config "branch.$branch.merge") || remote_branch="(no upstream)"
remote_branch=${remote_branch#refs/heads/}
if remote=$(git config "branch.$branch.remote"); then
remote_url=" ($(git config "remote.$remote.url"))" || \
remote_url=" (could not get the remote's URL)"
else
remote="(no remote)"
remote_url=
fi
# Avoid printing any unnecessary extra spaces
echo $remote_branch @ ${remote}$remote_url
}
# Fetches a single branch and its objects from a remote and sets it up as a
# tracking branch. With no name given, uses the name of the remote branch for
# the local branch.
fetch() {
local remote remote_branch local_branch error
if [[ $# -eq 0 || $# -gt 3 ]]; then
_usage "<remote> <remote branch> [<local branch>]"
return 1
fi
remote=$1
remote_branch=$2
local_branch=${3-$remote_branch}
git fetch "$remote" "+$remote_branch:$local_branch" || return $?
if ! git show-ref --verify -q "refs/heads/$local_branch"; then
_err "no local branch '$local_branch' was created"
return 1
fi
git branch --set-upstream "$local_branch" "remotes/$remote/$remote_branch"
error=$?
if [[ $error -ne 0 ]]; then
_err "failed to set '$remote_branch' on '$remote' as the upstream of '$local_branch'"
return $error
fi
}
# }}}
# Terminals, prompts and colors
_init_terminals_and_prompts_and_colors() {
# Use the true/false shell builtins to create booleans
local is_xterm=false
local has_256_colors=false
case $TERM in
# Assume that all terminals that identify themselves as either "xterm"
# or "xterm-256color" support 256 colors. This could be refined.
xterm | xterm-256color )
# Some terminal apps (e.g. Vim) check for this
export TERM=xterm-256color
is_xterm=true
has_256_colors=true
;;
rxvt-*256color )
has_256_colors=true
;;
esac
# Helper function for displaying the current Git branch in the Bash prompt
_where() {
local branch
branch=$(git symbolic-ref HEAD 2>/dev/null) || return
echo -n "${branch#refs/heads/} "
}
# Resets all color settings
local r='\[\033[0m\]'
if $has_256_colors; then
# See the following for reference:
# http://lucentbeing.com/blog/that-256-color-thing/
# http://jimlund.org/blog/?p=130
# Changes the foreground color for 256-color mode
_c() { echo -n '\[\033[38;05;'$1'm\]'; }
PS1="$(_c 46)\u $(_c 214)\h $(_c 39)\w $(_c 46)\$(_where)$r$ "
else
# Changes the foreground color for 8-color mode; 0-7 corresponds to
# black, red, green, yellow, blue, magenta, cyan, and white, in that
# order.
_c() { echo -n '\[\033[3'$1'm\]'; }
PS1="$(_c 2)\u $(_c 3)\h $(_c 4)\w $(_c 2)$(_where)$r$ "
fi
# For xterm, display the current working directory and username/host in the
# window title bar
$is_xterm && PS1="\[\033]0;\w \u \h\007\]$PS1"
}
_init_terminals_and_prompts_and_colors
# Site-specific settings
_local_settings_file=~/conf/bashlocal
[[ -e $_local_settings_file ]] && . "$_local_settings_file"