strings.subr revision 250702
116Salmif [ ! "$_STRINGS_SUBR" ]; then _STRINGS_SUBR=1
216Salm#
316Salm# 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 (INLUDING, 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: head/usr.sbin/bsdconfig/share/strings.subr 250702 2013-05-16 16:51:52Z dteske $
28#
29############################################################ GLOBALS
30
31#
32# Valid characters that can appear in an sh(1) variable name
33#
34# Please note that the character ranges A-Z and a-z should be avoided because
35# these can include accent characters (which are not valid in a variable name).
36# For example, A-Z matches any character that sorts after A but before Z,
37# including A and Z. Although ASCII order would make more sense, that is not
38# how it works.
39#
40VALID_VARNAME_CHARS="0-9ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"
41
42############################################################ FUNCTIONS
43
44# f_substr "$string" $start [ $length ]
45#
46# Simple wrapper to awk(1)'s `substr' function.
47#
48f_substr()
49{
50	local string="$1" start="${2:-0}" len="${3:-0}"
51	echo "$string" | awk "{ print substr(\$0, $start, $len) }"
52}
53
54# f_snprintf $var_to_set $size $format ...
55#
56# Similar to snprintf(3), write at most $size number of bytes into $var_to_set
57# using printf(1) syntax (`$format ...'). The value of $var_to_set is NULL
58# unless at-least one byte is stored from the output.
59#
60f_snprintf()
61{
62	local __var_to_set="$1" __size="$2"
63	shift 2 # var_to_set/size
64	eval "$__var_to_set"=\$\( printf \"\$@\" \| awk -v max=\"\$__size\" \''
65	{
66		len = length($0)
67		max -= len
68		print substr($0,0,(max > 0 ? len : max + len))
69		if ( max < 0 ) exit
70		max--
71	}'\' \)
72}
73
74# f_longest_line_length
75#
76# Simple wrapper to an awk(1) script to print the length of the longest line of
77# input (read from stdin). Supports the newline escape-sequence `\n' for
78# splitting a single line into multiple lines.
79#
80f_longest_line_length_awk='
81BEGIN { longest = 0 }
82{
83	if (split($0, lines, /\\n/) > 1)
84	{
85		for (n in lines)
86		{
87			len = length(lines[n])
88			longest = ( len > longest ? len : longest )
89		}
90	}
91	else
92	{
93		len = length($0)
94		longest = ( len > longest ? len : longest )
95	}
96}
97END { print longest }
98'
99f_longest_line_length()
100{
101	awk "$f_longest_line_length_awk"
102}
103
104# f_number_of_lines
105#
106# Simple wrapper to an awk(1) script to print the number of lines read from
107# stdin. Supports newline escape-sequence `\n' for splitting a single line into
108# multiple lines.
109#
110f_number_of_lines_awk='
111BEGIN { num_lines = 0 }
112{
113	num_lines += split(" "$0, unused, /\\n/)
114}
115END { print num_lines }
116'
117f_number_of_lines()
118{
119	awk "$f_number_of_lines_awk"
120}
121
122# f_isinteger $arg
123#
124# Returns true if argument is a positive/negative whole integer.
125#
126f_isinteger()
127{
128	local arg="$1"
129
130	# Prevent division-by-zero
131	[ "$arg" = "0" ] && return $SUCCESS
132
133	# Attempt to perform arithmetic divison (an operation which will exit
134	# with error unless arg is a valid positive/negative whole integer).
135	#
136	( : $((0/$arg)) ) > /dev/null 2>&1
137}
138
139# f_uriencode [$text]
140#
141# Encode $text for the purpose of embedding safely into a URL. Non-alphanumeric
142# characters are converted to `%XX' sequence where XX represents the hexa-
143# decimal ordinal of the non-alphanumeric character. If $text is missing, data
144# is instead read from standard input.
145#
146f_uriencode_awk='
147BEGIN {
148	output = ""
149	for (n = 0; n < 256; n++) pack[sprintf("%c", n)] = sprintf("%%%02x", n)
150}
151{
152	sline = ""
153	slen = length($0)
154	for (n = 1; n <= slen; n++) {
155		char = substr($0, n, 1)
156		if ( char !~ /^[[:alnum:]_]$/ ) char = pack[char]
157		sline = sline char
158	}
159	output = output ( output ? "%0a" : "" ) sline
160}
161END { print output }
162'
163f_uriencode()
164{
165	if [ $# -gt 0 ]; then
166		echo "$1" | awk "$f_uriencode_awk"
167	else
168		awk "$f_uriencode_awk"
169	fi
170}
171
172# f_uridecode [$text]
173#
174# Decode $text from a URI. Encoded characters are converted from their `%XX'
175# sequence into original unencoded ASCII sequences. If $text is missing, data
176# is instead read from standard input.
177#
178f_uridecode_awk='
179BEGIN { for (n = 0; n < 256; n++) chr[n] = sprintf("%c", n) }
180{
181	sline = ""
182	slen = length($0)
183	for (n = 1; n <= slen; n++)
184	{
185		seq = substr($0, n, 3)
186		if ( seq ~ /^%[[:xdigit:]][[:xdigit:]]$/ ) {
187			hex = substr(seq, 2, 2)
188			sline = sline chr[sprintf("%u", "0x"hex)]
189			n += 2
190		} else
191			sline = sline substr(seq, 1, 1)
192	}
193	print sline
194}
195'
196f_uridecode()
197{
198	if [ $# -gt 0 ]; then
199		echo "$1" | awk "$f_uridecode_awk"
200	else
201		awk "$f_uridecode_awk"
202	fi
203}
204
205# f_replaceall $string $find $replace [$var_to_set]
206#
207# Replace all occurrences of $find in $string with $replace. If $var_to_set is
208# either missing or NULL, the variable name is produced on standard out for
209# capturing in a sub-shell (which is less recommended due to performance
210# degradation).
211#
212f_replaceall()
213{
214	local __left="" __right="$1"
215	local __find="$2" __replace="$3" __var_to_set="$4"
216	while :; do
217		case "$__right" in *$__find*)
218			__left="$__left${__right%%$__find*}$__replace"
219			__right="${__right#*$__find}"
220			continue
221		esac
222		break
223	done
224	__left="$__left${__right#*$__find}"
225	if [ "$__var_to_set" ]; then
226		setvar "$__var_to_set" "$__left"
227	else
228		echo "$__left"
229	fi
230}
231
232# f_str2varname $string [$var_to_set]
233#
234# Convert a string into a suitable value to be used as a variable name
235# by converting unsuitable characters into the underscrore [_]. If $var_to_set
236# is either missing or NULL, the variable name is produced on standard out for
237# capturing in a sub-shell (which is less recommended due to performance
238# degradation).
239#
240f_str2varname()
241{
242	local __string="$1" __var_to_set="$2"
243	f_replaceall "$__string" "[!$VALID_VARNAME_CHARS]" "_" "$__var_to_set"
244}
245
246# f_shell_escape $string [$var_to_set]
247#
248# Escape $string for shell eval statement(s) by replacing all single-quotes
249# with a special sequence that creates a compound string when interpolated
250# by eval with surrounding single-quotes.
251#
252# For example:
253#
254# 	foo="abc'123"
255# 	f_shell_escape "$foo" bar # bar=[abc'\''123]
256# 	eval echo \'$foo\' # produces abc'123
257#
258# This is helpful when processing an argument list that has to retain its
259# escaped structure for later evaluations.
260#
261# WARNING: Surrounding single-quotes are not added; this is the responsibility
262# of the code passing the escaped values to eval (which also aids readability).
263#
264f_shell_escape()
265{
266	local __string="$1" __var_to_set="$2"
267	f_replaceall "$__string" "'" "'\\''" "$__var_to_set"
268}
269
270# f_shell_unescape $string [$var_to_set]
271#
272# The antithesis of f_shell_escape(), this function takes an escaped $string
273# and expands it.
274#
275# For example:
276#
277# 	foo="abc'123"
278# 	f_shell_escape "$foo" bar # bar=[abc'\''123]
279# 	f_shell_unescape "$bar" # produces abc'123
280#
281f_shell_unescape()
282{
283	local __string="$1" __var_to_set="$2"
284	f_replaceall "$__string" "'\\''" "'" "$__var_to_set"
285}
286
287############################################################ MAIN
288
289f_dprintf "%s: Successfully loaded." strings.subr
290
291fi # ! $_STRINGS_SUBR
292