3 # This defines some general, useful functions.
5 #hmmm: starting to get a bit beefy in here. perhaps there is a good way to refactor the functions into more specific folders, if they aren't really totally general purpose?
9 # test whether we've been here before or not.
11 type function_sentinel &>/dev/null
13 # there was no error, so we can skip the inits.
14 if [ ! -z "$DEBUG_FEISTY_MEOW" ]; then
15 echo "skipping function definitions, because already defined."
22 if [ -z "$skip_all" ]; then
24 if [ ! -z "$DEBUG_FEISTY_MEOW" ]; then
25 echo "feisty meow function definitions beginning now..."
28 # a handy little method that can be used for date strings. it was getting
29 # really tiresome how many different ways the script did the date formatting.
30 function date_stringer() {
32 if [ -z "$sep" ]; then sep='_'; fi
33 date +"%Y$sep%m$sep%d$sep%H%M$sep%S" | tr -d '/\n/'
36 # a slightly different but also handy time and date function. this is
37 # intended for prefixing on log lines, so that each line has the time it
38 # occurred as the first element.
39 function timestamper() {
40 date +"[%Y-%m-%d %H:%M:%S] " | tr -d '/\n/'
43 # a wrapper for the which command that finds items on the path. some OSes
44 # do not provide which, so we want to not be spewing errors when that
48 local to_find="$1"; shift
49 local WHICHER="$(/usr/bin/which which 2>/dev/null)"
50 #>&2 echo "got whicher as: $WHICHER"
52 # there is no which command here. we produce nothing due to this.
56 local sporkenz # must be defined local here, before call, or we don't get exit value?!
57 sporkenz=$($WHICHER "$to_find" 2>/dev/null)
58 #>&2 echo "broken with this line, but here is exit val: $?"
60 #>&2 echo "got whicher as: $WHICHER"
65 # makes a directory of the name specified and then tries to change the
66 # current directory to that directory.
68 if [ ! -d "$1" ]; then mkdir -p "$1"; fi
72 # returns true if the variable is an array.
74 [[ "$(declare -p $1)" =~ "declare -a" ]]
77 # returns true if the name provided is a defined alias.
85 # makes the status of pipe number N (passed as first parameter) into the
86 # main return value (i.e., the value for $?). this is super handy to avoid
87 # repeating the awkward looking code below in multiple places.
88 # the numbering starts at zero, for the first item at the head of the pipe.
89 function promote_pipe_return()
91 ( exit ${PIPESTATUS[$1]} )
96 # sets the main exit value with the pipe status from a smooshed combination
97 # of the top N pipe statuses.
98 # zero from all becomes zero, but any pipe status being non-zero will yield
99 # a non-zero exit status. note that the exit values involved are not
100 # preserved intact--only the fact of a bad, non-zero exit is signalled.
101 function combine_pipe_returns()
103 local preserved_statuses=( ${PIPESTATUS[@]} )
104 # we are told how many pipe statuses to consider...
106 # or if we are not told, then we make up a highest pipe status to consider.
107 if [ -z "$n" ]; then n=1; fi
108 # we always incorporate the highest level pipe status requested.
109 local accumulator=${preserved_statuses[$n]}
110 for (( looper = $n - 1; looper >= 0; looper-- )); do
111 #echo accumulator has $accumulator
112 #echo "loop at index [$looper]"
113 # math exercise below is to ensure that we won't overflow 255 limit on
114 # return values. we add about half of each return value being considered
115 # (accumulator and current pipe return) to make the "real" return value.
116 # this of course blows away mathematical comparisons for the return value
117 # in the future (unless all but one pipe status is zero).
118 # also, below, the addition of 1 below ensures that an error value of
119 # 1 cannot turn into a 0 when we do integer math division by 2.
120 combined=$(( (accumulator + ${preserved_statuses[$looper]} + 1) / 2 ))
121 #echo combined calced as $combined
123 # now push our new smooshed result into the accumulator. we'll merge it
124 # again if we have to keep looping.
125 accumulator=$combined
127 # signal the exit value.
128 ( exit $accumulator )
131 #hmmm: pretty handy general function, but horrible name here.
132 # ploop: sets the highest non-zero exit value possible.
133 function ploop() { (exit 255); return $?; }
137 # attempts to find the script file or at least the defition for a command
138 # given the command name. if it's an alias or a
139 # if the alias points directly at a script file, then that will be printed to stdout.
142 local command_name="$1"; shift
143 # first test checks whether it's a function or not. we have a special
144 # check here, since this actually does generate some text using our
145 # alias finding pattern below, but it's gibberish. better to rule this
147 maybe_found="$(type "$command_name" 2>/dev/null | grep -i "is a function")"
148 if [ ! -z "$maybe_found" ]; then
149 # we found a function, so just dump out its definition.
150 echo "$(type "$command_name")"
153 # now try searching for a simple alias.
154 maybe_found="$(type "$command_name" 2>/dev/null | sed -n -r -e "s/^.*is aliased to .[^ ]+ (.*).$/\1/p")"
155 if [ -z "$maybe_found" ]; then
156 #wtf? maybe_found="$(alias "$command_name" 2>/dev/null | sed -n -r -e "s/^.*is aliased to .[^ ]+ (.*).$/\1/p")"
157 #wtf2 if [ -z "$maybe_found" ]; then
158 # last ditch effort is to just show what "type" outputs.
159 maybe_found="$(type "$command_name")"
167 function fm_username()
169 # see if we can get the user name from the login name. oddly this sometimes doesn't work.
170 local custom_user="$(logname 2>/dev/null)"
171 if [ -z "$custom_user" ]; then
172 # try the normal unix user variable.
173 custom_user="$(sanitized_username)"
175 if [ -z "$custom_user" ]; then
176 # try the windows user variable.
177 custom_user="$USERNAME"
184 # displays the value of a variable in bash friendly format.
189 local varname="$1"; shift
190 if [ -z "$varname" ]; then
194 if is_alias "$varname"; then
195 #echo found $varname is alias
196 local tmpfile="$(mktemp $TMP/aliasout.XXXXXX)"
197 alias $varname | sed -e 's/.*=//' >$tmpfile
198 echo "alias $varname=$(cat $tmpfile)"
200 elif [ -z "${!varname}" ]; then
201 echo "$varname undefined"
203 if is_array "$varname"; then
204 #echo found $varname is array var
206 eval temparray="(\${$varname[@]})"
207 echo "$varname=(${temparray[@]})"
208 #hmmm: would be nice to print above with elements enclosed in quotes, so that we can properly
209 # see ones that have spaces in them.
211 #echo found $varname is simple
212 echo "$varname=${!varname}"
221 # when passed a list of things, this will return the unique items from that list as an echo.
224 # do the uniquification: split the space separated items into separate lines, then
225 # sort the list, then run the uniq tool on the list. results will be packed back onto
226 # one line when invoked like: local fredlist="$(uniquify a b c e d a e f a e d b)"
227 echo $* | tr ' ' '\n' | sort | uniq
230 # sets the variable in parameter 1 to the value in parameter 2, but only if
231 # that variable was undefined.
232 function set_var_if_undefined()
234 local var_name="$1"; shift
235 local var_value="$1"; shift
236 if [ -z "${!var_name}" ]; then
237 eval export $var_name="$var_value"
243 function success_sound()
245 if [ ! -z "$CLAM_FINISH_SOUND" ]; then
246 bash $FEISTY_MEOW_SCRIPTS/multimedia/sound_play.sh "$CLAM_FINISH_SOUND"
250 function error_sound()
252 if [ ! -z "$CLAM_ERROR_SOUND" ]; then
253 bash $FEISTY_MEOW_SCRIPTS/multimedia/sound_play.sh "$CLAM_ERROR_SOUND"
259 # echoes the maximum number of columns that the terminal supports. usually
260 # anything you print to the terminal with length less than (but not equal to)
261 # maxcols will never wrap.
262 function get_maxcols()
264 # calculate the number of columsn in the terminal.
265 local cols=$(stty size 2>/dev/null | awk '{print $2}')
271 # checks the result of the last command that was run, and if that failed,
272 # then this complains and exits from bash. the function parameters are
273 # used as the message to print as a complaint.
274 function exit_on_error()
276 if [ $? -ne 0 ]; then
277 echo -e "\n\nan important action failed and this script will stop:\n\n$*\n\n*** Exiting script..."
283 # like exit_on_error, but will keep going after complaining.
284 function continue_on_error()
286 if [ $? -ne 0 ]; then
287 echo -e "\n\na problem occurred, but we can continue:\n\n$*\n\n=> Continuing script..."
294 # accepts any number of arguments and outputs them to the feisty meow event log.
295 function log_feisty_meow_event()
297 echo -e "$(timestamper)-- $(sanitized_username)@$(hostname): $*" >> "$FEISTY_MEOW_EVENT_LOG"
302 # wraps secure shell with some parameters we like, most importantly to enable X forwarding.
306 save_terminal_title # remember the current terminal title.
307 /usr/bin/ssh -C "${args[@]}"
308 #hmmm: removed -Y flag because considered dangerous to trust remote hosts to not abuse our X session.
309 restore_terminal_title
312 # this version of ssh preserves the use of the -Y flag for when X forwarding is needed.
316 save_terminal_title # remember the current terminal title.
317 /usr/bin/ssh -Y "${args[@]}"
318 restore_terminal_title
323 # locates a process given a search pattern to match in the process list.
325 # + the -u flag specifies a user name, e.g. "-u joe", which causes only
326 # the processes of that user "joe" to be considered.
328 # + the -x flag specifies a pattern to exclude from the list, e.g. "-x pszap.sh"
329 # would ignore any processes that mention the phrase "pszap.sh".
332 # default user flag is for all users.
333 local excluder="ScrengeflebbitsAPhraseWeNeverExpecttomatchanythingYO298238"
334 # for our default, pick an exclusion string we would never match.
337 while [ $found_flag -eq 1 ]; do
338 # reset our sentinel now that we're safely in our loop.
341 # save the first argument, since we're going to shift the args.
343 if [ "$arg1" == "-u" ]; then
344 # handle the user flag.
346 #echo "found a -u parm and user=$2"
347 found_flag=1 # signal that we found one.
348 # skip these two arguments, since we've consumed them.
351 elif [ "$arg1" == "-x" ]; then
352 # handle the exclusion flag.
354 #echo "found a -x parm and excluder=$excluder"
355 found_flag=1 # signal that we found one.
356 # skip these two arguments, since we've consumed them.
362 # now that we've yanked any flags out, we can pull the rest of the
363 # arguments in as patterns to seek in the process list.
364 local -a patterns=("${@}")
366 #echo patterns list is: "${patterns[@]}"
369 local PID_DUMP="$(mktemp "$TMP/zz_pidlist.XXXXXX")"
372 if [ "$OS" == "Windows_NT" ]; then
373 # gets cygwin's (god awful) ps to show windoze processes also.
374 local EXTRA_DOZER_FLAGS="-W"
375 # pattern to use for peeling off the process numbers.
376 # local pid_finder_cmd="awk -- '{ print \$4; }'"
379 # flags which clean up the process listing output on unixes.
380 # apparently cygwin doesn't count as a type of unix, because their
381 # crummy specialized ps command doesn't support normal ps flags.
382 local EXTRA_UNIX_FLAGS="-o pid,args"
383 # pattern to use for peeling off the process numbers.
384 # local pid_finder_cmd="sed -n -e \\'s/^[[:space:]]*\([0-9][0-9]*\).*$/\\\\1/p\\'"
385 #echo pidfinder: $pid_finder_cmd
386 # local pid_finder_cmd="awk -- '{ print \$1; }'"
390 /bin/ps $EXTRA_DOZER_FLAGS $EXTRA_UNIX_FLAGS $user_flag | tail -n +2 >$PID_DUMP
392 #echo got all this stuff in the pid dump file:
396 # search for the pattern the user wants to find, and just pluck the process
397 # ids out of the results.
399 for i in "${patterns[@]}"; do
400 #echo "pattern curr is '$i'"
401 PIDS_SOUGHT+=($(cat $PID_DUMP \
403 | grep -v "$excluder" \
404 | awk -- "{ print \$${field_number}; }" ))
407 #echo pids sought list became:
408 #echo "${PIDS_SOUGHT[@]}"
411 if [ ${#PIDS_SOUGHT[*]} -ne 0 ]; then
412 local PIDS_SOUGHT2=$(printf -- '%s\n' ${PIDS_SOUGHT[@]} | sort | uniq)
414 PIDS_SOUGHT=${PIDS_SOUGHT2[*]}
415 echo ${PIDS_SOUGHT[*]}
420 # finds all processes matching the pattern specified and shows their full
421 # process listing (whereas psfind just lists process ids).
424 echo "psa finds processes by pattern, but there was no pattern on the command line."
427 local -a patterns=("${@}")
428 p=$(psfind "${patterns[@]}")
434 if [ "${patterns[0]}" == "-u" ]; then
435 # void the two elements with that user flag so we don't use them as patterns.
436 unset patterns[0] patterns[1]=
440 echo "Processes matching ${patterns[@]}..."
442 if [ -n "$IS_DARWIN" ]; then
445 # only print the header the first time.
446 if [ -z "$fuzil_sentinel" ]; then
449 ps $i -w -u | sed -e '1d'
454 # cases besides mac os x's darwin.
455 if [ "$OS" == "Windows_NT" ]; then
456 # special case for windows.
459 #hmmm: currently not working right for windows cygwin. we're getting proper
460 # winpids out of the list now, but not able to use them in ps?
461 # should i be keeping the weirdo pid that we were getting in column 1 and
462 # use that, except when talking to taskkill?
463 # need further research.
464 ps -W -p $curr | tail -n +2
467 # normal OSes can handle a nice simple query.
475 #hmmm: holy crowbars, this is an old one. do we ever still have any need of it?
476 # an unfortunately similarly named function to the above 'ps' as in process
477 # methods, but this 'ps' stands for postscript. this takes a postscript file
478 # and converts it into pcl3 printer language and then ships it to the printer.
479 # this mostly makes sense for an environment where one's default printer is
480 # pcl. if the input postscript causes ghostscript to bomb out, there has been
481 # some good success running ps2ps on the input file and using the cleaned
482 # postscript file for printing.
483 function ps2pcl2lpr() {
485 gs -sDEVICE=pcl3 -sOutputFile=- -sPAPERSIZE=letter "$i" | lpr -l
491 #hmmm: ugly absolute path here.
493 restore_terminal_title
496 # switches from a /X/path form to an X:/ form. this also processes cygwin paths.
497 function unix_to_dos_path() {
498 # we usually remove dos slashes in favor of forward slashes.
500 if [[ ! "$OS" =~ ^[Ww][iI][nN] ]]; then
501 # fake this value for non-windows (non-cygwin) platforms.
504 # for cygwin, we must replace the /home/X path with an absolute one, since cygwin
505 # insists on the /home form instead of /c/cygwin/home being possible. this is
506 # super frustrating and nightmarish.
507 DOSSYHOME="$(cygpath -am "$HOME")"
510 # if [ ! -z "$SERIOUS_SLASH_TREATMENT" ]; then
511 # # unless this flag is set, in which case we force dos slashes.
512 # echo "$1" | sed -e "s?^$HOME?$DOSSYHOME?g" | sed -e 's/\\/\//g' | sed -e 's/\/cygdrive//' | sed -e 's/\/\([a-zA-Z]\)\/\(.*\)/\1:\/\2/' | sed -e 's/\//\\/g'
514 echo "$1" | sed -e "s?^$HOME?$DOSSYHOME?g" | sed -e 's/\\/\//g' | sed -e 's/\/cygdrive//' | sed -e 's/\/\([a-zA-Z]\)\/\(.*\)/\1:\/\2/'
518 # # switches from an X:/ form to a /cygdrive/X/path form. this is only useful
519 # # for the cygwin environment currently.
520 # function dos_to_unix_path() {
521 # # we always remove dos slashes in favor of forward slashes.
522 ##old: echo "$1" | sed -e 's/\\/\//g' | sed -e 's/\([a-zA-Z]\):\/\(.*\)/\/\1\/\2/'
523 # echo "$1" | sed -e 's/\\/\//g' | sed -e 's/\([a-zA-Z]\):\/\(.*\)/\/cygdrive\/\1\/\2/'
526 # returns a successful value (0) if this system is debian or ubuntu.
527 function debian_like() {
528 # decide if we think this is debian or ubuntu or a variant.
531 ! -z "$(grep -i debian /etc/issue)" -o \
532 ! -z "$(grep -i ubuntu /etc/issue)" -o \
533 ! -z "$(grep -i 'Pop._OS' /etc/issue)" \
534 ]; then echo 1; else echo 0; fi)
535 if [ $DEBIAN_LIKE -eq 1 ]; then
536 # success; this is debianish.
539 # this seems like some other OS.
544 # this function wraps the normal sudo by ensuring we replace the terminal
545 # label before we launch what they're passing to sudo. we also preserve
546 # specific variables that enable the main user's ssh credentials to still
547 # be relied on for ssh forwarding, even if the '-i' flag is passed to cause
548 # a fresh shell (which normally doesn't get the launching user's environment
553 # hoist our X authorization info in case environment is passed along;
554 # this can allow root to use our display to show X.org windows.
555 if [ -z "$IMPORTED_XAUTH" -a ! -z "$DISPLAY" ]; then
556 export IMPORTED_XAUTH="$(xauth list $DISPLAY | head -n 1 | awk '{print $3}')"
557 local REMOVE_IMP_XAUTH=true
560 # launch sudo with just the variables we want to reach the other side.
562 varmods+="OLD_HOME=$HOME "
563 if [ ! -z "$IMPORTED_XAUTH" ]; then varmods+="IMPORTED_XAUTH=$IMPORTED_XAUTH "; fi
564 if [ ! -z "$SSH_AUTH_SOCK" ]; then varmods+="SSH_AUTH_SOCK=$SSH_AUTH_SOCK "; fi
565 /usr/bin/sudo $varmods "$@"
568 # take the xauth info away again if it wasn't set already.
569 if [ ! -z "$REMOVE_IMP_XAUTH" ]; then
572 restore_terminal_title
576 # trashes the .#blah files that cvs and subversion leave behind when finding conflicts.
577 # this kind of assumes you've already checked them for any salient facts.
578 function clean_cvs_junk() {
580 find $i -follow -type f -iname ".#*" -exec perl $FEISTY_MEOW_SCRIPTS/files/safedel.pl {} ";"
584 # overlay for nechung binary so that we can complain less grossly about it when it's missing.
586 local wheres_nechung=$(whichable nechung)
587 if [ -z "$wheres_nechung" ]; then
588 echo "The nechung oracle program cannot be found. You may want to consider"
589 echo "rebuilding the feisty meow applications with this command:"
590 echo "bash $FEISTY_MEOW_SCRIPTS/generator/produce_feisty_meow.sh"
597 # recreates all the generated files that the feisty meow scripts use.
598 function regenerate() {
599 # do the bootstrapping process again.
601 echo "$(timestamper)regenerating feisty meow script environment."
602 bash $FEISTY_MEOW_SCRIPTS/core/reconfigure_feisty_meow.sh
604 # force a full reload by turning off sentinel variables and methods.
605 unset -v CORE_VARIABLES_LOADED FEISTY_MEOW_LOADING_DOCK USER_CUSTOMIZATIONS_LOADED \
607 unalias CORE_ALIASES_LOADED &>/dev/null
608 unset -f function_sentinel
610 # reuse the original path if we can.
611 if [ ! -z "$FEISTY_MEOW_ORIGINAL_PATH" ]; then
612 export PATH="$FEISTY_MEOW_ORIGINAL_PATH"
615 # reload feisty meow environment in current shell.
616 log_feisty_meow_event "reloading the feisty meow scripts for $(sanitized_username) in current shell."
617 source "$FEISTY_MEOW_SCRIPTS/core/launch_feisty_meow.sh"
618 # run nechung oracle to give user a new fortune.
620 restore_terminal_title
623 # merges a set of custom scripts into the feisty meow environment. can be
624 # passed a name to use as the custom scripts source folder (found on path
625 # $FEISTY_MEOW_SCRIPTS/customize/{name}), or it will try to guess the name
626 # by using the login name.
627 function recustomize()
629 local custom_user="$1"; shift
630 if [ -z "$custom_user" ]; then
631 # default to login name if there was no name provided.
632 custom_user="$(fm_username)"
633 # we do intend to use the login name here to get the login name and to ignore
634 # if the user has sudo root access; we don't want to provide a custom
637 # chop off any email address style formatting to leave just the name.
638 custom_user="$(echo "$custom_user" | cut -f1 -d'@')"
642 if [ ! -d "$FEISTY_MEOW_SCRIPTS/customize/$custom_user" ]; then
643 echo -e "the customization folder for '$custom_user' is missing:
645 $FEISTY_MEOW_SCRIPTS/customize/$custom_user
647 we will skip recustomization, but these other customizations are available:
649 # a little tr and sed magic to fix the carriage returns into commas.
650 local line="$(find $FEISTY_MEOW_SCRIPTS/customize -mindepth 1 -maxdepth 1 -type d -exec basename {} ';' | tr '\n' '&' | sed 's/&/, /g' | sed -e 's/, $//')"
651 # make the line feeds and carriage returns manageable with tr.
652 # convert the ampersand, our weird replacement for EOL, with a comma + space in sed.
653 # last touch with sed removes the last comma.
658 # recreate the feisty meow loading dock.
659 regenerate >/dev/null
661 # jump into the loading dock and make our custom link.
662 pushd "$FEISTY_MEOW_LOADING_DOCK" &>/dev/null
663 if [ -h custom ]; then
664 # there's an existing link, so remove it.
667 # make sure we cleaned up the area before we re-link.
668 if [ -h custom -o -d custom -o -f custom ]; then
670 Due to an over-abundance of caution, we are not going to remove an unexpected
671 'custom' object found in the file system. This object is located in the
672 feisty meow loading dock here: $(pwd)
673 And here is a description of the rogue 'custom' object:
677 If you are pretty sure that this is just a remnant of an older approach in
678 feisty meow, where we copied the custom directory rather than linking it
679 (and it most likely is just such a bit of cruft of that nature), then please
680 remove that old remnant 'custom' item, for example by saying:
681 /bin/rm -rf \"custom\" ; popd
682 Sorry for the interruption, but we want to make sure this removal wasn't
683 automatic if there is even a small amount of doubt about the issue."
687 # create the custom folder as a link to the customizations.
688 ln -s "$FEISTY_MEOW_SCRIPTS/customize/$custom_user" custom
692 # now take into account all the customizations by regenerating the feisty meow environment.
695 restore_terminal_title
698 # generates a random password where the first parameter is the number of characters
699 # in the password (default 20) and the second parameter specifies whether to use
700 # special characters (1) or not (0).
701 # found function at http://legroom.net/2010/05/06/bash-random-password-generator
702 function random_password()
704 [ "$2" == "0" ] && CHAR="[:alnum:]" || CHAR="[:graph:]"
705 cat /dev/urandom | tr -cd "$CHAR" | head -c ${1:-32}
709 function add_cygwin_drive_mounts() {
710 for i in c d e f g h q z ; do
711 #hmmm: improve this by not adding the link if already there, or if the drive is not valid.
712 ln -s /cygdrive/$i $i
716 ############################
718 # takes a file to modify, and then it will replace any occurrences of the
719 # pattern provided as the second parameter with the text in the third
721 function replace_pattern_in_file()
723 local file="$1"; shift
724 local pattern="$1"; shift
725 local replacement="$1"; shift
726 if [ -z "$file" -o -z "$pattern" -o -z "$replacement" ]; then
727 echo "replace_pattern_in_file: needs a filename, a pattern to replace, and the"
728 echo "text to replace that pattern with."
731 sed -i -e "s%$pattern%$replacement%g" "$file"
734 # similar to replace_pattern_in_file, but also will add the new value
735 # when the old one did not already exist in the file.
736 function replace_if_exists_or_add()
738 local file="$1"; shift
739 local phrase="$1"; shift
740 local replacement="$1"; shift
741 if [ -z "$file" -o ! -f "$file" -o -z "$phrase" -o -z "$replacement" ]; then
742 echo "replace_if_exists_or_add: needs a filename, a phrase to replace, and the"
743 echo "text to replace that phrase with."
746 grep "$phrase" "$file" >/dev/null
747 # replace if the phrase is there, otherwise add it.
748 if [ $? -eq 0 ]; then
749 replace_pattern_in_file "$file" "$phrase" "$replacement"
751 # this had better be the complete line.
752 echo "$replacement" >>"$file"
756 ############################
758 # finds a variable (first parameter) in a particular property file
759 # (second parameter). the expected format for the file is:
761 function seek_variable()
763 local find_var="$1"; shift
764 local file="$1"; shift
765 if [ -z "$find_var" -o -z "$file" -o ! -f "$file" ]; then
766 echo -e "seek_variable: needs two parameters, firstly a variable name, and\nsecondly a file where the variable's value will be sought." 1>&2
771 if [ ${#line} -eq 0 ]; then continue; fi
772 # split the line into the variable name and value.
773 IFS='=' read -a assignment <<< "$line"
774 local var="${assignment[0]}"
775 local value="${assignment[1]}"
776 if [ "${value:0:1}" == '"' ]; then
777 # assume the entry was in quotes and remove them.
778 value="${value:1:$((${#value} - 2))}"
780 if [ "$find_var" == "$var" ]; then
786 # finds a variable (first parameter) in a particular XML format file
787 # (second parameter). the expected format for the file is:
788 # ... name="varX" value="valueX" ...
789 function seek_variable_in_xml()
791 local find_var="$1"; shift
792 local file="$1"; shift
793 if [ -z "$find_var" -o -z "$file" -o ! -f "$file" ]; then
794 echo "seek_variable_in_xml: needs two parameters, firstly a variable name, and"
795 echo "secondly an XML file where the variable's value will be sought."
800 if [ ${#line} -eq 0 ]; then continue; fi
801 # process the line to make it more conventional looking.
802 line="$(echo "$line" | sed -e 's/.*name="\([^"]*\)" value="\([^"]*\)"/\1=\2/')"
803 # split the line into the variable name and value.
804 IFS='=' read -a assignment <<< "$line"
805 local var="${assignment[0]}"
806 local value="${assignment[1]}"
807 if [ "${value:0:1}" == '"' ]; then
808 # assume the entry was in quotes and remove them.
809 value="${value:1:$((${#value} - 2))}"
811 if [ "$find_var" == "$var" ]; then
817 ############################
819 # goes to a particular directory passed as parameter 1, and then removes all
820 # the parameters after that from that directory.
821 function push_whack_pop()
823 local dir="$1"; shift
824 pushd "$dir" &>/dev/null
825 if [ $? -ne 0 ]; then echo failed to enter dir--quitting.; fi
826 rm -rf $* &>/dev/null
827 if [ $? -ne 0 ]; then echo received a failure code when removing.; fi
833 # new breed of definer functions goes here. still in progress.
835 # defines an alias and remembers that this is a new or modified definition.
836 # if the feisty meow codebase is unloaded, then so are all the aliases that
838 function define_yeti_alias()
840 # if alias exists already, save old value for restore,
841 # otherwise save null value for restore,
842 # have to handle unaliasing if there was no prior value of one
844 # add alias name to a list of feisty defined aliases.
846 #hmmm: first implem, just do the alias and get that working...
855 #hmmm: this points to an extended functions file being needed; not all of these are core.
857 # displays documentation in "md" formatted files.
860 local file="$1"; shift
861 pandoc "$file" | lynx -stdin
866 # just shows a separator line for the current console size, or uses the first
867 # parameter as the number of columns to expect. if a second parameter is provided,
868 # then that is used as the separator character(s).
872 if [ -z "$count" ]; then
873 count=$(($COLUMNS - 1))
876 # snag remaining paramters into the characters to show.
878 if [ -z "$characters" ]; then
882 #hmmm: works, but has flaw of disallowing spaces within the characters variable.
883 # local garptemp="$(printf '%*s' "$count")"
884 # local emission="${garptemp// /${characters}}"
886 local garptemp="$(dd if=/dev/zero bs="$count" count=1 2>/dev/null | tr '\0' 'Q')"
887 local emission="${garptemp//Q/${characters}}"
892 # alias for separator.
900 # count the number of sub-directories in a directory and echo the result.
901 function count_directories()
903 local subbydir="$1"; shift
904 numdirs="$(find "$subbydir" -mindepth 1 -maxdepth 1 -type d | wc -l)"
908 # takes a string and capitalizes just the first character. any capital letters in the remainder of
909 # the string are made lower case. the processed string is returned by an echo.
910 function capitalize_first_char()
912 local to_dromedary="$1"; shift
913 to_dromedary="$(tr '[:lower:]' '[:upper:]' <<< ${to_dromedary:0:1})$(tr '[:upper:]' '[:lower:]' <<< ${to_dromedary:1})"
917 # given a source path and a target path, this will make a symbolic link from
918 # the source to the destination, but only if the source actually exists.
919 function make_safe_link()
921 local src="$1"; shift
922 local target="$1"; shift
924 if [ -d "$src" ]; then
925 ln -s "$src" "$target"
926 exit_on_error "Creating symlink from '$src' to '$target'"
928 echo "Created symlink from '$src' to '$target'."
931 # pretty prints the json files provided as parameters.
932 function clean_json()
934 if [ -z "$*" ]; then return; fi
937 local file="$1"; shift
938 if [ -z "$file" ]; then break; fi
939 if [ ! -f "$file" ]; then "echo File '$file' does not exist."; continue; fi
940 temp_out="$TMP/$file.view"
941 cat "$file" | python -m json.tool > "$temp_out"
942 show_list+=($temp_out)
943 continue_on_error "pretty printing '$file'"
945 filedump "${show_list[@]}"
951 # only print our special headers or text fields.
955 grep -i "\"text\":\|^=.*" |
956 sed -e "s/\\\\r/$CR/g" -e "s/\\\\n/\\$LF/g"
961 # echoes the machine's hostname. can be used like so:
962 # local my_host=$(get_hostname)
963 function get_hostname()
965 # there used to be more variation in how to do this, but adopting mingw
966 # and cygwin tools really helped out.
967 local this_host=unknown
968 if [ "$OS" == "Windows_NT" ]; then
969 this_host=$(hostname)
970 elif [ ! -z "$(echo $MACHTYPE | grep apple)" ]; then
971 this_host=$(hostname)
972 elif [ ! -z "$(echo $MACHTYPE | grep suse)" ]; then
973 this_host=$(hostname --long)
974 elif [ -x "$(whichable hostname)" ]; then
975 this_host=$(hostname)
980 # makes sure that the provided "folder" is a directory and is writable.
981 function test_writable()
983 local folder="$1"; shift
984 if [ ! -d "$folder" -o ! -w "$folder" ]; then return 1; fi
989 # examines the provided "folder" name to test whether it is a directory
990 # and is writable. zero (success) is returned if the folder is found.
991 # if the folder is not found, and the second parameter passed is "up",
992 # then the folder is sought recursively in higher directory levels.
993 # if the directory is found anywhere below the root of the filesystem,
994 # then zero (success) is returned.
995 # in either case of the directory being found, the successful path location
996 # is emitted to stdout. (note that this may emit a relative path, if the
997 # "folder" is a relative path.)
998 # if the directory is not found by any means, then a non-zero failure
1000 # if the "folder" is a slash-less string, then it is treated as a simple
1001 # directory name to be sought in the current directory or above. but if the
1002 # folder has a full path with slashes, then the most basenamey directory
1003 # component is considered the directory to locate.
1004 function seek_writable()
1006 #hmmm: ever any use to search downwards? sure there is. ==> currently not supported, but should be.
1007 local folder="$1"; shift
1008 local recurse_up="$1"; shift
1010 # handle a folder with no path elements by jamming current dir into it.
1011 if [[ $string != *"/"* ]]; then
1012 local curdir="$( \cd "$(\dirname ".")" && /bin/pwd )"
1013 #echo "curdir is '$curdir'"
1014 folder="${curdir}/${folder}"
1015 #echo "folder is now '$folder'"
1018 # default for us is to not do any directory recursion...
1021 # magical conversion to lower case in bash.
1022 recurse_up="${recurse_up,,}"
1024 # check if they actually wanted recursion.
1025 if [ "$recurse_up" == "up" ]; then
1026 # yes, they do want to loop upwards if the relevant revision control
1027 # folder is not present right here.
1031 #hmmm: recursion bit below maybe has some useful reusable code, should be its own func,
1033 # pessimistic default assumes that we will not find it...
1034 # (here, zero means false, so this is not a bash return value.)
1035 local directory_present=0
1037 local mod_folder="$folder"
1038 #echo "mod folder before loop is '$mod_folder'"
1040 while [ ! -z "$mod_folder" ]; do
1042 # check for existence and writeability of the folder.
1043 if [ ! -d "$mod_folder" -o ! -w "$mod_folder" ]; then
1044 # evidence suggests the desired folder really isn't here at this location.
1046 #echo "mod folder does not exist at this level."
1049 #echo "mod folder DOES exist at this level."
1052 # check if we should be looping at all; if we are not going recursive,
1053 # then it's time to produce an exit value.
1054 if [ -z "$loop_up" -o $directory_present -eq 1 ]; then
1055 #echo "exiting now since not looping or found dir"
1056 # let them know where we found the file.
1058 # invert the sense of the directory presence to provide a bash return value.
1059 return $(( ! directory_present ))
1062 local base="$(basename "$mod_folder")"
1063 local parent="$(dirname "$mod_folder")"
1064 #echo parent is $parent
1065 local parents_parent="$(dirname "$parent")"
1066 #echo parents_parent is $parents_parent
1067 if [ "$parent" == "$parents_parent" ]; then
1068 # have to bail now, since we've reached the top of the filesystem.
1072 # reconstruct the path without the current unsuccessful parent directory.
1073 # basically, if mom says no, ask grandma.
1074 mod_folder="${parents_parent}/${base}"
1075 #echo "mod folder after reconstruction is '$mod_folder'"
1078 # if we got out of the loop to here, then this is a lack of success.
1084 # given a filename and a string to seek and a number of lines, then this
1085 # function will remove the first occurrence of a line in the file that
1086 # matches the string, and it will also axe the next N lines as specified.
1087 function create_chomped_copy_of_file()
1089 local filename="$1"; shift
1090 local seeker="$1"; shift
1091 local numlines=$1; shift
1093 #echo into create_chomped_copy...
1094 #var filename seeker numlines
1096 # make a backup first, oy.
1097 \cp -f "$filename" "/tmp/$(basename ${filename}).bkup-${RANDOM}"
1098 exit_on_error "backing up file: $filename"
1100 # make a temp file to write to before we move file into place in bind.
1101 local new_version="/tmp/$(basename ${filename}).bkup-${RANDOM}"
1102 rm -f "$new_version"
1103 exit_on_error "cleaning out new version of file from: $new_version"
1109 # don't bother looking at the lines if we're already in skip mode.
1110 if [[ $skip_count == 0 ]]; then
1111 # find the string they're seeking.
1112 if [[ ! "$line" =~ .*${seeker}.* ]]; then
1114 echo "$line" >> "$new_version"
1116 # a match! start skipping. we will delete this line and the next N lines.
1118 #echo first skip count is now $skip_count
1122 # we're already skipping. let's keep going until we hit the limit.
1124 #echo ongoing skip count is now $skip_count
1125 if (( $skip_count > $numlines )); then
1126 echo "Done skipping, and back to writing output file."
1132 #echo file we created looks like this:
1135 if [ ! -z "$found_any" ]; then
1136 # put the file back into place under the original name.
1137 \mv "$new_version" "$filename"
1138 exit_on_error "moving the new version into place in: $filename"
1140 # cannot always be considered an error, but we can at least gripe.
1141 echo "Did not find any matches for seeker '$seeker' in file: $filename"
1147 # space 'em all: fixes naming for all of the files of the appropriate types
1148 # in the directories specified. we skip any file with a dot in front, to
1149 # respect their hidden nature. currently the set of files we'll rename is
1150 # very boutique; it's in this function, and just happens to be the types of
1151 # files we work with a lot.
1152 function spacemall() {
1153 local -a dirs=("${@}")
1154 if [ ${#dirs[@]} -eq 0 ]; then
1158 local charnfile="$(mktemp $TMP/zz_charn.XXXXXX)"
1159 #hmmm: any way to do the below more nicely or reusably?
1160 #hmmm: yes! a variable with a list of files that are considered TEXT_FILE_EXTENSIONS or something like that.
1161 #hmmm: yes continued! also a variable for BINARY_FILE_EXTENSIONS to avoid those, where we need to in other scripts.
1162 #hmmm: wait, we actually have a mix here, since this is a renaming function and not a searching function; get it straight!
1163 #hmmm: would the composition of those two types of extensions cover all the files i want to rename? they have to be "important".
1164 find "${dirs[@]}" -follow -maxdepth 1 -mindepth 1 -type f -and -not -iname ".[a-zA-Z0-9]*" | \
1166 "csv\|doc\|docx\|eml\|html\|ics\|jpeg\|jpg\|m4a\|mov\|mp3\|mp4\|odp\|ods\|odt\|pdf\|png\|ppt\|pptx\|rtf\|txt\|vsd\|vsdx\|wav\|webp\|xls\|xlsx\|xml\|zip" | \
1167 sed -e 's/^/"/' | sed -e 's/$/"/' | \
1168 xargs bash "$FEISTY_MEOW_SCRIPTS/files/spacem.sh"
1169 # drop the temp file now that we're done.
1175 # tty relevant functions...
1177 # keep_awake: sends a message to the screen from the background.
1178 function keep_awake()
1180 # just starts the keep_awake process in the background.
1181 bash $FEISTY_MEOW_SCRIPTS/tty/keep_awake_process.sh &
1182 # this should leave the job running as %1 or a higher number if there
1183 # are pre-existing background jobs.
1188 # site avenger functions...
1192 THISDIR="$FEISTY_MEOW_SCRIPTS/site_avenger"
1193 source "$FEISTY_MEOW_SCRIPTS/site_avenger/shared_site_mgr.sh"
1199 # you have hit the borderline functional zone...
1201 #hmmm: not really doing anything yet; ubuntu seems to have changed from pulseaudio in 17.04?
1202 # restarts the sound driver.
1203 function fix_sound_driver() {
1204 # stop bash complaining about blank function body.
1207 # sudo service alsasound restart
1208 #elif pulse something
1209 # sudo pulseaudio -k
1210 # sudo pulseaudio -D
1212 # something else...?
1217 # ...and here's the end of the borderline functional zone.
1221 # NOTE: no more function definitions are allowed after this point.
1223 function function_sentinel()
1228 if [ ! -z "$DEBUG_FEISTY_MEOW" ]; then echo "feisty meow function definitions done."; fi
1232 # tests for our functions defined above.
1234 # change this to '1' to run the tests.
1237 if [ $run_tests != 0 ]; then
1238 echo running tests on set_var_if_undefined.
1240 set_var_if_undefined flagrant forknordle
1241 exit_on_error "testing if defined variable would be whacked"
1242 if [ $flagrant != petunia ]; then
1243 echo set_var_if_undefined failed to leave the test variable alone
1246 unset bobblehead_stomper
1247 set_var_if_undefined bobblehead_stomper endurance
1248 if [ $bobblehead_stomper != endurance ]; then
1249 echo set_var_if_undefined failed to set a variable that was not defined yet
1254 #echo exit value after ploop is $?
1255 #ploop | ploop | ploop | ploop
1256 #echo pipes after plooping: 0=${PIPESTATUS[0]} 1=${PIPESTATUS[1]} 2=${PIPESTATUS[2]} 3=${PIPESTATUS[3]}
1258 function test_combine_pipe_returns()
1261 CALL=(combine_pipe_returns 1)
1265 echo "call '${CALL[@]}' => $retval"
1268 CALL=(combine_pipe_returns 0)
1272 echo "call '${CALL[@]}' => $retval"
1275 CALL=(combine_pipe_returns 2)
1276 duckit | arghmore | grubblez
1279 echo "call '${CALL[@]}' => $retval"
1282 CALL=(combine_pipe_returns 3)
1283 ploop | ploop | ploop | ploop
1286 echo "call '${CALL[@]}' => $retval"
1289 CALL=(combine_pipe_returns 3)
1290 flarkas=$(ploop | ploop | ploop | ploop)
1293 echo "embedded quoted call '${CALL[@]}' => $retval"
1294 echo "and the flarkas string got: '$flarkas'"
1299 # now run the fancy pipe tests. hope they do not leak.
1300 echo running tests on combine_pipe_returns.
1301 test_combine_pipe_returns
1303 # more tests go here...