]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - usr.sbin/bsdconfig/share/strings.subr
MFC revisions 256321-256323,256331,256333,256335,256343:
[FreeBSD/stable/10.git] / usr.sbin / bsdconfig / share / strings.subr
1 if [ ! "$_STRINGS_SUBR" ]; then _STRINGS_SUBR=1
2 #
3 # Copyright (c) 2006-2013 Devin Teske
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
9 # 1. Redistributions of source code must retain the above copyright
10 #    notice, this list of conditions and the following disclaimer.
11 # 2. Redistributions in binary form must reproduce the above copyright
12 #    notice, this list of conditions and the following disclaimer in the
13 #    documentation and/or other materials provided with the distribution.
14 #
15 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 # ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 # SUCH DAMAGE.
26 #
27 # $FreeBSD$
28 #
29 ############################################################ INCLUDES
30
31 BSDCFG_SHARE="/usr/share/bsdconfig"
32 . $BSDCFG_SHARE/common.subr || exit 1
33
34 ############################################################ GLOBALS
35
36 #
37 # Valid characters that can appear in an sh(1) variable name
38 #
39 # Please note that the character ranges A-Z and a-z should be avoided because
40 # these can include accent characters (which are not valid in a variable name).
41 # For example, A-Z matches any character that sorts after A but before Z,
42 # including A and Z. Although ASCII order would make more sense, that is not
43 # how it works.
44 #
45 VALID_VARNAME_CHARS="0-9ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"
46
47 ############################################################ FUNCTIONS
48
49 # f_substr "$string" $start [ $length ]
50 #
51 # Simple wrapper to awk(1)'s `substr' function.
52 #
53 f_substr()
54 {
55         local string="$1" start="${2:-0}" len="${3:-0}"
56         echo "$string" | awk "{ print substr(\$0, $start, $len) }"
57 }
58
59 # f_snprintf $var_to_set $size $format ...
60 #
61 # Similar to snprintf(3), write at most $size number of bytes into $var_to_set
62 # using printf(1) syntax (`$format ...'). The value of $var_to_set is NULL
63 # unless at-least one byte is stored from the output.
64 #
65 f_snprintf()
66 {
67         local __var_to_set="$1" __size="$2"
68         shift 2 # var_to_set/size
69         eval "$__var_to_set"=\$\( printf \"\$@\" \| awk -v max=\"\$__size\" \''
70         {
71                 len = length($0)
72                 max -= len
73                 print substr($0,0,(max > 0 ? len : max + len))
74                 if ( max < 0 ) exit
75                 max--
76         }'\' \)
77 }
78
79 # f_vsnprintf $var_to_set $size $format $format_args
80 #
81 # Similar to vsnprintf(3), write at most $size number of bytes into $var_to_set
82 # using printf(1) syntax (`$format $format_args'). The value of $var_to_set is
83 # NULL unless at-least one byte is stored from the output.
84 #
85 # Example 1:
86 #
87 #       limit=7 format="%s"
88 #       format_args="'abc   123'" # 3-spaces between abc and 123
89 #       f_vsnprintf foo $limit "$format" "$format_args" # foo=[abc   1]
90 #
91 # Example 2:
92 #
93 #       limit=12 format="%s %s"
94 #       format_args="   'doghouse'      'foxhound'   "
95 #               # even more spaces added to illustrate escape-method
96 #       f_vsnprintf foo $limit "$format" "$format_args" # foo=[doghouse fox]
97 #
98 # Example 3:
99 #
100 #       limit=13 format="%s %s"
101 #       f_shell_escape arg1 'aaa"aaa' # arg1=[aaa"aaa] (no change)
102 #       f_shell_escape arg2 "aaa'aaa" # arg2=[aaa'\''aaa] (escaped s-quote)
103 #       format_args="'$arg1' '$arg2'" # use single-quotes to surround args
104 #       f_vsnprintf foo $limit "$format" "$format_args" # foo=[aaa"aaa aaa'a]
105 #
106 # In all of the above examples, the call to f_vsnprintf() does not change. Only
107 # the contents of $limit, $format, and $format_args changes in each example.
108 #
109 f_vsnprintf()
110 {
111         eval f_snprintf \"\$1\" \"\$2\" \"\$3\" $4
112 }
113
114 # f_longest_line_length
115 #
116 # Simple wrapper to an awk(1) script to print the length of the longest line of
117 # input (read from stdin). Supports the newline escape-sequence `\n' for
118 # splitting a single line into multiple lines.
119 #
120 f_longest_line_length_awk='
121 BEGIN { longest = 0 }
122 {
123         if (split($0, lines, /\\n/) > 1)
124         {
125                 for (n in lines)
126                 {
127                         len = length(lines[n])
128                         longest = ( len > longest ? len : longest )
129                 }
130         }
131         else
132         {
133                 len = length($0)
134                 longest = ( len > longest ? len : longest )
135         }
136 }
137 END { print longest }
138 '
139 f_longest_line_length()
140 {
141         awk "$f_longest_line_length_awk"
142 }
143
144 # f_number_of_lines
145 #
146 # Simple wrapper to an awk(1) script to print the number of lines read from
147 # stdin. Supports newline escape-sequence `\n' for splitting a single line into
148 # multiple lines.
149 #
150 f_number_of_lines_awk='
151 BEGIN { num_lines = 0 }
152 {
153         num_lines += split(" "$0, unused, /\\n/)
154 }
155 END { print num_lines }
156 '
157 f_number_of_lines()
158 {
159         awk "$f_number_of_lines_awk"
160 }
161
162 # f_isinteger $arg
163 #
164 # Returns true if argument is a positive/negative whole integer.
165 #
166 f_isinteger()
167 {
168         local arg="$1"
169
170         # Prevent division-by-zero
171         [ "$arg" = "0" ] && return $SUCCESS
172
173         # Attempt to perform arithmetic divison (an operation which will exit
174         # with error unless arg is a valid positive/negative whole integer).
175         #
176         ( : $((0/$arg)) ) > /dev/null 2>&1
177 }
178
179 # f_uriencode [$text]
180 #
181 # Encode $text for the purpose of embedding safely into a URL. Non-alphanumeric
182 # characters are converted to `%XX' sequence where XX represents the hexa-
183 # decimal ordinal of the non-alphanumeric character. If $text is missing, data
184 # is instead read from standard input.
185 #
186 f_uriencode_awk='
187 BEGIN {
188         output = ""
189         for (n = 0; n < 256; n++) pack[sprintf("%c", n)] = sprintf("%%%02x", n)
190 }
191 {
192         sline = ""
193         slen = length($0)
194         for (n = 1; n <= slen; n++) {
195                 char = substr($0, n, 1)
196                 if ( char !~ /^[[:alnum:]_]$/ ) char = pack[char]
197                 sline = sline char
198         }
199         output = output ( output ? "%0a" : "" ) sline
200 }
201 END { print output }
202 '
203 f_uriencode()
204 {
205         if [ $# -gt 0 ]; then
206                 echo "$1" | awk "$f_uriencode_awk"
207         else
208                 awk "$f_uriencode_awk"
209         fi
210 }
211
212 # f_uridecode [$text]
213 #
214 # Decode $text from a URI. Encoded characters are converted from their `%XX'
215 # sequence into original unencoded ASCII sequences. If $text is missing, data
216 # is instead read from standard input.
217 #
218 f_uridecode_awk='
219 BEGIN { for (n = 0; n < 256; n++) chr[n] = sprintf("%c", n) }
220 {
221         sline = ""
222         slen = length($0)
223         for (n = 1; n <= slen; n++)
224         {
225                 seq = substr($0, n, 3)
226                 if ( seq ~ /^%[[:xdigit:]][[:xdigit:]]$/ ) {
227                         hex = substr(seq, 2, 2)
228                         sline = sline chr[sprintf("%u", "0x"hex)]
229                         n += 2
230                 } else
231                         sline = sline substr(seq, 1, 1)
232         }
233         print sline
234 }
235 '
236 f_uridecode()
237 {
238         if [ $# -gt 0 ]; then
239                 echo "$1" | awk "$f_uridecode_awk"
240         else
241                 awk "$f_uridecode_awk"
242         fi
243 }
244
245 # f_replaceall $string $find $replace [$var_to_set]
246 #
247 # Replace all occurrences of $find in $string with $replace. If $var_to_set is
248 # either missing or NULL, the variable name is produced on standard out for
249 # capturing in a sub-shell (which is less recommended due to performance
250 # degradation).
251 #
252 f_replaceall()
253 {
254         local __left="" __right="$1"
255         local __find="$2" __replace="$3" __var_to_set="$4"
256         while :; do
257                 case "$__right" in *$__find*)
258                         __left="$__left${__right%%$__find*}$__replace"
259                         __right="${__right#*$__find}"
260                         continue
261                 esac
262                 break
263         done
264         __left="$__left${__right#*$__find}"
265         if [ "$__var_to_set" ]; then
266                 setvar "$__var_to_set" "$__left"
267         else
268                 echo "$__left"
269         fi
270 }
271
272 # f_str2varname $string [$var_to_set]
273 #
274 # Convert a string into a suitable value to be used as a variable name
275 # by converting unsuitable characters into the underscrore [_]. If $var_to_set
276 # is either missing or NULL, the variable name is produced on standard out for
277 # capturing in a sub-shell (which is less recommended due to performance
278 # degradation).
279 #
280 f_str2varname()
281 {
282         local __string="$1" __var_to_set="$2"
283         f_replaceall "$__string" "[!$VALID_VARNAME_CHARS]" "_" "$__var_to_set"
284 }
285
286 # f_shell_escape $string [$var_to_set]
287 #
288 # Escape $string for shell eval statement(s) by replacing all single-quotes
289 # with a special sequence that creates a compound string when interpolated
290 # by eval with surrounding single-quotes.
291 #
292 # For example:
293 #
294 #       foo="abc'123"
295 #       f_shell_escape "$foo" bar # bar=[abc'\''123]
296 #       eval echo \'$bar\' # produces abc'123
297 #
298 # This is helpful when processing an argument list that has to retain its
299 # escaped structure for later evaluations.
300 #
301 # WARNING: Surrounding single-quotes are not added; this is the responsibility
302 # of the code passing the escaped values to eval (which also aids readability).
303 #
304 f_shell_escape()
305 {
306         local __string="$1" __var_to_set="$2"
307         f_replaceall "$__string" "'" "'\\''" "$__var_to_set"
308 }
309
310 # f_shell_unescape $string [$var_to_set]
311 #
312 # The antithesis of f_shell_escape(), this function takes an escaped $string
313 # and expands it.
314 #
315 # For example:
316 #
317 #       foo="abc'123"
318 #       f_shell_escape "$foo" bar # bar=[abc'\''123]
319 #       f_shell_unescape "$bar" # produces abc'123
320 #
321 f_shell_unescape()
322 {
323         local __string="$1" __var_to_set="$2"
324         f_replaceall "$__string" "'\\''" "'" "$__var_to_set"
325 }
326
327 # f_expand_number $string [$var_to_set]
328 #
329 # Unformat $string into a number, optionally to be stored in $var_to_set. This
330 # function follows the SI power of two convention.
331 #
332 # The prefixes are:
333 #
334 #       Prefix  Description     Multiplier
335 #       k       kilo            1024
336 #       M       mega            1048576
337 #       G       giga            1073741824
338 #       T       tera            1099511627776
339 #       P       peta            1125899906842624
340 #       E       exa             1152921504606846976
341 #
342 # NOTE: Prefixes are case-insensitive.
343 #
344 # Upon successful completion, the value 0 is returned (or stored to
345 # $var_to_set); otherwise -1. Reasons for a -1 return include:
346 #
347 #       Given $string contains no digits.
348 #       An unrecognized prefix was given.
349 #       Result too large to calculate.
350 #
351 f_expand_number()
352 {
353         local __string="$1" __var_to_set="$2"
354         local __cp __num
355
356         # Remove any leading non-digits
357         while :; do
358                 __cp="$__string"
359                 __string="${__cp#[!0-9]}"
360                 [ "$__string" = "$__cp" ] && break
361         done
362
363         # Return `-1' if string didn't contain any digits
364         if [ ! "$__string" ]; then
365                 if [ "$__var_to_set" ]; then
366                         setvar "$__var_to_set" -1
367                 else
368                         echo -1
369                 fi
370                 return $FAILURE
371         fi
372
373         # Store the numbers
374         __num="${__string%%[!0-9]*}"
375
376         # Shortcut
377         if [ $__num -eq 0 ]; then
378                 if [ "$__var_to_set" ]; then
379                         setvar "$__var_to_set" 0
380                 else
381                         echo 0
382                 fi
383                 return $SUCCESS
384         fi
385
386         # Remove all the leading numbers from the string to get at the prefix
387         while :; do
388                 __cp="$__string"
389                 __string="${__cp#[0-9]}"
390                 [ "$__string" = "$__cp" ] && break
391         done
392
393         # Test for invalid prefix
394         case "$__string" in
395         ""|[KkMmGgTtPpEe]*) : known prefix ;;
396         *)
397                 # Unknown prefix
398                 if [ "$__var_to_set" ]; then
399                         setvar "$__var_to_set" -1
400                 else
401                         echo -1
402                 fi
403                 return $FAILURE
404         esac
405
406         # Multiply the number out
407         case "$__string" in
408         [Kk]) __num=$(( $__num * 1024 )) ;;
409         [Mm]) __num=$(( $__num * 1048576 )) ;;
410         [Gg]) __num=$(( $__num * 1073741824 )) ;;
411         [Tt]) __num=$(( $__num * 1099511627776 )) ;;
412         [Pp]) __num=$(( $__num * 1125899906842624 )) ;;
413         [Ee]) __num=$(( $__num * 1152921504606846976 )) ;;
414         esac
415         if [ $__num -le 0 ]; then
416                 # Arithmetic overflow
417                 if [ "$__var_to_set" ]; then
418                         setvar "$__var_to_set" -1
419                 else
420                         echo -1
421                 fi
422                 return $FAILURE
423         fi
424
425         # Return the number
426         if [ "$__var_to_set" ]; then
427                 setvar "$__var_to_set" $__num
428         else
429                 echo $__num
430         fi
431 }
432
433 ############################################################ MAIN
434
435 f_dprintf "%s: Successfully loaded." strings.subr
436
437 fi # ! $_STRINGS_SUBR