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
|
#
# keep -- Main function for bashkeep; provided with a list of NAMEs, whether
# shell functions or variables, writes the current definition of each NAME to a
# directory $BASHKEEP (defaults to ~/.bashkeep.d) with a .bash suffix, each of
# which is reloaded each time this file is called. This allows you to quickly
# arrange to keep that useful shell function or variable you made inline on
# subsequent logins.
#
# Consider a shell function declared inline with the NAME 'ayy':
#
# $ ayy() { printf '%s\n' lmao ; }
# $ ayy
# lmao
# $ keep ayy
# $ keep
# ayy
# $ exit
#
# Then, on next login, the function is redefined for you:
#
# $ ayy
# lmao
#
# To get rid of it:
#
# $ keep -d ayy
#
keep() {
# Figure out the directory to which we're reading and writing these scripts
local bashkeep
bashkeep=${BASHKEEP:-$HOME/.bashkeep.d}
mkdir -p -- "$bashkeep" || return
# Parse options
local opt delete
local OPTERR OPTIND OPTARG
while getopts 'dh' opt ; do
case $opt in
# -d given; means delete the keepfiles for the given names
d)
delete=1
;;
# -h given; means show help
h)
while IFS= read -r line ; do
printf '%s\n' "$line"
done <<EOF
$FUNCNAME: Keep variables and functions in shell permanently by writing them to
named scripts iterated on shell start, in \$BASHKEEP (defaults to
~/.bashkeep.d).
USAGE:
$FUNCNAME
List all the current kept variables and functions
$FUNCNAME NAME1 [NAME2 ...]
Write the current definition for the given NAMEs to keep files
$FUNCNAME -d NAME1 [NAME2 ...]
Delete the keep files for the given NAMEs
$FUNCNAME -h
Show this help
EOF
return
;;
# Unknown other option
\?)
printf 'bash: %s -%s: invalid option\n' \
"$FUNCNAME" "$opt" >&2
return 2
;;
esac
done
shift "$((OPTIND-1))"
# If any arguments left, we must be either keeping or deleting
if (($#)) ; then
# Start keeping count of any errors
local -i errors
errors=0
# Iterate through the NAMEs given
local name
for name ; do
# Check NAMEs for validity
case $name in
# NAME must start with letters or an underscore, and contain no
# characters besides letters, numbers, or underscores
*[^a-zA-Z0-9_]*|[^a-zA-Z_]*)
printf 'bash: %s: %s not a valid NAME\n' \
"$FUNCNAME" "$name" >&2
((errors++))
;;
# NAME is valid, proceed
*)
# If -d was given, delete the keep files for the NAME
if ((delete)) ; then
rm -- "$bashkeep"/"$name".bash \
|| ((errors++))
# Otherwise, attempt to create the keep file, using an
# appropriate call to the declare builtin
else
{ case $(type -t "$name") in
'function')
declare -f -- "$name"
;;
*)
declare -p -- "$name"
;;
esac ; } > "$bashkeep"/"$name".bash \
|| ((errors++))
fi
;;
esac
done
# Return 1 if we accrued any errors, 0 otherwise
return "$((errors > 0))"
fi
# Deleting is an error, since we need at least one argument
if ((delete)) ; then
printf 'bash: %s: must specify at least one NAME to delete\n'
"$FUNCNAME" >&2
return 2
fi
# Otherwise the user must want us to print all the NAMEs kept
(
shopt -s dotglob nullglob
declare -a keeps
keeps=("${bashkeep}"/*.bash)
keeps=("${keeps[@]##*/}")
keeps=("${keeps[@]%.bash}")
((${#keeps[@]})) || exit 0
printf '%s\n' "${keeps[@]}"
)
}
# Complete calls to keep with existing function names and variable names
complete -A function -A variable keep
# Load any existing scripts in bashkeep
if [[ -d ${BASHKEEP:-$HOME/.bashkeep.d} ]] ; then
for bashkeep in "${BASHKEEP:-$HOME/.bashkeep.d}"/*.bash ; do
[[ -e $bashkeep ]] || continue
source "$bashkeep"
done
unset -v bashkeep
fi
|