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
|
# Limit to ksh93; most of this works in mksh, but not all of it, and pdksh
# doesn't have a `typeset -p` that includes printable values at all.
case $KSH_VERSION in
*' 93'*) ;;
*) return ;;
esac
#
# keep -- Main function for kshkeep; provided with a list of NAMEs, whether
# shell functions or variables, writes the current definition of each NAME to a
# directory $KSHKEEP (defaults to ~/.kshkeep.d) with a .ksh 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
#
function keep {
# Name self
typeset self
self=keep
# Figure out the directory to which we're reading and writing these scripts
typeset kshkeep
kshkeep=${KSHKEEP:-"$HOME"/.kshkeep.d}
mkdir -p -- "$kshkeep" || return
# Parse options
typeset opt delete
typeset 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)
cat <<EOF
$self: Keep variables and functions in shell permanently by writing them to
named scripts iterated on shell start, in \$KSHKEEP (defaults to
~/.kshkeep.d).
USAGE:
$self
List all the current kept variables and functions
$self NAME1 [NAME2 ...]
Write the current definition for the given NAMEs to keep files
$self -d NAME1 [NAME2 ...]
Delete the keep files for the given NAMEs
$self -h
Show this help
EOF
return
;;
# Unknown other option
\?)
printf 'ksh: %s -%s: invalid option\n' \
"$self" "$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
typeset -i errors
errors=0
# Iterate through the NAMEs given
typeset 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 'ksh: %s: %s not a valid NAME\n' \
"$self" "$name" >&2
((errors++))
;;
# NAME is valid, proceed
*)
# If -d was given, delete the keep files for the NAME
if ((delete)) ; then
rm -- "$kshkeep"/"$name".ksh ||
((errors++))
# Save a function
elif [[ $(whence -v "$name" 2>/dev/null) == *' is a function' ]] ; then
typeset -f -- "$name" >"$kshkeep"/"$name".ksh ||
((errors++))
# Save a variable
elif [[ -n "$name" ]] ; then
typeset -p -- "$name" >"$kshkeep"/"$name".ksh ||
((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 'ksh: %s: must specify at least one NAME to delete\n' \
"$self" >&2
return 2
fi
# Otherwise the user must want us to print all the NAMEs kept
(
for keep in "$kshkeep"/*.ksh ; do
[[ -f "$keep" ]] || break
keep=${keep##*/}
keep=${keep%.ksh}
printf '%s\n' "$keep"
done
)
}
# Load any existing scripts in kshkeep
for kshkeep in "${KSHKEEP:-"$HOME"/.kshkeep.d}"/*.ksh ; do
[[ -e $kshkeep ]] && source "$kshkeep"
done
unset -v kshkeep
|