├── .github └── workflows │ └── makefile.yml ├── .gitignore ├── DONE ├── LICENSE ├── Makefile ├── Makefile-template ├── Makefile.inc ├── README.md ├── TODO ├── arg-utils.sh ├── bash-check.sh ├── bash-lib.sh ├── calendar-utils.sh ├── calfaq ├── .gitignore ├── Makefile ├── Readme.txt ├── calfaq.c ├── calfaq.h ├── calfaq.zip ├── calfunc.c └── calfunc.man ├── cli-template.sh ├── date-utils.sh ├── demo-date-parser.sh ├── experiments ├── check-bash-aarrays.sh ├── mort.sh ├── mort2.sh └── test-args.sh ├── gen-test-dates.sh ├── generate-prompt-colors ├── hash-utils.sh ├── help-util.sh ├── list-utils.sh ├── maybe-install-bash-lib ├── misc └── calendar.l ├── no-test-sync-files.sh ├── option-utils.sh ├── prompt-colors.txt ├── real-utils.sh ├── reset-util-vars.sh ├── run-utils.sh ├── sh-utils.sh ├── sync-files.sh ├── t.sh ├── t1.sh ├── t2.sh ├── t3.sh ├── talk-utils.sh ├── test-all ├── test-arg-utils.sh ├── test-date-utils.sh ├── test-dates.dat ├── test-hash-utils.sh ├── test-list-utils.sh ├── test-real-utils.sh ├── test-sh-utils.sh ├── test-talk-utils.sh ├── test-template.sh ├── test-text-utils.sh ├── test-time-utils.sh ├── test-utils.sh ├── test ├── hash1_init_test.err.ref ├── hash1_init_test.out.ref ├── hash1a_init_test.err.ref ├── hash1a_init_test.out.ref ├── hash1b_init_test.err.ref ├── hash1b_init_test.out.ref ├── hash2_info.err.ref ├── hash2_info.out.ref ├── hash2_init_test.err.ref ├── hash2_init_test.out.ref ├── hash3_info.err.ref ├── hash3_info.out.ref ├── join_list_3.err.ref ├── join_list_3.out.ref ├── join_list_3and.err.ref ├── join_list_3and.out.ref ├── join_list_3nl.err.ref ├── join_list_3nl.out.ref ├── join_list_3or.err.ref ├── join_list_3or.out.ref ├── join_list_3sp.err.ref ├── join_list_3sp.out.ref ├── join_list_3tab.err.ref ├── join_list_3tab.out.ref ├── join_list_words.err.ref ├── join_list_words.out.ref ├── join_list_words_nowrap.err.ref ├── join_list_words_nowrap.out.ref ├── list_add_help.err.ref ├── list_add_help.out.ref ├── list_add_nohelp.err.ref ├── list_add_nohelp.out.ref ├── list_add_once_help.err.ref ├── list_add_once_help.out.ref ├── list_get2_help.err.ref ├── list_get2_help.out.ref ├── list_get_help.err.ref ├── list_get_help.out.ref ├── list_help.err.ref ├── list_help.out.ref ├── list_init_help.err.ref ├── list_init_help.out.ref ├── list_init_nohelp.err.ref ├── list_init_nohelp.out.ref ├── list_item2_help.err.ref ├── list_item2_help.out.ref ├── list_item_help.err.ref ├── list_item_help.out.ref ├── list_push2_help.err.ref ├── list_push2_help.out.ref ├── list_push_help.err.ref ├── list_push_help.out.ref ├── map_list_joinstr.err.ref ├── map_list_joinstr.out.ref ├── phout1.err.ref ├── phout1.out.ref ├── phout2.err.ref ├── phout2.out.ref ├── phout3.err.ref ├── phout3.out.ref ├── phout4.err.ref ├── phout4.out.ref ├── phout5.err.ref ├── phout5.out.ref ├── plout1.err.ref ├── plout1.out.ref ├── plout2.err.ref ├── plout2.out.ref ├── plout3.err.ref ├── plout3.out.ref ├── plout4.err.ref ├── plout4.out.ref ├── plout5.err.ref ├── plout5.out.ref ├── plouta1.err.ref ├── plouta1.out.ref ├── plouta2.err.ref ├── plouta2.out.ref ├── plouta3.err.ref ├── plouta3.out.ref ├── plouta4.err.ref ├── plouta4.out.ref ├── plouta5.err.ref ├── plouta5.out.ref ├── plouta6.err.ref ├── plouta6.out.ref ├── real_help.err.ref ├── real_help.out.ref ├── talk1.err.ref ├── talk1.out.ref ├── talk2.err.ref ├── talk2.out.ref ├── talk3.err.ref ├── talk3.out.ref ├── talk_functions.err.ref ├── talk_functions.out.ref ├── talk_w_2_args.err.ref ├── talk_w_2_args.out.ref ├── talk_w_2_args2.err.ref ├── talk_w_2_args2.out.ref ├── talkf_basic_test.err.ref ├── talkf_basic_test.out.ref ├── vtalkf_basic_test.err.ref └── vtalkf_basic_test.out.ref ├── text-utils.sh ├── time-utils.sh └── words /.github/workflows/makefile.yml: -------------------------------------------------------------------------------- 1 | name: Makefile CI for bash-lib 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - name: Run check 18 | run: make tests 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | prompt-colors.sh* 2 | test/*.out 3 | test/*.err 4 | -------------------------------------------------------------------------------- /DONE: -------------------------------------------------------------------------------- 1 | # DONE 2 | # Copyright 2006-2022 Alan K. Stebbens 3 | # 4 | # A change log for the bash-lib 5 | 6 | 2022/09/10 7 | * text-utils.sh: 8 | - fix missing __split_str 9 | * test-text-utils.sh: 10 | - add tests for it 11 | * all files: 12 | - update copyrights 13 | * Makefile: 14 | - fix calfunc dependency 15 | * test-date-utils.sh: 16 | - add days_between tests 17 | * calfaq/Makefile: 18 | - non-Darwin compiles need help 19 | 20 | 2022/07/17 21 | * Makefile: 22 | - make invocation path explicitly relative 23 | * hash-utils.sh: 24 | - sort the keys for printing on hash_print 25 | 26 | 2015/05/31 27 | * sh-utils.sh: 28 | - refactored into talk-utils, arg-utils, run-utils 29 | * Makefile: 30 | - added the newly refactored modules 31 | * test-run-utils.sh: 32 | - added new tests 33 | * test-talk-utils.sh: 34 | - extracted tests from test-sh-utils.sh 35 | 36 | 2014/07/26 37 | * Makefile: 38 | - fixed missing "bash-lib.sh" script 39 | * text-utils.sh: 40 | - added STDIN pipe capability on almost all text filters 41 | - added split_input function to split STDIN 42 | * README: 43 | - fixed some typos, other simple improvements 44 | * hash-utils.sh: 45 | - added necessary initialization of new hash variables. 46 | * test-text-utils.sh: 47 | - refactored all the tests to support new STDIN feature 48 | * sh-utils.sh: 49 | - added args_or_stdin function to pass along ars, or 50 | read from STDIN 51 | 52 | 2014/06/11 53 | * Makefile.inc 54 | - fixed "status" to find first occurrence of .git, .svn, or RCS in the 55 | current directory, or above. 56 | 57 | 2014/03/10 58 | * list-utils.sh: 59 | - improved list_add; added interactive help when there are insufficient arguments 60 | - the interactive help required improved "help" text. 61 | * README.md: 62 | - improved the list-utils descriptions (from the help text). 63 | - improved the real-utils descriptions. 64 | 65 | 2014/03/01 66 | * calfaq/ 67 | calfaq/calfunc.c 68 | - added reference code to compute julian/absolute day numbers. 69 | * gen-test-dates.sh: 70 | - creates "test-dates.dat" file using 500 random dates. 71 | * date-utils.sh: 72 | - rewritten date_to_jdays, date_to_adays, and reciprocal functions. 73 | * test-date-utils.sh: 74 | - incorporate new changes. 75 | * calendar.l: 76 | - a reference Lisp library for calendrical calculations. 77 | * calendar-utils.sh: 78 | - a bash implemnentation of calendar.l, but not used anywhere else yet. 79 | 80 | 2014/01/29 81 | 82 | * hash-utils.sh: 83 | - inception, based on ruby methods 84 | * test-all.sh 85 | - run all tests named "test-*-*.sh" 86 | * test-utils.sh 87 | - refactored for new usage in test-hash-utils.sh 88 | - added more informative default error messages 89 | * test-hash-utils.sh 90 | - new tests against hash-util functions. 91 | * real-utils.sh 92 | - added real_help function; updated the docs 93 | * README.md: 94 | - updated with hash-utils info 95 | - updated with new real-utils info 96 | 97 | 2014/01/26 98 | 99 | * date-utils.sh: 100 | - added EUROPEAN_DATES envar 101 | - renamed "date_arg" as "parse_date" 102 | - added more formats to support in the parsing 103 | - renamed "get_date_x_years_since" to "get_date_x_years_before" 104 | - added "get_date_x_years_since" 105 | - updated copyright 106 | * list-utils.sh: 107 | - updated copyright 108 | * test-date-utils.sh: 109 | - fixed some tests. 110 | - added some more tests 111 | * test-list-utils.sh: 112 | - Fixed wordlist creation 113 | - Abstracted the "check_output" function. 114 | * test-sh-utils.sh 115 | - reorganized to use test-util.sh 116 | - encapsulated all tests within "test_" functions. 117 | * test-utils.sh: 118 | - Added "check_output" function to catpure & compare stdout/err. 119 | - added support for filtering test name by patterns on command line 120 | - added -k (keep) option for reference output retention 121 | - added -r (randomize) option to randomize the test order 122 | * Makefile.inc 123 | - when invoking shell functions, use bash by default 124 | * test/* 125 | - lots of captured new reference output 126 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2008-2022 Alan Stebbens 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for bash-lib 2 | # 3 | # Copyright 2006-2022 Alan K. Stebbens 4 | 5 | SHELL := $(shell which bash) 6 | bindirs = 7 | bins = 8 | tbindirs = $(HOME)/bin 9 | tbins = calfunc 10 | 11 | GDATE = /usr/local/bin/gdate 12 | 13 | $(tbindirs)/calfunc: calfaq/calfunc.c calfaq/calfaq.h 14 | cd calfaq ; $(MAKE) install prefix=$(HOME) 15 | 16 | $(GDATE): 17 | brew install gdate 18 | 19 | test-dates.dat: gen-test-dates.sh 20 | ./gen-test-dates.sh 21 | 22 | test-dates-utils.sh: test-dates.dat $(GDATE) 23 | 24 | libdirs = $(HOME)/lib 25 | 26 | libs = \ 27 | Makefile.inc \ 28 | Makefile-template \ 29 | arg-utils.sh \ 30 | bash-check.sh \ 31 | bash-lib.sh \ 32 | calendar-utils.sh \ 33 | cli-template.sh \ 34 | date-utils.sh \ 35 | hash-utils.sh \ 36 | help-util.sh \ 37 | list-utils.sh \ 38 | option-utils.sh \ 39 | prompt-colors.sh \ 40 | real-utils.sh \ 41 | reset-util-vars.sh \ 42 | run-utils.sh \ 43 | sh-utils.sh \ 44 | sync-files.sh \ 45 | talk-utils.sh \ 46 | test-utils.sh \ 47 | test-template.sh \ 48 | text-utils.sh \ 49 | time-utils.sh \ 50 | # end of list 51 | 52 | prompt-colors.sh: generate-prompt-colors 53 | ./generate-prompt-colors 54 | 55 | clean:: 56 | rm -f prompt-colors.sh* 57 | rm -f test/*.{out,err} 58 | 59 | subdirs= 60 | 61 | tests= \ 62 | test-arg-utils.sh \ 63 | test-date-utils.sh \ 64 | test-hash-utils.sh \ 65 | test-list-utils.sh \ 66 | test-real-utils.sh \ 67 | test-sh-utils.sh \ 68 | test-talk-utils.sh \ 69 | test-text-utils.sh \ 70 | test-time-utils.sh \ 71 | #test-sync-files.sh \ 72 | # end of tests 73 | 74 | include Makefile.inc 75 | 76 | # vim: sw=4 ai 77 | -------------------------------------------------------------------------------- /Makefile-template: -------------------------------------------------------------------------------- 1 | # Makefile for libraries 2 | # $Id$ 3 | # 4 | 5 | bindirs = 6 | bins = 7 | 8 | libdirs = $(HOME)/lib 9 | libs = \ 10 | # end of list 11 | 12 | subdirs= 13 | 14 | include Makefile.inc 15 | 16 | # vim: sw=4 ai 17 | -------------------------------------------------------------------------------- /Makefile.inc: -------------------------------------------------------------------------------- 1 | # Makefile.inc 2 | # Copyright 2006-2022 Alan K. Stebbens 3 | # 4 | # $Id$ 5 | # 6 | # Makefile for managing sets of files 7 | # 8 | # bins = list of "binaries" (could be scripts) 9 | # bindirs = list of directories into which the binaries can be installed 10 | # libs = list of "libraries" (could be any kind of file) 11 | # libdirs = list of directories in which to install the libraries. 12 | # tbins = binaries needed to run tests 13 | # tbindirs = directories into which the test binaries can be installed 14 | # tests = list of test scripts 15 | # 16 | # distuser is a userid for remote distribution 17 | # disthost is the default host for remote distribution 18 | # distpath is the path on disthost under which the ZIP distribution of 19 | # the files will be installed. 20 | # 21 | 22 | SHELL ?= /bin/bash 23 | 24 | distuser ?= $$USER 25 | disthost ?= $(distuser)@$$DISTHOST 26 | distpath ?= . 27 | 28 | # These should be defined in the "parent" Makefile 29 | # bindirs 30 | # bins 31 | # libdirs 32 | # libs 33 | # incdirs 34 | # incs 35 | # cssdirs 36 | # cssfiles 37 | # tmpldirs 38 | # tmpls 39 | # tbins 40 | # tbindirs 41 | # tests 42 | # subdirs 43 | 44 | # This is a list of files which are in development, and which should not be installed 45 | # indev 46 | 47 | # if the target is "diff" or "difflist", use -k to keep going even if "diff" errors 48 | 49 | ifneq (,$(findstring diff,${MAKECMDGOALS})) 50 | MAKEFLAGS += -k 51 | endif 52 | 53 | # if subdirs is defined, then use -w to show what directory we are in 54 | ifdef subdirs 55 | MAKEFLAGS += -w 56 | endif 57 | 58 | allfiles = $(bins) $(libs) $(incs) $(cssfiles) Makefile RCS 59 | 60 | afile = dummy 61 | 62 | .PHONY: subdirs default help status diff diffs difflist 63 | .PHONY: fetch install install-bins install-libs install-files zip putzip getzip $(subdirs) 64 | 65 | default: help 66 | .DEFAULT_GOAL = help 67 | help: 68 | @echo "You can make these things:" 69 | @echo " help" 70 | @echo " status -- do git/svn/rlog status (if ./.git exists)" 71 | @echo " diff -- show differences with installed versions" 72 | @echo " difflist -- show different files installed versions" 73 | @echo " install -- install" 74 | @echo " fetch -- fetch the installed file(s)" 75 | @echo " check -- run tests (also 'tests')" 76 | @echo " zip -- Create a zip file " 77 | @echo " putzip -- distribute zipfile to yoda" 78 | @echo " getzip -- fetch zipfile from yoda" 79 | @echo "" 80 | @echo "Use bins='file1 ..' or libs='file1 ..' to work on only the named files" 81 | @echo "" 82 | @echo " bindirs = $(bindirs)" 83 | @echo " libdirs = $(libdirs)" 84 | @echo " incdirs = $(incdirs)" 85 | @echo " cssdirs = $(cssdirs)" 86 | @echo " tmpldirs = $(tmpldirs)" 87 | @echo " tbindirs = $(tbindirs)" 88 | @echo " subdirs = $(subdirs)" 89 | @echo "" 90 | @echo " bins = $(bins)" 91 | @echo " libs = $(libs)" 92 | @echo " incs = $(incs)" 93 | @echo "cssfiles = $(cssfiles)" 94 | @echo " tmpls = $(tmpls)" 95 | @echo " tbins = $(tbins)" 96 | @echo " tests = $(tests)" 97 | 98 | 99 | check test tests: $(tests) install-tbins 100 | @for file in $(tests) ; do \ 101 | echo '' ; echo "Testing $$file .." ; \ 102 | case $$file in \ 103 | *.rb) ruby -I . $$file ;; \ 104 | *.sh) bash $$file ;; \ 105 | *.pl) perl $$file ;; \ 106 | *.py) python -I . $$file ;; \ 107 | *) ./$$file ;; \ 108 | esac ; \ 109 | done 110 | 111 | zip: $(zipfile) 112 | $(zipfile): $(allfiles) 113 | zip -r $(zipfile) $(allfiles) 114 | 115 | putzip: $(zipfile) 116 | scp $(zipfile) $(disthost):$(distpath) 117 | 118 | getzip: 119 | scp $(disthost):$(distpath)/$(zipfile) . 120 | 121 | status: 122 | @for pth in . .. ../.. ../../.. ../../../.. ; do \ 123 | if [[ -d $$pth/.git ]] ; then git status ; \ 124 | elif [[ -d $$pth/.svn ]] ; then svn status ; \ 125 | elif [[ -d $$pth/RCS ]] ; then rlog -R -L $$pth/RCS/* ; fi ; \ 126 | done 127 | 128 | subdirs: $(subdirs) 129 | $(subdirs): ; $(MAKE) -C $@ $(MAKECMDGOALS) 130 | 131 | 132 | # $(call check_dirs_for_changed_files,FILES,DIRS,ACTION) 133 | # ACTION can reference $$dir and $$file 134 | 135 | check_dirs_for_changed_files = \ 136 | for file in $(1) ; do \ 137 | for dir in $(2) ; do \ 138 | $(call changed_file_action,$$dir/$$file,$$file,$(3)) ; \ 139 | done ; \ 140 | done 141 | 142 | # $(call changed_file_action,OLD,NEW,ACTION) 143 | 144 | changed_file_action = \ 145 | if [[ ! -f $(1) || -n `diff -q $(1) $(2)` ]]; then \ 146 | $(3) ; \ 147 | fi 148 | 149 | action_if_exists = \ 150 | if [[ -f $(1) ]]; then \ 151 | $(2) ; \ 152 | fi 153 | 154 | 155 | # These are the known actions 156 | # diff_file 157 | # copy_file 158 | # install_file 159 | 160 | diff_file = \ 161 | echo "========================================" ; \ 162 | diff -uawBN $(1) $(2) 163 | 164 | copy_file = (set -x ; cp $(1) $(2) ) 165 | install_file = (set -x ; install -bSC -m $(mode) $(1) $(2) ) 166 | install_py_file = $(call install_file,$(1),$(2)) ; \ 167 | for ext in pyc pyo ; do \ 168 | if [[ -f $(basename $(2)).$$ext ]]; then \ 169 | (set -x ; rm -f $(basename $(2)).$$ext ) ; \ 170 | fi ; \ 171 | done 172 | 173 | diff_changed_files = $(call check_dirs_for_changed_files,$(1),$(2),$(call diff_file,$$dir/$$file,$$file)) 174 | list_changed_files = $(call check_dirs_for_changed_files,$(1),$(2),printf "%30s\t%s\n" $$file $$dir/$$file) 175 | fetch_changed_files = $(call check_dirs_for_changed_files,$(1),$(2),$(call copy_file,$$dir/$$file,$$file)) 176 | 177 | action_to_targets = \ 178 | $(call $(1),$(bins),$(bindirs)) ; \ 179 | $(call $(1),$(libs),$(libdirs)) ; \ 180 | $(call $(1),$(incs),$(incdirs)) ; \ 181 | $(call $(1),$(cssfiles),$(cssdirs)) ; \ 182 | $(call $(1),$(tmpls),$(tmpldirs)) 183 | 184 | diff diffs: subdirs ; @$(call action_to_targets,diff_changed_files) 185 | difflist: subdirs ; @$(call action_to_targets,list_changed_files) 186 | fetch: subdirs ; @$(call action_to_targets,fetch_changed_files) 187 | 188 | install: install-bins install-libs install-incs install-cssfiles install-tmpls install-tbins 189 | 190 | install_targets = $(foreach the_dir,$(1),$(MAKE) the_dir=$(the_dir) files="$(2)" mode=$(3) install-files ;) 191 | install-files: $(addprefix $(the_dir)/,$(files)) 192 | 193 | install-bins: subdirs ; @$(call install_targets,$(bindirs),$(bins),775) 194 | install-libs: subdirs ; @$(call install_targets,$(libdirs),$(libs),664) 195 | install-incs: subdirs ; @$(call install_targets,$(incdirs),$(incs),664) 196 | install-cssfiles: subdirs ; @$(call install_targets,$(cssdirs),$(cssfiles),664) 197 | install-tmpls: subdirs ; @$(call install_targets,$(tmpldirs),$(tmpls),664) 198 | install-tbins: subdirs ; @$(call install_targets,$(tbindirs),$(tbins),775) 199 | 200 | $(the_dir): ; mkdir -p $@ 201 | $(the_dir)/%.py: %.py $(the_dir) ; @$(call changed_file_action,$@,$<,$(call install_py_file,$<,$@)) 202 | $(the_dir)/%.rb: %.rb $(the_dir) ; @$(call changed_file_action,$@,$<,$(call install_file,$<,$@)) 203 | $(the_dir)/%.js: %.js $(the_dir) ; @$(call changed_file_action,$@,$<,$(call install_file,$<,$@)) 204 | $(the_dir)/%.sh: %.sh $(the_dir) ; @$(call changed_file_action,$@,$<,$(call install_file,$<,$@)) 205 | $(the_dir)/%.css: %.sh $(the_dir) ; @$(call changed_file_action,$@,$<,$(call install_file,$<,$@)) 206 | $(the_dir)/%: % $(the_dir) ; @$(call changed_file_action,$@,$<,$(call install_file,$<,$@)) 207 | 208 | # vim: sw=4 ai noexpandtab sts=8 209 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Things to do 2 | ============ 3 | 4 | * write more tests for date-utils: date_adjust, date_add, date_sub, days_between 5 | 6 | * update README for new date-utils functions (tomorrow, yesterday, get_date_x_days ...) 7 | 8 | * Create the check_stdout, check_stderr, etc. functions. 9 | 10 | * write tests for run-utils.sh 11 | - add tests for add_trap 12 | 13 | * write more tests for arg-utils.sh 14 | 15 | * create a test suite for test-utils.sh: test-test-utils.sh :-) 16 | 17 | * check for co-dependencies in all the -util.sh libraries. Eg: mutually sourcing each other. 18 | -------------------------------------------------------------------------------- /arg-utils.sh: -------------------------------------------------------------------------------- 1 | # arg-utils.sh 2 | # 3 | # handy functions for flexibly managing function arguments in bash scripts 4 | # 5 | # Copyright 2006-2022 Alan K. Stebbens 6 | # 7 | 8 | ARG_UTILS_VERSION="arg-utils.sh v2.4" 9 | 10 | [[ "$ARG_UTILS_SH" = "$ARG_UTILS_VERSION" ]] && return 11 | ARG_UTILS_SH="$ARG_UTILS_VERSION" 12 | 13 | source help-util.sh 14 | 15 | arg_utils_help() { 16 | help_pager <<'EOF' 17 | The `arg-utils.sh` library is a collection of bash functions that enable 18 | flexible argument handling on functions that need to be able to accept 19 | arguments on the command-line or on STDIN. 20 | 21 | When writing a bash function that can accept input on the command line or from 22 | STDIN, the function should begin with an invocation of one of the following 23 | functions. 24 | 25 | For example, if we had a function that needed a numeric argument, the following 26 | invocation would be used: 27 | 28 | local f=`__numarg_or_input "$1"` 29 | 30 | If a text argument is needed: 31 | 32 | local txtarg=`__arg_or_input "$1"` 33 | 34 | For those cases where two or more arguments can be accepted, either on the 35 | command-line or from STDIN: 36 | 37 | local args=( `__args_or_input "$@"` ) 38 | 39 | The following are the arg-util functions: 40 | 41 | __numarg_or_input "$1" Return a numeric argument or read it from `STDIN` 42 | 43 | __arg_or_input "$1" Return the argument or read it from `STDIN` 44 | 45 | __args_or_input "$@" Return arguments or read them from `STDIN` 46 | 47 | __args_or_stdin "$@" Return the arguments or read all of `STDIN` 48 | 49 | __append_args "$@" Append the arguments to the next line from `STDIN` 50 | 51 | __append_arg "$1" Append the argument to the next line from `STDIN` 52 | EOF 53 | } 54 | 55 | help_arg_utils() { arg_utils_help ; } 56 | 57 | # The following functions, "__numarg_or_input", "__arg_for_input", and 58 | # "__args_or_input" enable bash functions using them to flexibly accept an 59 | # argument, or arguments, on their call, or on STDIN. 60 | # 61 | # For example, let's say we have two bash functions to convert Celsius to 62 | # Farheneit and vice-versa. Let's call them "c2f" and "f2c". With these 63 | # functions, they can be used in two ways: 64 | # 65 | # Typical functions with arguments: 66 | # 67 | # c2f 69 # convert 69C to F 68 | # f2c 10 # convert 10F to C 69 | # 70 | # Or, accepting their input on STDIN, as in a pipe: 71 | # 72 | # echo 69 | c2f # convert 69C to F 73 | # echo 10 | f2c # convert 10F to C 74 | # 75 | # The advantage of the latter appraoch is that the functions can be fitted into 76 | # a pipe where the data can come from another process directly, on its STDOUT. 77 | # 78 | # The definition of these two functions would be: 79 | # 80 | # # f2c -- convert F to C via: (°F - 32) x 5/9 = °C 81 | # function f2c() { 82 | # local f=`__numarg_or_input "$1"` 83 | # echo "scale=1; ($f - 32)*5/9' | bc 84 | # } 85 | # # c2f -- convert C to F via °C x 9/5 + 32 = °F 86 | # function c2f() { 87 | # local c=`__numarg_or_input "$1"` 88 | # echo "scale=1; $c * 9/5 + 32" | bc 89 | # } 90 | 91 | 92 | # local arg=`numarg_or_input $1` 93 | # 94 | # Return the numeric argument or read from stdin 95 | 96 | # OLD name; deprecated 97 | numarg_or_input() { 98 | __numarg_or_input "$@" 99 | } 100 | 101 | __numarg_or_input() { 102 | local -i arg 103 | if [[ $# -eq 0 || -z "$1" ]] ; then 104 | local func=`__calling_funcname` 105 | local -a args 106 | while (( ${#args[*]} == 0 )) ; do 107 | read -p "${func}? " args 108 | done 109 | arg=$(( 10#${args[0]} )) 110 | else 111 | arg=$(( 10#$1 )) 112 | fi 113 | echo $arg 114 | } 115 | 116 | # func=`__calling_funcname` 117 | # 118 | # Obtain the first function name not prefixed with "__" 119 | 120 | __calling_funcname() { 121 | local _x=1 122 | local func="${FUNCNAME[1]}" 123 | for (( _x=1; _x <= ${#FUNCNAME[*]}; _x++ )); do 124 | func="${FUNCNAME[$_x]}" 125 | [[ "${func:0:2}" == '__' ]] && continue 126 | [[ "${func}" == 'arg_or_input' ]] && continue 127 | [[ "${func}" == 'args_or_input' ]] && continue 128 | [[ "${func}" == 'args_or_stdin' ]] && continue 129 | [[ "${func}" == 'numarg_or_input' ]] && continue 130 | break 131 | done 132 | echo "$func" 133 | } 134 | 135 | __dump_func_stack() { 136 | local _i 137 | for(( _i=0 ; _i <= ${#FUNCNAME[*]}; _i++ )) ; do 138 | printf 1>&2 "%2d: %s\n" $_i "${FUNCNAME[$_i]}" 139 | done 140 | } 141 | 142 | # local arg=`__arg_or_input "$1"` 143 | # 144 | # Return the argument given, or the first non-empty line from STDIN 145 | 146 | # deprecated OLD name 147 | arg_or_input() { 148 | __arg_or_input "$@" 149 | } 150 | 151 | __arg_or_input() { 152 | local arg 153 | if [[ $# -eq 0 || -z "$1" ]]; then 154 | local func=`__calling_funcname` 155 | local -a args 156 | while (( ${#args[*]} == 0 )) ; do 157 | read -p "${func}? " args 158 | done 159 | arg="${args[0]}" 160 | else 161 | arg="$1" 162 | fi 163 | echo -n "$arg" 164 | } 165 | 166 | 167 | # local args=( `__args_or_input "$@"` ) 168 | # 169 | # Return the arguments or read a line of non-empty input 170 | 171 | # deprecated OLD name 172 | 173 | args_or_input() { 174 | __args_or_input "$@" 175 | } 176 | 177 | __args_or_input() { 178 | if (( $# == 0 )) ; then 179 | local -a args 180 | local func=`__calling_funcname` 181 | while (( ${#args[*]} == 0 )); do 182 | read -p "$func? " -a args 183 | done 184 | echo "${args[@]}" 185 | else 186 | echo -n "$@" 187 | fi 188 | } 189 | 190 | # some-pipe | __args_or_stdin "$@" 191 | # 192 | # return the given arguments, or read & return STDIN until EOF 193 | 194 | # deprecated OLD name 195 | args_or_stdin() { 196 | __args_or_stdin "$@" 197 | } 198 | 199 | __args_or_stdin() { 200 | if [[ $# -gt 0 ]] ; then 201 | echo -n "$*" 202 | else 203 | cat 204 | fi 205 | } 206 | 207 | arg_or_stdin() { __args_or_stdin "$1" ; } 208 | __arg_or_stdin() { __args_or_stdin "$1" ; } 209 | 210 | 211 | # __append_arg ARG 212 | # __append_args ARGS 213 | # 214 | # appends ARGS to the next line of input, and return the entire string 215 | # 216 | # echo SOMEDATA | __append_arg SOMEARG ==> SOMEDATA SOMEARG 217 | 218 | 219 | __append_arg() { 220 | local -a data 221 | read -a data 222 | echo -n "${data[@]}" "$@" 223 | } 224 | __append_args() { __append_arg "$@" ; } 225 | 226 | # deprecated OLD names 227 | append_arg() { __append_arg "$@" ; } 228 | append_args() { __append_arg "$@" ; } 229 | 230 | # end of arg-utils.sh 231 | # vim: sw=2 ai 232 | -------------------------------------------------------------------------------- /bash-check.sh: -------------------------------------------------------------------------------- 1 | # bash-check.sh 2 | # 3 | # this script checks to see if bash is running and if the version is >= 3.3 4 | 5 | if [[ "${#BASH_VERSINFO[@]}" -eq 0 || 6 | ${BASH_VERSINFO[0]} -lt 3 || 7 | ( ${BASH_VERSINFO[0]} -eq 3 && ${BASH_VERSINFO[1]} -lt 3 ) ]] 8 | then 9 | echo 1>&2 "This script can only run with bash version >= 3.3" 10 | if [[ -n "$BASH_VERSION" ]]; then 11 | echo 1>&2 "This is bash $BASH_VERSION" 12 | else 13 | echo 1>&2 "This is not bash!" 14 | fi 15 | exit 75 # EPROGMISMATCH 16 | fi 17 | -------------------------------------------------------------------------------- /bash-lib.sh: -------------------------------------------------------------------------------- 1 | # bash-lib.sh 2 | # Copyright 2009-2022 Alan K. Stebbens 3 | # 4 | # source all the bash library files 5 | 6 | source help-util.sh 7 | source arg-utils.sh 8 | source bash-check.sh 9 | source calendar-utils.sh 10 | source date-utils.sh 11 | source hash-utils.sh 12 | source list-utils.sh 13 | source option-utils.sh 14 | source real-utils.sh 15 | source reset-util-vars.sh 16 | source run-utils.sh 17 | source sh-utils.sh 18 | source talk-utils.sh 19 | source test-utils.sh 20 | source text-utils.sh 21 | source time-utils.sh 22 | 23 | # sync-files.sh # not generally useful yet 24 | -------------------------------------------------------------------------------- /calfaq/.gitignore: -------------------------------------------------------------------------------- 1 | calfunc 2 | calfunc.dSYM 3 | -------------------------------------------------------------------------------- /calfaq/Makefile: -------------------------------------------------------------------------------- 1 | # makefile 2 | # 3 | prefix ?= /usr/local 4 | share_prefix ?= /usr/local/share 5 | bindir ?= $(prefix)/bin 6 | mandir ?= $(share_prefix)/man/man$(mansec) 7 | mansec = 1 8 | manpage = $(prog).$(mansec) 9 | 10 | prog = calfunc 11 | 12 | ifneq ($(OSNAME),Darwin) 13 | CFLAGS = -DLIBBSD_OVERLAY -I/usr/include/bsd 14 | LDFLAGS = -lbsd 15 | endif 16 | 17 | $(prog): calfunc.c calfaq.h Makefile 18 | cc $(CFLAGS) $(LDFLAGS) -o $@ -ggdb -lc -lpcre calfaq.c calfunc.c 19 | 20 | $(prog).$(mansec): calfunc.man 21 | 22 | .PHONY: install install-bin install-man 23 | 24 | install: install-bin install-man 25 | 26 | install-bin: $(bindir) $(bindir)/$(prog) 27 | $(bindir): 28 | install -d $@ 29 | $(bindir)/$(prog): $(prog) 30 | install -b $? $(@D) 31 | 32 | install-man: $(mandir) $(mandir)/$(manpage) 33 | $(mandir): 34 | install -d $@ 35 | $(mandir)/$(manpage): $(prog).man 36 | install -b $? $@ 37 | -------------------------------------------------------------------------------- /calfaq/Readme.txt: -------------------------------------------------------------------------------- 1 | CALFAQ version 1.1, 4 April 2008 2 | ================================ 3 | 4 | COPYRIGHT 5 | --------- 6 | These functions are Copyright (c) 2008 by Claus Tondering 7 | (claus@tondering.dk). 8 | 9 | The "calfunc.c" program is Copyright (c) 2014 by Alan K. Stebbens 10 | 11 | 12 | LICENSE 13 | ------- 14 | The code is distributed under the Boost Software License, which says: 15 | 16 | Boost Software License - Version 1.0 - August 17th, 2003 17 | 18 | Permission is hereby granted, free of charge, to any person or 19 | organization obtaining a copy of the software and accompanying 20 | documentation covered by this license (the "Software") to use, reproduce, 21 | display, distribute, execute, and transmit the Software, and to prepare 22 | derivative works of the Software, and to permit third-parties to whom the 23 | Software is furnished to do so, all subject to the following: 24 | 25 | The copyright notices in the Software and this entire statement, including 26 | the above license grant, this restriction and the following disclaimer, 27 | must be included in all copies of the Software, in whole or in part, and 28 | all derivative works of the Software, unless such copies or derivative 29 | works are solely in the form of machine-executable object code generated 30 | by a source language processor. 31 | 32 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 33 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 34 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 35 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 36 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR 37 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 38 | USE OR OTHER DEALINGS IN THE SOFTWARE. 39 | 40 | DESCRIPTION 41 | ----------- 42 | 43 | These functions are an implementation in the C language of the formulas 44 | presented in the Calendar FAQ at 45 | http://www.tondering.dk/claus/calendar.html. 46 | 47 | The documentation of each function is found in the .c and .h files. 48 | 49 | The implementation follows the formulas mentioned in version 2.9 of the FAQ 50 | quite closely. The focus of the implementation is on simplicity and clarity. 51 | For this reason, no complex data structures or classes are used, nor has any 52 | attempt been made to optimize the code. Also, no verification of the input 53 | parameters is performed (except in the function simple_gregorian_easter). 54 | 55 | All numbers (including Julian Day Numbers which current have values of 56 | almost 2,500,000) are assumed to be representable as variables of type 57 | 'int'. 58 | 59 | COMPATIBILITY 60 | ------------- 61 | All of this code has been successfully built and run using Microsoft Visual 62 | Studio .NET 2003. 63 | 64 | All of this code has been successfully built and run using GCC version 65 | 3.4.4. 66 | 67 | CHANGES SINCE VERSION 1.0 68 | ------------------------- 69 | The code of version 1.1 is exactly identical to version 1.0. The only 70 | difference is that the comments now refer to version 2.9 of the Calendar 71 | FAQ rather than version 2.8. 72 | -------------------------------------------------------------------------------- /calfaq/calfaq.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CALFAQ version 1.1, 4 April 2008 3 | * 4 | * COPYRIGHT: 5 | * These functions are Copyright (c) 2008 by Claus Tondering 6 | * (claus@tondering.dk). 7 | * 8 | * LICENSE: 9 | * The code is distributed under the Boost Software License, which 10 | * says: 11 | * 12 | * Boost Software License - Version 1.0 - August 17th, 2003 13 | * 14 | * Permission is hereby granted, free of charge, to any person or 15 | * organization obtaining a copy of the software and accompanying 16 | * documentation covered by this license (the "Software") to use, 17 | * reproduce, display, distribute, execute, and transmit the 18 | * Software, and to prepare derivative works of the Software, and 19 | * to permit third-parties to whom the Software is furnished to do 20 | * so, all subject to the following: 21 | * 22 | * The copyright notices in the Software and this entire 23 | * statement, including the above license grant, this restriction 24 | * and the following disclaimer, must be included in all copies of 25 | * the Software, in whole or in part, and all derivative works of 26 | * the Software, unless such copies or derivative works are solely 27 | * in the form of machine-executable object code generated by a 28 | * source language processor. 29 | * 30 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 31 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 32 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND 33 | * NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR 34 | * ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR 35 | * OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 36 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 37 | * USE OR OTHER DEALINGS IN THE SOFTWARE. 38 | * 39 | * DESCRIPTION: 40 | * These functions are an implementation in the C language of the 41 | * formulas presented in the Calendar FAQ at 42 | * http://www.tondering.dk/claus/calendar.html. 43 | * 44 | * The implementation follows the formulas mentioned in version 2.9 45 | * of the FAQ quite closely. The focus of the implementation is on 46 | * simplicity and clarity. For this reason, no complex data 47 | * structures or classes are used, nor has any attempt been made to 48 | * optimize the code. Also, no verification of the input parameters 49 | * is performed (except in the function simple_gregorian_easter). 50 | * 51 | * All numbers (including Julian Day Numbers which current have 52 | * values of almost 2,500,000) are assumed to be representable as 53 | * variables of type 'int'. 54 | */ 55 | 56 | /* 57 | * Calendar styles 58 | */ 59 | #define JULIAN 0 60 | #define GREGORIAN 1 61 | 62 | 63 | /* 64 | * is_leap: 65 | * Determines if a year is a leap year. 66 | * Input parameters: 67 | * Calendar style (JULIAN or GREGORIAN) 68 | * Year (must be >0) 69 | * Returns: 70 | * 1 if the year is a leap year, 0 otherwise. 71 | * 72 | * Note: The algorithm assumes that AD 4 is a leap year. This may be 73 | * historically inaccurate. See the FAQ. 74 | * 75 | * Reference: Sections 2.1.1 and 2.2.1 of version 2.9 of the FAQ. 76 | */ 77 | int is_leap(int style, int year); 78 | 79 | 80 | /* 81 | * days_in_month: 82 | * Calculates the number of days in a month. 83 | * Input parameters: 84 | * Calendar style (JULIAN or GREGORIAN) 85 | * Year (must be >0) 86 | * Month (1..12) 87 | * Returns: 88 | * The number of days in the month (28..31) 89 | */ 90 | int days_in_month(int style, int year, int month); 91 | 92 | 93 | /* 94 | * solar_number: 95 | * Calculates the Solar Number of a given year. 96 | * Input parameter: 97 | * Year (must be >0) 98 | * Returns: 99 | * Solar Number (1..28) 100 | * 101 | * Reference: Section 2.4 of version 2.9 of the FAQ. 102 | */ 103 | int solar_number(int year); 104 | 105 | 106 | /* 107 | * day_of_week: 108 | * Calculates the weekday for a given date. 109 | * Input parameters: 110 | * Calendar style (JULIAN or GREGORIAN) 111 | * Year (must be >0) 112 | * Month (1..12) 113 | * Day (1..31) 114 | * Returns: 115 | * 0 for Sunday, 1 for Monday, 2 for Tuesday, etc. 116 | * 117 | * Reference: Section 2.6 of version 2.9 of the FAQ. 118 | */ 119 | int day_of_week(int style, int year, int month, int day); 120 | 121 | 122 | /* 123 | * golden_number: 124 | * Calculates the Golden Number of a given year. 125 | * Input parameter: 126 | * Year (must be >0) 127 | * Returns: 128 | * Golden Number (1..19) 129 | * 130 | * Reference: Section 2.13.3 of version 2.9 of the FAQ. 131 | */ 132 | int golden_number(int year); 133 | 134 | 135 | /* 136 | * epact: 137 | * Calculates the Epact of a given year. 138 | * Input parameters: 139 | * Calendar style (JULIAN or GREGORIAN) 140 | * Year (must be >0) 141 | * Returns: 142 | * Epact (1..30) 143 | * 144 | * Reference: Section 2.13.5 of version 2.9 of the FAQ. 145 | */ 146 | int epact(int style, int year); 147 | 148 | 149 | /* 150 | * paschal_full_moon: 151 | * Calculates the date of the Paschal full moon. 152 | * Input parameters: 153 | * Calendar style (JULIAN or GREGORIAN) 154 | * Year (must be >0) 155 | * Output parameters: 156 | * Address of month of Paschal full moon (3..4) 157 | * Address of day of Pascal full moon (1..31) 158 | * 159 | * Reference: Section 2.13.4 and 2.13.6 of version 2.9 of the FAQ. 160 | */ 161 | void paschal_full_moon(int style, int year, int *month, int *day); 162 | 163 | 164 | /* 165 | * easter: 166 | * Calculates the date of Easter Sunday. 167 | * Input parameters: 168 | * Calendar style (JULIAN or GREGORIAN) 169 | * Year (must be >0) 170 | * Output parameters: 171 | * Address of month of Easter Sunday (3..4) 172 | * Address of day of Easter Sunday (1..31) 173 | * 174 | * Reference: Section 2.13.7 of version 2.9 of the FAQ. 175 | */ 176 | void easter(int style, int year, int *month, int *day); 177 | 178 | 179 | /* 180 | * simple_gregorian_easter: 181 | * Calculates the date of Easter Sunday in the Gregorian calendar. 182 | * Input parameter: 183 | * Year (must be in the range 1900..2099) 184 | * Output parameters: 185 | * Address of month of Easter Sunday (3..4) 186 | * Address of day of Easter Sunday (1..31) 187 | * 188 | * If the year is outside the legal range, *month is set to zero. 189 | * 190 | * Reference: Section 2.13.8 of version 2.9 of the FAQ. 191 | */ 192 | void simple_gregorian_easter(int year, int *month, int *day); 193 | 194 | 195 | /* 196 | * indiction: 197 | * Calculates the Indiction of a given year. 198 | * Input parameter: 199 | * Year (must be >0) 200 | * Returns: 201 | * Indiction (1..15) 202 | * 203 | * Reference: Section 2.15 of version 2.9 of the FAQ. 204 | */ 205 | int indiction(int year); 206 | 207 | 208 | /* 209 | * julian_period: 210 | * Calculates the year in the Julian Period corresponding to a given 211 | * year. 212 | * Input parameter: 213 | * Year (must be in the range -4712..3267). The year 1 BC must be 214 | * given as 0, the year 2 BC must be given as -1, etc. 215 | * Returns: 216 | * The corresponding year in the Julian period 217 | * 218 | * Reference: Section 2.16 of version 2.9 of the FAQ. 219 | */ 220 | int julian_period(int year); 221 | 222 | 223 | /* 224 | * date_to_jdn: 225 | * Calculates the Julian Day Number for a given date. 226 | * Input parameters: 227 | * Calendar style (JULIAN or GREGORIAN) 228 | * Year (must be > -4800). The year 1 BC must be given as 0, the 229 | * year 2 BC must be given as -1, etc. 230 | * Month (1..12) 231 | * Day (1..31) 232 | * Returns: 233 | * Julian Day Number 234 | * 235 | * Reference: Section 2.16.1 of version 2.9 of the FAQ. 236 | */ 237 | int date_to_jdn(int style, int year, int month, int day); 238 | 239 | 240 | /* 241 | * jdn_to_date: 242 | * Calculates the date for a given Julian Day Number. 243 | * Input parameter: 244 | * Calendar style (JULIAN or GREGORIAN) 245 | * Julian Day Number 246 | * Output parameters: 247 | * Address of year. The year 1 BC will be stored as 0, the year 248 | * 2 BC will be stored as -1, etc. 249 | * Address of month (1..12) 250 | * Address of day (1..31) 251 | * 252 | * Reference: Section 2.16.1 of version 2.9 of the FAQ. 253 | */ 254 | void jdn_to_date(int style, int JD, int *year, int *month, int *day); 255 | 256 | 257 | /* 258 | * week_number: 259 | * Calculates the ISO 8601 week number (and corresponding year) for a given 260 | * Gregorian date. 261 | * Input parameters: 262 | * Year (must be >0) 263 | * Month (1..12) 264 | * Day 265 | * Output parameters: 266 | * Address of week number (1..53) 267 | * Address of corresponding year 268 | * 269 | * Reference: Section 7.8 of version 2.9 of the FAQ. 270 | */ 271 | void week_number(int year, int month, int day, int *week_number, int *week_year); 272 | -------------------------------------------------------------------------------- /calfaq/calfaq.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/calfaq/calfaq.zip -------------------------------------------------------------------------------- /calfaq/calfunc.man: -------------------------------------------------------------------------------- 1 | .Dd Feb 17, 2014 2 | .Dt calfunc 1 3 | .Os 4 | .Sh NAME 5 | .Nm calfunc 6 | .Nd Program to exercise and display various Calendar functions 7 | .Sh SYNOPSIS 8 | .Nm calfunc 9 | .Op Fl dhinv 10 | .Ar command argument 11 | .Sh DESCRIPTION 12 | The 13 | .Nm calfunc 14 | program reads various calendar commands and arguments from the command line or from 15 | .Nm stdin , 16 | and executes them. 17 | .Pp 18 | .Nm caltest 19 | performs date calculations using the 20 | .Nm 'calfaq' 21 | C library. 22 | .Pp 23 | Options: 24 | .Bl -tag -width indent 25 | .It Fl d 26 | Debug mode 27 | .It Fl h 28 | show this help 29 | .It Fl i 30 | interactive mode -- read commands from 31 | .Va STDIN 32 | .It Fl n 33 | no run: don't compute, just show the parsed tokens 34 | .It Fl v 35 | verbose (talk a lot) 36 | .El 37 | .Pp 38 | Commands: 39 | .Pp 40 | The commands may be given as unique abbreviations, or 41 | as the specific alternative. 42 | .Pp 43 | .Bl -column 15 "Command " "Alt" " Argument" "Function" 44 | .It Sy Command Ta Sy Alt Ta Sy Argument Ta Sy Function 45 | .It Ic julian Ta Ic j Ta DATE Ta convert DATE to Julian days 46 | .It Ic "date " Ta Ic d Ta JDN Ta convert Julian Day Number (JDN) into a date 47 | .It Ic absdays Ta Ic a Ta DATE Ta convert DATE to absolute days 48 | .It Ic weeknum Ta Ic wn Ta Ar "DATE|JDN" Ta the weeknum for the given DATE/JDN 49 | .It Ic weekday Ta Ic wd Ta Ar "DATE|JDN" Ta The weekday for the given DATE/JDN 50 | .It Ic leapyear Ta Ic ly Ta YEAR Ta waether or not YEAR is a leap year 51 | .It Ic solarnum Ta Ic sn Ta YEAR Ta the solar number of YEAR 52 | .It Ic epact Ta Ic ep Ta YEAR Ta the epact for YEAR 53 | .It Ic pashcalmoon Ta Ic pm Ta YEAR Ta the date of the Paschal full moon 54 | .It Ic easter Ta Ic e Ta YEAR Ta The date of Easter for an absolute YEAR 55 | .It Ic eastersunday Ta Ic es Ta YEAR Ta Easter Sunday (Gregorian Calendar) for YEAR 56 | .It Ic julianperiod Ta Ic jp Ta YEAR Ta the Julian Year for the given absolute YEAR 57 | .El 58 | .Pp 59 | The 60 | .Nm DATE 61 | can be in any of the following formats: 62 | .Bl indent 63 | .It YYYY-MM-DD 64 | .It MM/DD/YYYY 65 | .It DD.MM.YYYY 66 | .It DD-MMM-YYYY 67 | .It MMMM DD, YYYY 68 | .It DDD MMM DD HH:MM:SS YYYY TZONE 69 | .El 70 | 71 | \.".Sh FILES 72 | 73 | \.".Sh SEE ALSO 74 | 75 | \.".Sh HISTORY 76 | 77 | The 78 | .Nm calfaq.c 79 | library functions are copyright (c) 2008 by Claus Tondering (claus@tondering.dk). 80 | .Pp 81 | The 82 | .Nm calfunc.c 83 | program is copyright (c) 2014 by Alan K. Stebbens 84 | 85 | .Sh AUTHOR 86 | 87 | Alan K. Stebbens 88 | -------------------------------------------------------------------------------- /cli-template.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # my-script some-args 3 | # 4 | # This is a template for a command-line script. 5 | # It supports four options: -n, -v, -h, and -i FILE. 6 | # 7 | # See "help getopts" for more information on adding other options. 8 | # 9 | # Copyright 2019-2022 Alan K. Stebbens 10 | 11 | # set the name and directory of this program 12 | PROG="${0##*/}" 13 | DIR="${0%/*}" 14 | 15 | # include ~/bin and ~/lib to get my bash scripts 16 | export PATH=$PATH:$HOME/bin:$HOME/lib 17 | 18 | # include some library scripts 19 | source talk-utils.sh 20 | source run-utils.sh 21 | 22 | usage() { 23 | cat 1>&2 <k; k++){ 17 | h = int(p*j + 0.5) 18 | c = m - h 19 | p = p - c 20 | printf "%d\t%5d.%02d\t%5d.%02d\t%6d.%02d\n", 21 | k,int(h/100),h%100,int(c/100),c%100,int(p/100),p%100 22 | } 23 | }' 24 | 25 | -------------------------------------------------------------------------------- /experiments/mort2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # mortgage payment calculator 3 | # 4 | #### http://www.jeacle.ie/mortgage/instructions.html 5 | # 6 | # First you must define some variables to make it easier to set up: 7 | # 8 | # P = principal, the initial amount of the loan 9 | # 10 | # I = the annual interest rate (from 1 to 100 percent) 11 | # 12 | # L = length, the length (in years) of the loan, or at least the length over 13 | # which the loan is amortized. 14 | # 15 | # The following assumes a typical conventional loan where the interest is 16 | # compounded monthly. First I will define two more variables to make the 17 | # calculations easier: 18 | # 19 | # J = monthly interest in decimal form = I / (12 x 100) 20 | # 21 | # N = number of months over which loan is amortized = L x 12 22 | # 23 | # Okay now for the big monthly payment (M) formula, it is: 24 | # 25 | # J 26 | # M = P x ------------------------ 27 | # 1 - ( 1 + J ) ^ -N 28 | # 29 | # where 1 is the number one (it does not appear too clearly on some browsers) 30 | # 31 | # So to calculate it, you would first calculate 1 + J then take that 32 | # to the -N (minus N) power, subtract that from the number 1. Now take 33 | # the inverse of that (if you have a 1/X button on your calculator 34 | # push that). Then multiply the result times J and then times P. 35 | # Sorry, for the long way of explaining it, but I just wanted to be 36 | # clear for everybody. 37 | # 38 | # The one-liner for a program would be (adjust for your favorite 39 | # language): 40 | # 41 | # M = P * ( J / (1 - (1 + J) ** -N)) 42 | # 43 | # So now you should be able to calculate the monthly payment, M. 44 | # 45 | # To calculate the amortization table you need to do some iteration 46 | # (i.e. a simple loop). I will tell you the simple steps: 47 | # 48 | # Step 1: Calculate H = P x J, this is your current monthly interest 49 | # 50 | # Step 2: Calculate C = M - H, this is your monthly payment minus your 51 | # monthly interest, so it is the amount of principal you pay for that 52 | # month 53 | # 54 | # Step 3: Calculate Q = P - C, this is the new balance of your 55 | # principal of your loan. 56 | # 57 | # Step 4: Set P equal to Q and go back to Step 1: You thusly loop 58 | # around until the value Q (and hence P) goes to zero. 59 | # 60 | 61 | export PATH=$PATH:.:$HOME/lib 62 | source real-utils.sh 63 | 64 | # input variables 65 | P='172000.00' # principle of the loan in dollars 66 | L=15 # length of the loan in years 67 | I='5.5' # annual interest rate on the loan 68 | 69 | # derived variables 70 | n=$(( 12 * L )) # number of payments 71 | j=`real_eval "$I/1200" 10` # monthly interest rate 72 | 73 | # Now, compute the monthly payment, in dollars 74 | M=`real_eval "$P * ( $j / (1 - (1 + $j) ^ -$n))" 10` 75 | M=`round $M 2` # round to the nearest penny 76 | 77 | printf "Total Principal: %10.2f\n" "$P" 78 | printf "Monthly Payment: %10.2f\n\n" "$M" 79 | printf "%-8s %-8s %-10s %-10s\n" 'Payment' 'Interest' 'Principal' 'Balance' 80 | 81 | ti='0.0' p="$P" 82 | for ((k=0; k>$TESTDATA 57 | let days[adays]=1 58 | fi 59 | done 60 | echo '' 61 | echo "Done" 62 | exit 63 | -------------------------------------------------------------------------------- /generate-prompt-colors: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # generate-prompt-colors-file 3 | # 4 | # generate-prompt-colors-files -hnv [OUTPUTFILE] 5 | # 6 | # run this script to create "prompt-colors.sh", which contains 7 | # standard prompt color definitions, and a "reset_prompt_colors" 8 | # function to unset them all. 9 | # 10 | # Copywrite 2006-2022 Alan K. Stebbens 11 | 12 | PROG="${0##*/}" 13 | THIS_DIR="${0%/*}" 14 | 15 | # this is the filename we create 16 | PROMPT_COLOR_FILENAME='prompt-colors.sh' 17 | 18 | export PATH=$PATH:$HOME/lib 19 | source sh-utils.sh 20 | source list-utils.sh 21 | source talk-utils.sh 22 | source run-utils.sh 23 | 24 | usage() { 25 | cat 1>&2 < 1 )); then 102 | cv="${1};${2}" 103 | else 104 | cv="${1}" 105 | fi 106 | echo "\$'\033[${cv}m'" 107 | } 108 | 109 | # def_color NAME ATTRCODE COLORCODE 110 | assign_color_name() { 111 | local color_name="$1" 112 | local code="`prompt_color_string $2 $3`" 113 | local def="$color_name=$code" 114 | votalk "+ $def" 115 | echo " $def" 116 | add_list color_name_list $color_name 117 | } 118 | 119 | echo "#" 120 | echo "# define_color_names -- invoke to define all of the prompt color names" 121 | echo "#" 122 | echo 'define_color_names() {' 123 | 124 | #ResetColor="`prompt_color_string 0`" # Text reset 125 | 126 | map_color_names Bold $AttrBright 127 | map_color_names Bright $AttrBright 128 | map_color_names Dim $AttrDim 129 | map_color_names '' $AttrNorm 130 | 131 | assign_color_name IntenseBlack 0 90 132 | assign_color_name ResetColor 0 0 133 | 134 | echo "}" 135 | 136 | # now create the reset function 137 | 138 | echo "" 139 | echo "# reset_color_names -- invoke to unset all the color names" 140 | echo "" 141 | echo "reset_color_names() {" 142 | map_list color_name_list "echo \" unset \$item\"" "$CHAR_NL" 143 | echo "}" 144 | } 145 | 146 | # create_prompt_color_file OUTDIR 147 | # 148 | # if OUTDIR is missing, use '.' (current directory) by default 149 | # the output filename is always 'prompt-colors.sh' 150 | # 151 | # The following variables should be defined before invoking this function: 152 | # 153 | # $PROMPT_COLOR_FILENAME, $PROG, $USER, $ARGSTR 154 | 155 | create_prompt_color_file() { 156 | local outdir="${1:-.}" 157 | 158 | if [[ ! -d "$outdir" ]]; then 159 | error "The directory '$outdir' doesn't exist!" 160 | fi 161 | 162 | local outfile="$outdir/$PROMPT_COLOR_FILENAME" 163 | 164 | if [[ -f "$outfile" ]]; then 165 | oldfile="$outfile.old" 166 | nqtalk "Renaming previous file to $oldfile" 167 | run "mv \"$outfile\" \"$oldfile\"" 168 | fi 169 | 170 | # redirect STDOUT to the output file 171 | exec 3>&1 # save a copy of current STDOUT 172 | if (( ! norun )) ; then 173 | exec 1>"$outfile" 174 | else 175 | exec 1>&2 176 | fi 177 | cat <&- # close STDOUT 196 | exec 1>&3 # recover original STDOUT 197 | if (( ! norun )) ; then 198 | lines=`wc -l <"$outfile"` 199 | talkf "%d lines written to %s\n" $lines "$outfile" 200 | else 201 | talk "Nothing written" 202 | fi 203 | } 204 | 205 | # the main app processing 206 | 207 | ARGSTR="${ARGV[@]}" 208 | 209 | while getopts 'hnv' opt ; do 210 | case "$opt" in 211 | h) usage ;; 212 | n) norun=1 ;; 213 | v) verbose=1 ;; 214 | esac 215 | done 216 | shift $(( OPTIND - 1 )) 217 | 218 | outdir="$1{:-.}" # output directory or '.' by default 219 | 220 | create_prompt_color_file $1 221 | 222 | exit 223 | 224 | # end of prompt-colors.sh 225 | # vim: set ai sw=2 226 | -------------------------------------------------------------------------------- /hash-utils.sh: -------------------------------------------------------------------------------- 1 | # hash-utils.sh 2 | # Copyright 2014-2022 Alan K. Stebbens 3 | # 4 | # bash script utilities for managing hashes (associative arrays) 5 | 6 | HASH_UTILS_VERSION='hash-utils.sh v1.2' 7 | [[ "$HASH_UTILS_SH" = "$HASH_UTILS_VERSION" ]] && return 8 | HASH_UTILS_SH="$HASH_UTILS_VERSION" 9 | 10 | source list-utils.sh 11 | source help-util.sh 12 | 13 | hash_help() { 14 | cat 1>&2 <VAL into the hash 30 | hash_set VAR KEY VAL # alias to "hash_put" 31 | 32 | hash_get VAR KEY # get the Nth item of VAR to stdout 33 | 34 | hash_delete VAR KEY # delete VAR[KEY] 35 | 36 | hash_delete_if VAR KEY CONDITION # delete VAR[KEY} if CONDITION is true 37 | 38 | hash_keys VAR # return all the keys in hash 39 | 40 | hash_values VAR # return all the values in hash 41 | 42 | hash_each VAR KEYVAR EXPR # eval EXPR, setting KEYVAR to each key in VAR 43 | 44 | hash_copy HASH NEWHASH KEY1 ... # copy items at KEY1, .. from HASH1 to NEWHASH 45 | 46 | in_hash VAR KEY # test if KEY is in the hash VAR 47 | has_key VAR KEY 48 | hash_member VAR KEY 49 | hash_include VAR KEY 50 | 51 | hash_size VAR # returns the number of key=>value pairs 52 | 53 | hash_merge VAR1 VAR2 # merge key/value pairs from VAR2 with VAR1 54 | 55 | hash_print HASHVAR [indent=INDENT] [width=WIDTH] [gutter=GUTTER] [cols=COLS] [sep=SEP] 56 | 57 | print the items in HASHVAR in vertically-sorted columns. The number of 58 | columns is determined by COLS, if given, or by WIDTH (defaults to 80) 59 | divided by the maximum width of all items in HASHVAR. 60 | 61 | Use GUTTER blanks (default 2) to separate columns. 62 | 63 | If SEP is given, it is used to delimit columns intead of blanks. 64 | 65 | Each option may be abbreviated to its leading character (e.g., "g" for "gutter"). 66 | 67 | hash_help # describe the list functions 68 | 69 | EOF 70 | } 71 | help_hash() { hash_help ; } 72 | 73 | error() { echo "$*" 1>&2 ; exit 1 ; } 74 | 75 | # hash_init VAR [DEFAULT] 76 | # 77 | # Initialize VAR as an empty hash 78 | # if DEFAULT given, set as the default value on non-existant keys 79 | 80 | hash_init() { 81 | help_args_func hash_help $# || return 82 | local var="$1" 83 | eval "unset $var" 84 | eval "declare -gA $var" # declare the global associative array 85 | eval "${var}=()" 86 | (( $# > 1 )) && { hash_set_default $var "$2" ; } 87 | } 88 | 89 | # hash_set_default VAR DEFAULT 90 | # 91 | # Set the default value for the hash VAR 92 | 93 | hash_set_default() { 94 | help_args_func hash_help $# 2 || return 95 | eval "declare -g ${1}_default" 96 | eval "${1}_default=\"$2\"" 97 | } 98 | 99 | # hash_default VAR 100 | # 101 | # Return the default value for the hash VAR 102 | 103 | hash_default() { 104 | help_args_func hash_help $# || return 105 | eval "echo \"\${${1}_default}\"" 106 | } 107 | 108 | # hash_info VAR 109 | # 110 | # Display information about the hash VAR: 111 | # hash: somehash 112 | # size: 4 113 | # default: (empty) 114 | 115 | hash_info() { 116 | help_args_func hash_help $# || return 117 | local var="$1" 118 | local type=`declare -p $var 2>/dev/null` 119 | if [[ ! ( "$type" =~ -A ) ]]; then 120 | printf "%s is not a hash\n" $var 121 | return 1 122 | fi 123 | local size=`hash_size $var` 124 | local def=`hash_default $var` 125 | printf " hash: %s\n" $var 126 | printf " size: %d\n" $size 127 | if [[ -n "$def" ]]; then 128 | printf "default: %s\n" $def 129 | fi 130 | } 131 | 132 | 133 | # hash_set VAR KEY VALUE [KEY2 VAL2 ...] 134 | # Supports insertion of multiple values into the hash 135 | 136 | hash_set() { 137 | help_args_func hash_help $# 3 || return 138 | local var="$1" 139 | shift 140 | local key val 141 | while (( $# > 0 )) ; do 142 | key="$1" val="$2" ; shift 2 143 | eval "$var[\"$key\"]=\"$val\"" 144 | done 145 | } 146 | hash_put() { hash_set "$@" ; } 147 | 148 | # hash_get_keys HASHVAR 149 | # 150 | # Set "keys" to the list of keys in HASHVAR 151 | 152 | hash_get_keys() { 153 | eval "keys=( \"\${!$1[@]}\" )" 154 | } 155 | 156 | # hash_reset VAR 157 | # 158 | # Reset the hash VAR to its initial empty state. 159 | 160 | hash_reset() { 161 | help_args_func hash_help $# || return 162 | local var="$1" keys k 163 | hash_get_keys $var 164 | for k in "${keys[@]}" ; do 165 | unset $var['$k'] 166 | done 167 | } 168 | 169 | 170 | # hash_get VAR KEY 171 | # 172 | # Get the value associated with KEY. If there is none, use any defined default value. 173 | 174 | hash_get() { 175 | help_args_func hash_help $# 2 || return 176 | local val 177 | eval "val=\"\${$1['$2']}\"" 178 | [[ -z "$val" ]] && { eval "val=\"\${${1}_default}\"" ; } 179 | echo "$val" 180 | } 181 | 182 | # hash_delete VAR KEY 183 | # 184 | # Delete VAR[KEY] 185 | 186 | hash_delete() { 187 | help_args_func hash_help $# 2 || return 188 | unset $1["$2"] 189 | } 190 | 191 | # hash_delete_if VAR KEY COND 192 | # 193 | # Delete VAR[KEY] if COND is true 194 | 195 | hash_delete_if() { 196 | help_args_func hash_help $# 3 || return 197 | if [[ -n "$3" ]]; then 198 | unset $1["$2"] 199 | fi 200 | } 201 | 202 | # hash_keys VAR 203 | # hash_values VAR 204 | 205 | hash_keys() { 206 | help_args_func hash_help $# || return 207 | eval "echo \"\${!$1[@]}\"" 208 | } 209 | 210 | hash_values() { 211 | help_args_func hash_help $# || return 212 | eval "echo \"\${$1[@]}\"" 213 | } 214 | 215 | # hash_each VAR KEY EXPR 216 | # hash_each_pair VAR KEY VAL EXPR 217 | # 218 | # Iterate over the key/value pairs in VAR, seting KEY to each key, and then 219 | # evaluating EXPR. 220 | # 221 | # When "hash_each_pair" is invoked, the current key is assigned to key, and the 222 | # current value is assigned to VAL on each iteration. 223 | 224 | hash_each() { 225 | help_args_func hash_help $# 3 || return 226 | local val 227 | hash_each_pair $1 $2 val "$3" 228 | } 229 | 230 | hash_each_pair() { 231 | help_args_func hash_help $# 4 || return 232 | local name key keyn valn expr 233 | local -a keys 234 | name="${1:?'Missing hash variable name'}" 235 | keyn=${2:-key} valn=${3:-val} expr="$4" 236 | hash_get_keys $name 237 | for key in "${keys[@]}" ; do 238 | eval "${keyn}=\"$key\"" 239 | eval "${valn}=`hash_get $name \"$key\"`" 240 | eval "$expr" 241 | done 242 | } 243 | 244 | 245 | # in_hash HASH KEY 246 | # 247 | # Returns 0 if KEY is defined in the HASH 248 | # Returns 1 otherwise. 249 | 250 | in_hash() { 251 | help_args_func hash_help $# 2 || return 252 | local val=`hash_get $1 "$2"` 253 | [[ -n "$val" ]] && return 0 || return 1 254 | } 255 | has_key() { in_hash "$@" ; } # aliases 256 | hash_member() { in_hash "$@" ; } 257 | hash_include() { in_hash "$@" ; } 258 | 259 | 260 | # hash_size NAME -- return list size 261 | hash_size() { 262 | help_args_func hash_help $# || return 263 | eval "echo \"\${#$1[@]}\"" 264 | } 265 | 266 | # hash_merge VAR1 VAR2 267 | # 268 | # Merge hash VAR2 into VAR1 269 | 270 | hash_merge() { 271 | help_args_func hash_help $# 2 || return 272 | local key val 273 | hash_each_pair $2 key val "hash_put $1 \"\$key\" \"\$val\"" 274 | } 275 | 276 | # print_hash hash [indent=INDENT] [width=WIDTH] [gw=GUTTER] [cols=COLS] [sep=SEP] 277 | # 278 | # Print list in vertically sorted columns, optionally indented, limit output 279 | # to `maxwidth` separate columns by `GUTTER` blanks (defaults to 2). Use SEP 280 | # as separator character between columns. 281 | # 282 | # hash=( [KEY1]=VAL1 283 | # [KEY2]=VAL2 284 | # ... 285 | # ) 286 | 287 | hash_print() { 288 | help_args_func hash_help $# || return 289 | local var="$1" 290 | shift 291 | local indent width gutter sep cols widtharg gutterarg separg prefix 292 | _set_args "indent width gutter sep cols" "$@" 293 | # set defaults 294 | gutter="${gutter:-2}" 295 | widtharg= 296 | [[ -n "$width" ]] && { widtharg="-w$width " ; } 297 | [[ -n "$gutter" ]] && { gutterarg="-g$gutter" ; } 298 | [[ -n "$sep" ]] && { separg="-C'$sep'" ; } 299 | # sort & shape the items 300 | if [[ -z "$indent" ]]; then 301 | hash_print_items $var | rs -t $widtharg $gutterarg $separg 0 $cols 302 | else 303 | prefix=`printf "%*s" $indent ' '` 304 | hash_print_items $var | rs -t $widtharg $gutterarg $separg 0 $cols | ( 305 | while read line ; do 306 | printf "%s%s\n" "$prefix" "$line" 307 | done 308 | ) 309 | fi 310 | } 311 | print_hash() { "$@" ; } 312 | 313 | # hash_print_items VAR 314 | hash_print_items() { 315 | help_args_func hash_help $# || return 316 | local var="$1" key val 317 | local -a keys 318 | hash_get_keys $var 319 | __list_sort keys 320 | for key in "${keys[@]}" ; do 321 | val=`hash_get $var "$key"` 322 | printf "%s['%s']='%s'\n" $var "$key" "$val" 323 | done 324 | } 325 | 326 | # _set_args "OPT1 OPT2 ..." "$@" 327 | # 328 | # Scan the arguments looking for OPTn=VAL, and set each OPTn to the 329 | # value. 330 | 331 | _set_args() { 332 | local optlist=() 333 | add_list optlist $1 334 | shift 335 | while (( $# > 0 )) ; do 336 | local opt="${1%=*}" # get opt part of 'opt=val' 337 | local val="${1#*=}" # get val part of 'opt=val' 338 | shift 339 | local var=`lookup_list optlist $opt` 340 | [[ -n "$var" ]] || error "No such option: '$opt'" 341 | case "$?" in 342 | 0) eval "$var=\"$val\"" ;; 343 | 1) error "No such option: '$opt'" ;; 344 | 2) error "'$opt' is ambiguous" ;; 345 | esac 346 | done 347 | } 348 | 349 | # end of hash-utils.sh 350 | # vim: sw=2 ai 351 | 352 | -------------------------------------------------------------------------------- /help-util.sh: -------------------------------------------------------------------------------- 1 | # help-util.sh 2 | # Copyright 2015 Alan K. Stebbens 3 | # 4 | # This utility makes it easy to provide helpful responses for shell functions 5 | # that are missing arguments. 6 | # 7 | # Each collection of related shell functions can share a common "help_FUNC" 8 | # function, which is then filtered for the specific function name for which 9 | # help is being sought. 10 | # 11 | # Each function that can be used by a user should start with a call to 12 | # "help_args_func", passing the HELPFUNC, $#, and the mininum number of 13 | # arguments. 14 | # 15 | # If the using function is called with less than the required arguments the 16 | # HELPFUNC is invoked and the output filtered through a simple filter that does 17 | # not print until the calling function name is found and then prints only until 18 | # the next empty line of test. 19 | # 20 | # Each collection of functions that wish to make use of this utility should 21 | # have a HELPFUNC that prints a brief description of each command (function), 22 | # where each function name begins an unindented comment line, with exactly one 23 | # blank after the comment character. A description may follow -- as a bash 24 | # comment, indented or not. Finally, the doc entry for the given function is 25 | # an empty comment line. 26 | # 27 | # For reference examples, please see either list-utils.sh or hash-utils.sh. 28 | 29 | # help_pager </dev/null` 41 | if [[ -n "$prog" ]]; then 42 | export HELP_PAGER="$prog" 43 | break 44 | fi 45 | done 46 | fi 47 | $prog 48 | } 49 | 50 | # usage: 51 | # 52 | # some_function() { 53 | # help_args_func HELPFUNC $# [NEEDED] 54 | # ... 55 | # } 56 | 57 | help_args_func() { 58 | local func _x 59 | for (( _x=1; _x <= ${#FUNCNAME[*]} ; _x++ )); do 60 | func="${FUNCNAME[$_x]}" 61 | [[ "$func" =~ ^_|^help_ ]] || break 62 | done 63 | (( $2 >= ${3:-1} )) && return 0 # return true on sufficient args 64 | # not enough args. Show some help 65 | $1 | help_func_filter $func 66 | return 1 # return false on help 67 | } 68 | 69 | # help_func_filter FUNC 70 | 71 | # filters STDIN for FUNC and any following lines starting with a blank 72 | 73 | help_func_filter() { 74 | awk "BEGIN { t=\"\" } 75 | /^$func[^a-z0-9_]/ { p=1; 76 | if (length(t) > 0){print t}; 77 | print 78 | next 79 | } 80 | /^[ ]*$/ { if (p) {exit} 81 | t=\"\" 82 | next 83 | } 84 | /^[^ ]/ { if(!p) { 85 | t = t \$0 86 | next 87 | } else { 88 | print 89 | } 90 | }" 91 | } 92 | 93 | # end of help-util.sh 94 | -------------------------------------------------------------------------------- /maybe-install-bash-lib: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # maybe-install-bash-lib LIBS .. 3 | # 4 | # This utility can be used with other bash libraries that depend on bash-lib, 5 | # as part of their own installation procuedure, to ensure that the bash-lib is 6 | # installed. 7 | # 8 | # LIBS can be any of the files intalled as part of the bash-lib set. 9 | # 10 | # The file name can be given with, or without the ".sh" suffix, and with, or 11 | # withou the leading directory. For example, as "sh-utils", "sh-utils.sh", or 12 | # "$HOME/lib/sh-utils.sh" 13 | 14 | talk(){ echo 1>&2 "$*" ; } 15 | 16 | install_bash_lib() { 17 | tmpdir=/tmp/bash-lib.$$ 18 | logdir=$tmpdir.log 19 | local status 20 | talk "Installing github.com/aks/bash-lib .." 21 | if ( set -x 22 | mkdir $tmpdir 23 | cd $tmpdir 24 | git clone https://github.com/aks/bash-lib.git 25 | cd ./bash-lib 26 | make install 27 | ) &> $logdir ; then 28 | talk "github.com/aks/bash-lib installed" 29 | status=0 30 | else 31 | status=$? 32 | talk "An error occurred installing bash-lib." 33 | fi 34 | talk "See $logdir for details." 35 | exit $status 36 | } 37 | 38 | # if file_paths_missing $PATH ; then 39 | # install-files 40 | # fi 41 | # 42 | # Return success (0) if PATH needs to be installed 43 | 44 | file_paths_missing() { 45 | local file prog 46 | for prog in $@ ; do 47 | case "$prog" in 48 | /*.sh) file="$prog" ;; 49 | /*) file="$prog.sh" ;; 50 | *.sh) file="$HOME/lib/$prog" ;; 51 | *) file="$HOME/lib/$prog.sh" ;; 52 | esac 53 | if [[ ! -f $file ]]; then 54 | return 0 55 | fi 56 | done 57 | return 1 58 | } 59 | 60 | if (( $# > 0 )); then 61 | libs="$*" 62 | else 63 | libs="sh-utils list-utils" 64 | fi 65 | 66 | if file_paths_missing $libs ; then 67 | install_bash_lib 68 | fi 69 | 70 | exit 0 71 | -------------------------------------------------------------------------------- /no-test-sync-files.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # copyright 2006-2014 Alan K. Stebbens 3 | # 4 | # Test module for date-utils.sh 5 | 6 | export PATH=.:$PATH:$HOME/lib 7 | 8 | source sync-files.sh 9 | 10 | # Ja Fe Ma Ap Ma Jn Jl Au Se Oc No De 11 | month_limits=( 31 28 31 30 31 30 31 31 30 31 30 31 ) 12 | 13 | next_year() { 14 | month=1 15 | : $(( ++year )) 16 | } 17 | 18 | next_month() { 19 | if (( ++month > 12 )) ; then 20 | day=1 21 | next_year 22 | fi 23 | } 24 | 25 | # next_date 26 | next_day() { 27 | if (( day++ >= month_limits[month-1] )) ; then 28 | if (( month == 2 && ((year % 100) == 0) && ((year % 400) == 0) )) ; then # Feb? 29 | if (( day > 29 )); then 30 | next_month 31 | fi 32 | else # not leap year 33 | next_month 34 | fi 35 | fi 36 | } 37 | 38 | test_a_date() { 39 | local date="$1" 40 | yr=`get_year file-$date` 41 | mo=`get_month file-$date` 42 | dy=`get_day file-$date` 43 | if (( 10#$yr < 100 )) ; then 44 | if (( 10#$yr != (year % 100) )) ; then 45 | error "Bad year '$yr' for date: $date" 46 | fi 47 | elif (( 10#$yr != 10#$year )); then 48 | error "Bad year '$yr' for date: $date" 49 | fi 50 | if (( 10#$mo != 10#$month )) ; then 51 | error "Bad month '$mo' for date: $date" 52 | fi 53 | if (( 10#$dy != 10#$day )) ; then 54 | error "Bad day '$dy' for date: $date" 55 | fi 56 | } 57 | 58 | test_get_funs() { 59 | year=1995 60 | month=1 61 | day=1 62 | last_year=0 63 | while (( year < 2011 )); do 64 | if (( last_year != year )) ; then 65 | printf " %s" $year 66 | fi 67 | last_year=$year 68 | date1=`printf "%4d-%02d-%02d" $year $month $day` 69 | date2=`printf "%4d%02d%02d" $year $month $day` 70 | date3=`printf "%2d%02d%02d" $((year % 100)) $month $day` 71 | test_a_date "$date1" 72 | test_a_date "$date2" 73 | test_a_date "$date3" 74 | next_day 75 | done 76 | echo "" 77 | } 78 | 79 | test_get_funs 80 | exit 81 | -------------------------------------------------------------------------------- /option-utils.sh: -------------------------------------------------------------------------------- 1 | # option-utils.sh -- 2 | # Copyright 2015-2022 Alan K. Stebbens 3 | 4 | # utility for collecting lists of options and arguments 5 | 6 | OPTION_UTILS_VERSION="option-utils.sh v1.1" 7 | [[ "$OPTION_UTILS_SH" = "$OPTION_UTILS_VERSION" ]] && return 8 | OPTION_UTILS_SH="$OPTION_UTILS_VERSIONS" 9 | 10 | source help-util.sh 11 | 12 | option_help() { 13 | cat 1>&2 <<'EOF' 14 | The option-util.sh library is a small set of functions to manage 15 | building options and arguments, which is often needed in the 16 | development of command-line utilities. 17 | 18 | These functions use two global variables: `option_pairs` and 19 | `options`. The `option_pairs` variable is used to accumulate pairs 20 | of options and arguments, eg: "-F FILE", while `options` is used to 21 | accumulate single character options that can be clustered behind a 22 | single dash "-". 23 | 24 | All of the accumulated options and arguments can be output with 25 | `all_opts`. 26 | 27 | init_opts # empty "option_pairs" and "options" 28 | 29 | reset_opts # same as init_opts 30 | 31 | add_optarg OPTION ARG .. # add OPTION and ARG to the option_pairs list 32 | 33 | add_option OPTION .. # add OPTION to the single options list 34 | add_opt OPTION .. # eg: add_arg -c -d .. or add_arg c d .. 35 | 36 | all_opts # outputs both option_pairs and options 37 | EOF 38 | } 39 | help_option() { option_help ; } 40 | 41 | init_options() { 42 | declare -g option_pairs= 43 | declare -g options='-' # a list of clustered single options 44 | } 45 | 46 | reset_options() { init_options ; } 47 | 48 | add_optarg() { 49 | help_args_func option_help $# 2 || return 50 | __add_optarg "$@" 51 | } 52 | 53 | __add_optarg() { 54 | while (( $# > 0 )); do 55 | case "$1" in 56 | -*) option_pairs+=" $1 $2" ;; 57 | *) option_pairs+=" -$1 $2" ;; 58 | esac 59 | shift 2 60 | done 61 | } 62 | 63 | add_option() { 64 | help_args_func option_help $# 1 || return 65 | __add_option "$@" 66 | } 67 | 68 | __add_option() { 69 | while (( $# > 0 )); do 70 | case "$1" in 71 | -*) options+="${1#-}" ;; # append option without leading '-' 72 | *) options+="$1" ;; 73 | esac 74 | shift 75 | done 76 | } 77 | 78 | add_opt() { add_option "$@" ; } 79 | __add_opt() { __add_option "$@" ; } 80 | 81 | all_opts() { 82 | if (( ${#options} == 1 )); then # don't output empty '-' 83 | echo "$option_pairs" 84 | else 85 | echo "$option_pairs $options" # output both the option_pairs and options 86 | fi 87 | } 88 | all_args() { all_opts "$@" ; } 89 | __all_opts() { all_opts "$@" ; } 90 | __all_args() { all_opts "$@" ; } 91 | 92 | # end of option-utils.sh 93 | -------------------------------------------------------------------------------- /prompt-colors.txt: -------------------------------------------------------------------------------- 1 | Set Display Attributes 2 | 3 | Set Attribute Mode [{attr1};...;{attrn}m 4 | Sets multiple display attribute settings. The following lists standard attributes: 5 | 6 | 0 Reset all attributes 7 | 1 Bright 8 | 2 Dim 9 | 4 Underscore 10 | 5 Blink 11 | 7 Reverse 12 | 8 Hidden 13 | 14 | Foreground Colours 15 | 30 Black 16 | 31 Red 17 | 32 Green 18 | 33 Yellow 19 | 34 Blue 20 | 35 Magenta 21 | 36 Cyan 22 | 37 White 23 | 24 | Background Colours 25 | 40 Black 26 | 41 Red 27 | 42 Green 28 | 43 Yellow 29 | 44 Blue 30 | 45 Magenta 31 | 46 Cyan 32 | 47 White 33 | -------------------------------------------------------------------------------- /real-utils.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # real-utils.sh 3 | # 4 | # Copyright 2015 Alan K. Stebbens 5 | 6 | 7 | REAL_UTILS_VERSION="real-utils.sh v1.5" 8 | [[ "$REAL_UTILS_SH" = "$REAL_UTILS_VERSION" ]] && return 9 | REAL_UTILS_SH="$REAL_UTILS_VERSION" 10 | 11 | real_help() { 12 | cat 1>&2 <<'EOF' 13 | real-utils.sh is a bash library that enables real number arithmetic in bash 14 | scripts. Real numbers are managed as flaoting point strings in the format 15 | "X.Y", where X is the integer portion, and "Y" is the fractional part. 16 | 17 | Usage: 18 | 19 | source real-utils.sh 20 | 21 | real_compute "EXPRESSIN" [SCALE] 22 | 23 | real_eval "EXPRESSION" [SCALE] 24 | 25 | real_cond EXPRESSION [SCALE] 26 | 27 | real_int REAL 28 | 29 | real_frac REAL 30 | 31 | real_help 32 | 33 | Descriptions: 34 | 35 | real_compute "EXPRESSION" [SCALE] 36 | 37 | The `real_compute` bash function evaluates `EXPRESSION` using syntax, operators 38 | and functions as described in the `bc` manual. All numbers and variables 39 | within `EXPRESSION` are interpreted by `bc`. The result of the computation is 40 | output to `STDOUT`. 41 | 42 | If an error occurs, there is no indication. This function does not set a 43 | return code, nor does it set the shell status variable `$?`. Use `real_eval` 44 | for those effects. 45 | 46 | In addition to the operators and functions defined by `bc`, the following 47 | additional functions are also made available within the `EXPRESSION`: 48 | 49 | abs(x) deg(x) log10(x) rad(x) 50 | acos(x) exp(x) logn(x) round(x,s) 51 | asin(x) frac(x) ndeg(x) sin(x) 52 | atan(x) int(x) pi() tan(x) 53 | cos(x) log(x) pow(x,y) 54 | 55 | To see the `bc` definitions of these functions, use the `real_functions` 56 | function. 57 | 58 | real_eval "EXPRESSION" [SCALE] 59 | 60 | The `real_eval` bash function invokes `real_compute` on the arguments, prints 61 | the result on `STDOUT`, and returns with the `bc` return code `$?` (0 or 1, for 62 | success or error, respectively). 63 | 64 | real_cond "EXPRESSION" [SCALE] 65 | 66 | `EXPRESSION` is a real number conditional which should evaluate to 1 or 0. The 67 | return status is 0 for true, 1 for false. Example usage: 68 | 69 | if real_cond "$num < $max" 2 ; then 70 | ... 71 | fi 72 | 73 | 74 | real_scale=NUM 75 | 76 | Set the precision of subsequent real number arithmetic results. The 77 | default is 2. 78 | 79 | real_int REAL -- outputs the integer portion of a REAL number 80 | real_frac REAL -- outputs the fractional portion of a REAL number 81 | 82 | sin R, cos R, tan R -- trig functions on radians R 83 | asin X, acos X, atan X -- inverse trig functions 84 | cotan X, sec X, cosec X -- cotangent, secant, cosecant 85 | arccot X -- arc-cotangent 86 | hypot X Y -- hypotenuse X, Y [sqrt(X^2 + Y^2)] 87 | sqrt X -- square-root of X 88 | logn X, log X -- natural log, log base 10 89 | exp X -- exponent X of E (e.g., e^X) 90 | pow X Y -- power function [X^Y] 91 | rad D -- convert degrees D to radians 92 | deg R -- convert radians R to degrees 93 | ndeg R -- convert radians R to natural degrees (0..360) 94 | round X S -- Round X to S decimals. When S=0, rounds to the nearest integer. 95 | real_int X -- outputs integer portion of X 96 | real_frac X -- outputs fractional portion of X 97 | abs X -- Return the absolute value of X. 98 | 99 | PI = 3.141592653589793 100 | TAU = 6.283185307179586 # 2*PI 101 | E = 2.718281828459045 102 | 103 | EOF 104 | } 105 | help_real() { real_help ; } 106 | 107 | # Default scale used by real functions. 108 | [[ -n "$real_scale" ]] || export real_scale=2 109 | 110 | # real_functions -- define our built-in functions (for bc) 111 | 112 | real_functions() { 113 | cat <<'EOF' 114 | define pi() { auto r,s ; s=scale; scale=10 ; r=4*a(1); scale=s ; return(r) ; } 115 | define int(x) { auto r,s ; s=scale; scale=0 ; r=((x - x%1)/1) ; scale=s ; return(r) ; } 116 | define frac(x) { auto r,s ; s=scale; scale=0 ; r=(x%1) ; scale=s ; return(r) ; } 117 | define sin(x) { return(s(x)) ; } 118 | define cos(x) { return(c(x)) ; } 119 | define tan(x) { return(s(x)/c(x)) ; } 120 | define asin(x) { return(2*a(x/(1+sqrt(1-(x^2))))) ; } 121 | define acos(x) { return(2*a(sqrt(1-(x^2))/(1+x))) ; } 122 | define atan(x) { return(a(x)) ; } 123 | define logn(x) { return(l(x)) ; } 124 | define log(x) { return(l(x)/l(10.0)) ; } 125 | define log10(x) { return(log(x)) ; } 126 | define exp(x) { return(e(x)) ; } 127 | define pow(x,y) { return(x^y) ; } 128 | define rad(x) { return(x*pi()/180) ; } 129 | define deg(x) { return(x*180/pi()) ; } 130 | define ndeg(x) { return((360 + deg(x))%360) ; } 131 | define round(x,s) { auto r,o 132 | o=scale(x) ; scale=s+1 133 | r = x + 5*10^(-(s+1)) 134 | scale=s 135 | return(r/1) ; } 136 | define abs(x) { if ( x<0 ) return(-x) else return(x) ; } 137 | EOF 138 | } 139 | 140 | # real_args_or_input "$@" 141 | # 142 | # Return the 2 arguments, with special quoting on arg1, or read from STDIN 143 | 144 | real_args_or_input() { 145 | if (( $# == 0 )) ; then 146 | local -a args 147 | local func="${FUNCNAME[1]}" 148 | while (( ${#args[*]} == 0 )); do 149 | read -p "$func? " -a args 150 | done 151 | echo "set - '${args[0]}' ${args[1]}" 152 | else 153 | echo "set - '$1' $2" 154 | fi 155 | } 156 | # real_compute EXPR [SCALE] 157 | # 158 | # Basic computational engine: performs bc-based evaluation, but does not do 159 | # shell status or return code management. 160 | 161 | real_compute() { 162 | eval "`real_args_or_input \"$@\"`" 163 | ( real_functions 164 | echo "scale=${2:-$real_scale} ; " 165 | echo "$1" 166 | ) | bc -lq 2>/dev/null 167 | } 168 | 169 | # real_eval 'EXPRESSION' [SCALE] 170 | # 171 | # Performs evaluation of an arithmentic expression, supporting real numbers. 172 | # 173 | # $? == 1 => bad calculation 174 | 175 | real_eval() { 176 | eval "`real_args_or_input \"$@\"`" 177 | local stat=0 res=0 scale="${2:-$real_scale}" 178 | if (( $# > 0 )) ; then 179 | res=`real_compute "$1" $scale` 180 | stat=$? 181 | if [[ $stat -eq 0 && -z "$res" ]]; then stat=1; fi 182 | fi 183 | echo $res 184 | return $stat 185 | } 186 | 187 | # real_cond CONDITION [SCALE] 188 | # 189 | # Test a conditional expression using real numbers. 190 | # 191 | # if real_cond "10.1 > 9.3" 1 192 | # ... 193 | # fi 194 | 195 | real_cond() { 196 | eval "`real_args_or_input \"$@\"`" 197 | local cond=0 scale=${2:-$real_scale} 198 | if (( $# > 0 )); then 199 | cond=`real_compute "$1" $scale` 200 | if [[ -z "$cond" ]]; then cond=0; fi 201 | if [[ "$cond" != 0 && "$cond" != 1 ]]; then cond=0; fi 202 | fi 203 | local stat=$((cond == 0)) 204 | return $stat 205 | } 206 | 207 | # real_int [REAL] # return the integer part of a REAL 208 | # real_frac [REAL] # return the fractional part of a REAL 209 | # 210 | # Alternative usage: 211 | # 212 | # echo REAL | real_int 213 | # echo REAL | real_frac 214 | # 215 | # Note: these are simple text functions on the string representation of real 216 | # numbers. 217 | 218 | real_int() { 219 | eval "`real_args_or_input \"$@\"`" 220 | echo "${1%.*}" 221 | } 222 | 223 | real_frac() { 224 | eval "`real_args_or_input \"$@\"`" 225 | if [[ "$1" =~ \. ]]; then 226 | echo ".${1#*.}" 227 | fi 228 | } 229 | 230 | 231 | # Math functions 232 | # 233 | # All math functions operate with scale=8 unless overriden 234 | # 235 | # Some handy trig constants 236 | 237 | PI='3.141592653589793' 238 | TAU='6.283185307179586' # 2*PI 239 | E='2.718281828459045' 240 | 241 | # Trig functions 242 | # 243 | # sin REAL [SCALE=8] 244 | # cos REAL 245 | # tan REAL 246 | # 247 | # cotan REAL - cotangent 248 | # sec REAL - secant 249 | # csc REAL - cosecant 250 | # 251 | # arcsin REAL - arcsine aka "asin" 252 | # arccos REAL - arcosine aka "acos" 253 | # arctan REAL - arctan aka "atan" 254 | # 255 | # pi = 3.141592654 256 | # tau = 2*pi 257 | 258 | sin() { eval `real_args_or_input "$@"` ; real_eval "s($1)" ${2:-8} ; } 259 | cos() { eval `real_args_or_input "$@"` ; real_eval "c($1)" ${2:-8} ; } 260 | tan() { eval `real_args_or_input "$@"` ; real_eval "(s($1)/c($1))" ${2:-8} ; } 261 | 262 | cotan() { eval `real_args_or_input "$@"` ; real_eval "(c($1)/s($1))" ${2:-8} ; } 263 | sec() { eval `real_args_or_input "$@"` ; real_eval "(1/c($1))" ${2:-8} ; } 264 | cosec() { eval `real_args_or_input "$@"` ; real_eval "(1/s($1))" ${2:-8} ; } 265 | csc() { eval `real_args_or_input "$@"` ; cosec "$@" ; } 266 | 267 | # hypot X Y [SCALE] 268 | hypot() { eval `real_args_or_input "$@"` ; real_eval "sqrt(($1)^2 + ($2)^2)" ${3:-8} ; } 269 | 270 | # Inverse trig funcs 271 | asin() { eval `real_args_or_input "$@"` ; real_eval "asin($1)" ${2:-8} ; } 272 | acos() { eval `real_args_or_input "$@"` ; real_eval "acos($1)" ${2:-8} ; } 273 | atan() { eval `real_args_or_input "$@"` ; real_eval "atan($1)" ${2:-8} ; } 274 | 275 | arccot() { eval `real_args_or_input "$@"` ; real_eval "(($PI/2)-a($1))" ${2:-8} ; } 276 | arcsin() { eval `real_args_or_input "$@"` ; asin "$@" ; } 277 | arccos() { eval `real_args_or_input "$@"` ; acos "$@" ; } 278 | arctan() { eval `real_args_or_input "$@"` ; atag "$@" ; } 279 | 280 | # Log functions 281 | logn() { eval `real_args_or_input "$@"` ; real_eval "l($1)" ${2:-8} ; } 282 | log10() { eval `real_args_or_input "$@"` ; real_eval "l($1)/l(10.0)" ${2:-8} ; } 283 | log() { eval `real_args_or_input "$@"` ; log10 "$@" ; } 284 | exp() { eval `real_args_or_input "$@"` ; real_eval "e($1)" ${2:-8} ; } 285 | 286 | # Power function 287 | pow() { eval `real_args_or_input "$@"` ; real_eval "$1^$2" ${2:-8} ; } 288 | 289 | # rad x -- convert degrees to radians 290 | # deg x -- convert radians to degrees 291 | # ndeg x -- convert radians to normalized degrees (0 <= d <= 360) 292 | # 293 | # 1 rad == 180 deg / PI 294 | # 1 deg == PI rad / 180 295 | 296 | deg() { eval `real_args_or_input "$@"` ; real_eval "deg($1)" ${2:-8} ; } 297 | ndeg() { eval `real_args_or_input "$@"` ; real_eval "ndeg($1)" ${2:-8} ; } 298 | rad() { eval `real_args_or_input "$@"` ; real_eval "rad($1)" ${2:-8} ; } 299 | 300 | # absolute X 301 | abs() { eval `real_args_or_input "$@"` ; real_eval "abs($1)" ; } 302 | 303 | # Round NUM [SCALE] -- round NUM at the SCALE 304 | 305 | round() { eval `real_args_or_input "$@"` ; real_eval "round($1, ${2:-(scale($1)-1)})" ${2:-8} ; } 306 | 307 | 308 | # vim: sw=2: ai: 309 | -------------------------------------------------------------------------------- /reset-util-vars.sh: -------------------------------------------------------------------------------- 1 | # reset-util-vars.sh 2 | # 3 | # each module checks a variable to avoid recursive sourcing. These variables 4 | # are not exported, so subshells will always source correctly. 5 | # 6 | # This function definition must be sourcd into bash, and then the function 7 | # invoked in order to clear these variables within the current bash context. 8 | 9 | reset_util_vars() { 10 | unset ARG_UTILS_SH 11 | unset CALENDAR_UTILS_SH 12 | unset DATE_UTILS_SH 13 | unset HASH_UTILS_SH 14 | unset LIST_UTILS_SH 15 | unset REAL_UTILS_SH 16 | unset RUN_UTILS_SH 17 | unset OPTION_UTILS_SH 18 | unset SH_UTILS_SH 19 | unset TALK_UTILS_SH 20 | unset TEST_UTILS_SH 21 | unset TEXT_UTILS_SH 22 | unset TIME_UTILS_SH 23 | } 24 | -------------------------------------------------------------------------------- /run-utils.sh: -------------------------------------------------------------------------------- 1 | # run-utils.sh 2 | # 3 | # handy functions for running system commands in bash scripts, with 4 | # optional support for $norun and $verbose modes. 5 | # 6 | # Copyright 2006-2022 Alan K. Stebbens 7 | # 8 | 9 | RUN_UTILS_VERSION="sh-utils.sh v1.8" 10 | 11 | [[ "$RUN_UTILS_SH" = "$RUN_UTILS_VERSION" ]] && return 12 | RUN_UTILS_SH="$RUN_UTILS_VERSION" 13 | 14 | source talk-utils.sh 15 | source help-util.sh 16 | 17 | run_utils_help() { 18 | cat 1>&2 <<'EOF' 19 | Shell utility functions for running system commands: 20 | 21 | run COMMAND ARGS .. Show `COMMAND` `ARGS` if `$norun` or `$verbose`; 22 | run `COMMAND` unless `$norun`. 23 | 24 | safe_run COMMAND ARGS ... Same as "run", but always executes. 25 | 26 | rm_file_later FILE Cause `FILE` to be removed upon program exit. 27 | 28 | add_trap "CMD" SIGNAL .. Add `CMD` to the trap list for `SIGNAL` 29 | 30 | EOF 31 | } 32 | help_run_utils() { run_utils_help ; } 33 | 34 | # rm_file_later FILE 35 | # 36 | # schedule file for later removal on program exit 37 | 38 | rm_file_later() { 39 | help_args_func run_utils_help $# || return 1 40 | local rmfiles="/tmp/rmfiles-$$.sh" 41 | local lockfile=$rmfiles.lock 42 | touch $rmfiles 43 | lockfile $lockfile # lock the semaphore 44 | ( fgrep -v $rmfiles $rmfiles ; echo "/bin/rm -f '$1'" ; echo "/bin/rm -f $rmfiles" ) > $rmfiles.new 45 | mv -f $rmfiles.new $rmfiles 46 | rm -f $lockfile # release the semaphore 47 | add_trap "/bin/sh $rmfiles" EXIT HUP INT TERM 48 | add_trap "exit" EXIT HUP INT TERM 49 | } 50 | 51 | # add_trap "Command" SIGNAL .. 52 | 53 | # add_trap CMD [SIGNAL ...] 54 | add_trap() { 55 | local cmd="$1" ; shift 56 | local sig cmds 57 | for sig in "$@" ; do 58 | cmds=`trap_cmd $sig` 59 | cmds="$cmds${cmds:+; }$cmd" 60 | trap "$cmds" $sig 61 | done 62 | } 63 | 64 | # trap_cmd SIGNAL 65 | trap_cmd() { 66 | local cmds=`trap -p $1` 67 | cmds="${cmds#*\'}" 68 | cmds="${cmds%\'*}" 69 | echo "$cmds" 70 | } 71 | 72 | # run COMMAND ARGS ... 73 | 74 | run() { 75 | help_args_func run_utils_help $# 1 || return 1 76 | if [[ -n "$norun" ]]; then 77 | talk "(norun) $@" 78 | else 79 | safe_run "$@" 80 | fi 81 | return 0 82 | } 83 | 84 | # safe_run COMMAND ARGS 85 | # Safe run -- run command even in "$norun" mode 86 | 87 | safe_run() { 88 | help_args_func run_utils_help $# 1 || return 1 89 | if [[ -n "$verbose$norun" ]]; then 90 | talk ">> $@" 91 | fi 92 | if ! eval "$@" ; then 93 | code=$? 94 | return $code 95 | fi 96 | return 0 97 | } 98 | 99 | # end of run-utils.sh 100 | # vim: sw=2 ai 101 | -------------------------------------------------------------------------------- /sh-utils.sh: -------------------------------------------------------------------------------- 1 | # sh-utils.sh 2 | # 3 | # handy functions for writing bash-based scripts 4 | # 5 | # Copyright 2006-2022 Alan K. Stebbens 6 | # 7 | 8 | SH_UTILS_VERSION="sh-utils.sh v2.2" 9 | 10 | [[ "$SH_UTILS_SH" = "$SH_UTILS_VERSION" ]] && return 11 | SH_UTILS_SH="$SH_UTILS_VERSION" 12 | 13 | # Need to bring these in with sh-utils -- because I have many 14 | # scripts that were developed before the refactoring. 15 | 16 | source help-util.sh 17 | source talk-utils.sh 18 | source run-utils.sh 19 | source option-utils.sh 20 | 21 | help_sh_utils() { 22 | help_pager <<'EOF' 23 | The `sh-utils.sh` includes several groups of functions which collectively are 24 | quite useful in the development of command-line utilities and other system 25 | scripts. 26 | 27 | The following are separate modules that are included with sh-utils: 28 | 29 | - arg-utils - help with arguments or STDIN 30 | - help-util - help with help on selected functions 31 | - option-utils - manage option and argument lists 32 | - run-utils - run system commands, with $norun and $verbose 33 | - talk-utils - conditional output to STDERR 34 | 35 | In addition, `sh-utils.sh` defines some additional functions: 36 | 37 | rm_file_later FILE Cause `FILE` to be removed upon program exit. 38 | 39 | add_trap "CMD" SIGNAL .. Add `CMD` to the trap list for `SIGNAL` 40 | 41 | rm_trap "CMD" SIGNAL .. Remove 'CMD' from the trap list for `SIGNAL` 42 | 43 | get_traps SIGNAL Output (on STDOUT) the trap commands for SIGNAL 44 | 45 | filter_traps CMD Filter CMD out of the newline-separated commands on STDIN 46 | 47 | reset_traps SIGNAL .. Reset (remove) all traps on `SIGNAL` 48 | 49 | fn_exists FUNCTION Return 0 (true) if `FUNCTION` exists; 1 (false) otherwise 50 | 51 | EOF 52 | } 53 | 54 | sh_utils_help() { help_sh_utils ; } 55 | 56 | # rm_file_later FILE 57 | # 58 | # schedule file for later removal on program exit 59 | 60 | rm_file_later() { 61 | help_args_func help_sh_utils $# 1 || return 1 62 | __rm_file_later "$@" 63 | } 64 | 65 | __rm_file_later() { 66 | local rmfiles="/tmp/rmfiles-$$.sh" 67 | touch $rmfiles 68 | ( fgrep -v $rmfiles $rmfiles ; echo "/bin/rm -f '$1'" ; echo "/bin/rm -f $rmfiles" ) > $rmfiles.new 69 | mv -f $rmfiles.new $rmfiles 70 | add_trap "/bin/sh $rmfiles" EXIT HUP INT TERM 71 | add_trap "exit" EXIT HUP INT TERM 72 | } 73 | 74 | # add_trap "Command" SIGNAL .. 75 | 76 | add_trap() { 77 | help_args_func help_sh_utils $# 2 || return 1 78 | __add_trap "$@" 79 | } 80 | 81 | __add_trap() { 82 | local cmd="$1" ; shift 83 | local sig traps 84 | for sig in "$@" ; do 85 | traps="`get_traps $sig | filter_traps \"$cmd\"`" 86 | if [[ -n "$traps" ]]; then 87 | trap "$traps ; $cmd" $sig 88 | else 89 | trap "$cmd" $sig 90 | fi 91 | done 92 | } 93 | 94 | # rm_trap "CMD" SIGNAL 95 | 96 | rm_trap() { 97 | help_args_func help_sh_utils $# 2 || return 1 98 | __rm_trap "$@" 99 | } 100 | 101 | __rm_trap() { 102 | local cmd="$1" ; shift 103 | local sig traps 104 | for sig in "$@" ; do 105 | traps="`__get_traps $sig | __filter_traps \"$cmd\"`" 106 | if [[ -n "$traps" ]]; then 107 | trap "$cmd" $sig 108 | else 109 | trap - $sig 110 | fi 111 | done 112 | trap -p $sig 1>&2 113 | } 114 | 115 | # echo "some traps" | filter_traps CMD 116 | 117 | filter_traps() { 118 | help_args_func help_sh_utils $# 1 || return 1 119 | __filter_traps "$@" 120 | } 121 | 122 | __filter_traps() { 123 | local cmd="$1" ; shift 124 | fgrep -v "$cmd" | sed -e "s/$'\n'/ ; /g" 125 | } 126 | 127 | # reset_traps SIG 128 | 129 | reset_traps() { 130 | help_args_func help_sh_utils $# 1 || return 1 131 | __reset_traps "$@" 132 | } 133 | 134 | __reset_traps() { 135 | local sig 136 | for sig in "$@" ; do 137 | trap - $sig 138 | done 139 | } 140 | 141 | # get_traps SIGNAL 142 | 143 | get_traps() { 144 | help_args_func help_sh_utils $# 1 || return 1 145 | __get_traps "$@" 146 | } 147 | 148 | __get_traps() { 149 | trap -p $1 | sed -E "s/^trap -- '([^']*)' SIG.*$/\1/" 150 | } 151 | 152 | # fn_exists FUNCTION 153 | # 154 | # Return 0 (true) or 1 (false) if FUNCTION is defined. 155 | 156 | fn_exists() { declare -f "$1" >/dev/null ; } 157 | 158 | 159 | # end of sh-utils.sh 160 | # vim: sw=2 ai 161 | -------------------------------------------------------------------------------- /sync-files.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2006-2014 by Alan K. Stebbens 3 | # 4 | # sync-files.sh -- library for managing file syncs 5 | # 6 | # usage: 7 | # 8 | # export PATH=$PATH:$HOME/lib/ 9 | # . sync-files.sh 10 | # 11 | # FILES_DIR=PATH 12 | # Set this to the directory containing the files to be synchronized 13 | # 14 | # DEST_INFO=( USER@HOST/PATH1 USER@HOST/PATH2 ... ) 15 | # 16 | # USE_DATED_FILES=1 17 | # set this if the files will have date suffixes; e.g., 18 | # FILENAME-YYYY-MM-DD.EXT 19 | # 20 | # USE_DATE_DIRS=1 21 | # set this if the dated files should be filed into dated directories 22 | # 23 | # FILE_ITERATOR 24 | # Set this to an expression which evaluates to one or more name parts, 25 | # to be used in a loop. 26 | # 27 | # Example: FILE_ITERATOR='returns aareturns perfstats' 28 | # 29 | # FILENAME_PREFIX 30 | # the filename prefix; if not defined will be "file-" 31 | # 32 | # FILENAME_SUFFIX 33 | # the filename suffix, occuring after the iterator. If not defined, 34 | # is empty. 35 | # 36 | # FILENAME_EXT 37 | # the extension. If not defined, use ".txt". 38 | # 39 | # FILENAME_BUILDER 40 | # set this to an expression that evaluates to a filename using 41 | # the variable "$file_iter" or "$file". 42 | # 43 | # Example: FILENAME_BUILDER="mfolio-$file_iter-$(date +%F).html" 44 | # 45 | # FILE_GENERATOR 46 | # Set this to an expression which evaluates to a command that generates 47 | # the current file, using the variables, $file_iter, $file, $date. 48 | # The command is invoked using the "run" function, which applies $norun 49 | # and $verbose. 50 | # 51 | # Example: 52 | # 53 | # FILE_GENERATOR="dfi -pmf -mfolio-$file_iter -html -csslink Returns.css -o mfolio-$file_iter-" 54 | # 55 | # Provided functions: 56 | # 57 | # build_files 58 | # sync_files 59 | 60 | 61 | RSYNC="rsync -e 'ssh -i $(echo ~build)/.ssh/id_rsa' " 62 | 63 | # The first destination is the development host -- it receives both the dated 64 | # suffix files and the "plain" files too. 65 | # 66 | # The additional destionation hosts receive only the plain name of the file 67 | # (without a date suffix). 68 | 69 | if [[ -z "$DEST_INFO" ]]; then 70 | DEST_INFO=( ) 71 | fi 72 | 73 | # for development 74 | 75 | export PATH=/usr/local/bin:~build/bin:/Marketocracy/Scripts/build/bin:$PATH 76 | 77 | show_standard_opts() { 78 | cat 1>&2 <&2 < $2" 176 | fi 177 | run $RSYNC "$1" "$2" 178 | } 179 | 180 | sync_files() { 181 | local first=1 182 | for destinfo in "${DEST_INFO[@]}" ; do 183 | for file_iter in ${FILE_ITERATOR:?'FILE_ITERATOR not defined!'} ; do 184 | datedfile=$( ls -t1 ${FILENAME_PREFIX:-file-}$file_iter${FILENAME_SUFFIX:-}-*.${FILENAME_EXT:-.txt} | head -1 ) 185 | if [[ -n "$datedfile" && -f "$datedfile" ]]; then 186 | 187 | # the first desition is the development target -- we 188 | # save the dated copies to that host (and only that host) 189 | 190 | if [[ -n "$first" ]]; then 191 | sync_a_dated_file "$datedfile" "$destinfo" 192 | fi 193 | 194 | # save the current file to the "plain" name destination 195 | plainfile="${FILENAME_PREFIX:-file-}$file_iter${FILENAME_SUFFIX:-}.${FILENAME_EXT:-.txt}" 196 | 197 | sync_a_file "$datedfile" "$destinfo/$plainfile" 198 | 199 | fi 200 | done 201 | 202 | # if we are in dev mode, stop here 203 | [[ -n "$dev_mode" ]] && break 204 | first= 205 | done 206 | } 207 | 208 | # get_date_dir FILENAME-YYYY-MM-DD.EXT [DIRECTORY] 209 | 210 | get_dated_dir() { 211 | local file="$1" 212 | local dest="$2"${2:+/} 213 | local year=$( get_year "$file") 214 | local month=$( get_month "$file") 215 | local day=$( get_day "$file") 216 | local thepath="$dest$year/$month/$day" 217 | echo "$thepath" 218 | } 219 | 220 | # sync_a_dated_file FILE-YYYY-MM-DD.html DESTSPEC:DESTPATH/ 221 | # 222 | # Using the YYYY, MM, DD parts of the file name, sync the file to a 223 | # dated directory path of: DESTPATH/YYYY/MM/DD/ 224 | 225 | sync_a_dated_file() { 226 | local thepath="`get_dated_dir \"$1\" \"$2\"`" 227 | remote_mkdir "$thepath" 228 | sync_a_file "$1" "$thepath/" 229 | } 230 | 231 | # remote_mkdir PATH 232 | # 233 | # Ensure that PATH exists on the remote host 234 | 235 | # mkdir_cache is a cache of filenames that have been made. We check this to 236 | # avoid remaking files multiple times. 237 | 238 | mkdir_cache="/tmp/mkdir_cache.$$" 239 | trap "rm -f $mkdir_cache ; exit" 0 1 2 3 240 | touch $mkdir_cache 241 | 242 | # remote_mkdir HOST:PATH 243 | remote_mkdir() { 244 | if ! fgrep -q "$1" $mkdir_cache ; then 245 | local host="${1%%:*}" 246 | local path="${1#*:}" 247 | run "ssh $host mkdir -pv $path" 248 | echo "$1" >>$mkdir_cache 249 | fi 250 | } 251 | 252 | dev_mode() { export dev_mode=1 ; export prod_mode= ; } 253 | prod_mode() { export dev_mode= ; export prod_mode=1 ; } 254 | 255 | setup() { 256 | run "cd `echo ${FILES_DIR:?'No FILES_DIR defined!'}`" 257 | } 258 | 259 | parse_opts() { 260 | 261 | [[ $# -eq 0 ]] && usage 262 | 263 | while getopts 'dD:pfFnNhsv' opt ; do 264 | if ! standard_opt "$opt" ; then 265 | if [[ -n "$extra_opts" ]]; then 266 | eval "$extra_opts $opt" 267 | fi 268 | fi 269 | done 270 | } 271 | 272 | standard_opt() { 273 | case "$1" in 274 | d) dev_mode ;; 275 | D) date=`parseDate.sh -f "$OPTARG"` ;; 276 | p) prod_mode ;; 277 | f) export build_files=1 ;; 278 | F) export force=1 ;; 279 | n) export norun=1 ;; 280 | N) export NORSYNC=1 ;; 281 | h) usage ;; 282 | s) export sync_files=1 ;; 283 | v) export verbose=1 ;; 284 | *) return 0 ;; 285 | esac 286 | return 1 287 | } 288 | 289 | # process_args "$@" 290 | 291 | parse_args() { 292 | OPTIND=1 293 | while [[ $# -gt 0 ]]; do 294 | case "$1" in 295 | dev|devel|development) dev_mode ;; 296 | pr|prod|production) prod_mode ;; 297 | *) error "I have no idea what you want to do! \"$1\"" ;; 298 | esac 299 | OPTIND=$(( OPTIND + 1 )) 300 | shift 301 | done 302 | } 303 | 304 | validate_args() { 305 | 306 | # if production mode, defaults are build and sync 307 | if [[ -n "$prod_mode" && -z "$build_files" && -z "$sync_files" ]]; then 308 | build_files=1 sync_files=1 309 | fi 310 | 311 | # if dev mode, default is build 312 | if [[ -n "$dev_mode" && -z "$build_files" && -z "$sync_files" ]]; then 313 | build_files=1 sync_files= 314 | fi 315 | 316 | # if syncing and neither -d nor -p, default to -d 317 | if [[ -n "$sync_files" && -z "$dev_mode" && -z "$prod_mode" ]]; then 318 | dev_mode 319 | fi 320 | 321 | if [[ -z "$prod_mode" && -z "$dev_mode" && -z "$build_files" && -z "$sync_files" ]]; then 322 | error "Need an option." 323 | fi 324 | 325 | if [[ -n "$date" ]]; then 326 | datearg=" -date $date" 327 | fi 328 | 329 | } 330 | 331 | set_rsync_opts() { 332 | local update='-u --size-only' 333 | if [[ -n "$forcefiles" ]]; then 334 | update= 335 | fi 336 | RSYNC="$RSYNC -aK ${NORSYNC:+-n} $update --chmod=ugo=rwX ${verbose:+-v}" 337 | } 338 | 339 | process_args() { 340 | [[ -n "$build_files" ]] && build_files 341 | [[ -n "$sync_files" ]] && sync_files 342 | } 343 | 344 | -------------------------------------------------------------------------------- /t.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | func1() { 4 | echo $(( ( 30001340 / 1340 ) * 500 / 15 )) 5 | } 6 | 7 | func2() { 8 | if (( $# > 0 )); then 9 | arg=$(( 10#$1 )) 10 | else 11 | local num 12 | read num 13 | arg=$(( 10#$num )) 14 | fi 15 | echo "arg is $arg" 16 | echo "100 * arg is $(( arg * 100 ))" 17 | } 18 | 19 | func3() { 20 | func1 | func2 21 | } 22 | 23 | func4() { 24 | arg=`func1` 25 | func2 "$arg" 26 | } 27 | 28 | touch log3 29 | 30 | time for ((i=0;i<=1000; i++)) ; do func3 >> log3 ; done 31 | 32 | touch log4 33 | 34 | time for ((i=0;i<=1000; i++)) ; do func4 >> log4 ; done 35 | 36 | exit 37 | -------------------------------------------------------------------------------- /t1.sh: -------------------------------------------------------------------------------- 1 | #/bin/sh 2 | # Copyright 2006-2022, Alan K. Stebbens 3 | # 4 | # Test module for list-utils.sh 5 | # 6 | 7 | export PATH=.:$HOME/lib:$PATH 8 | 9 | source list-utils.sh 10 | source test-utils.sh 11 | 12 | tlist1=( now is the time for all good men to come to the aid of their country ) 13 | 14 | items=( `grep_list tlist1 the` ) 15 | 16 | echo "$?" 17 | echo `join_list items` 18 | exit 19 | -------------------------------------------------------------------------------- /t2.sh: -------------------------------------------------------------------------------- 1 | #/bin/sh 2 | echo "\ 3 | s/ /%20/g ; s/\\\$/%24/g ; s/\>/%3E/g ; 4 | s/#/%23/g ; s/\%/%25/g ; s/\[/%5B/g ; 5 | s/'/%27/g ; s/\&/%26/g ; s/\]/%5D/g ; 6 | s/,/%2C/g ; s/\(/%28/g ; s/\^/%5E/g ; 7 | s/-/%2D/g ; s/\)/%29/g ; s/\`/%60/g ; 8 | s/=/%3D/g ; s/\*/%2A/g ; s/\{/%7B/g ; 9 | s/[\]/%5C/g ; s/\+/%2B/g ; s/\|/%7C/g ; 10 | s/\!/%21/g ; s/\//%2F/g ; s/\}/%7D/g ; 11 | s/\"/%22/g ; s/\/g ; s/\%7E/~/g" 25 | -------------------------------------------------------------------------------- /t3.sh: -------------------------------------------------------------------------------- 1 | #/bin/sh 2 | # Copyright 2006-2014, Alan K. Stebbens 3 | # 4 | # Test module for list-utils.sh 5 | # 6 | 7 | export PATH=.:$HOME/lib:$PATH 8 | 9 | source list-utils.sh 10 | source test-utils.sh 11 | 12 | test_10_print_list() { 13 | start_test 14 | words=( 15 | apple banana cherry dog elephant fox giraffe hawk indigo manzana milk november 16 | october december january february march april may june july august 17 | ) 18 | print_list words 19 | echo '' 20 | print_list words i=1 21 | echo '' 22 | print_list words i=2 c=5 23 | echo '' 24 | print_list words i=3 c=4 25 | echo '' 26 | print_list words c=3 i=4 27 | echo '' 28 | print_list words c=2 i=5 29 | echo '' 30 | end_test 31 | } 32 | 33 | 34 | init_tests "$@" 35 | run_tests 36 | summarize_tests 37 | 38 | exit 39 | -------------------------------------------------------------------------------- /talk-utils.sh: -------------------------------------------------------------------------------- 1 | # talk-utils.sh 2 | # 3 | # handy output functions for writing bash-based scripts 4 | # 5 | # Copyright 2006-2022 Alan K. Stebbens 6 | # 7 | 8 | TALK_UTILS_VERSION="talk-utils.sh v1.8" 9 | 10 | [[ "$TALK_UTILS_SH" = "$TALK_UTILS_VERSION" ]] && return 11 | TALK_UTILS_SH="$TALK_UTILS_VERSION" 12 | 13 | source help-util.sh 14 | 15 | talk_help() { 16 | help_pager <<'EOF' 17 | Shell output utility functions: 18 | 19 | The `talk`, `error`, and `die` functions print their arguments on STDERR. The 20 | `talk` and `talkf` functions print unconditionally to STDERR, and return 21 | success (0). The related functions with prefixes of 'v', 'vo', 'nr', 'nv', 22 | 'nq' print conditionally and return success (0) if they printed, and failure 23 | (1) otherwise. This allows them to be used on conditionals. 24 | 25 | The `warn` function is just another name for `talk`: it prints its output on 26 | STDERR. The `error` function does the same, accepting an optional error CODE, 27 | and then exits. The `die` function sends a `SIGABRT` signal to the parent 28 | process id, forcing an abort. 29 | 30 | talk MSG .. Print all args on `STDERR` 31 | vtalk MSG .. If `$norun` or `$verbose` is set, print all args. 32 | votalk MSG .. If `$verbose` only (no `$norun`) is set, print all args. 33 | nrtalk MSG .. If `$norun` set, print all args 34 | nvtalk MSG .. Unless `$verbose` is set, print all args 35 | nqtalk MSG .. Unless `$quiet` is set, print all args 36 | nrvtalk MSG .. If `$norun` or `$verbose`, print all args 37 | 38 | talkf FMT ARGS .. Printf `FMT` `ARGS` 39 | vtalkf FMT ARGS .. If `$norun` or `$verbose` set, printf `FMT, `ARGS` 40 | votalkf FMT ARGS .. If `$verbose` only (no `$norun`) is set, printf `FMT`, `ARGS` 41 | nrtalkf FMT ARGS .. If `$norun` set, printf `FMT`, `ARGS` 42 | nvtalkf FMT ARGS .. Unless `$verbose` is set, printf `FMT` `ARGS` 43 | nqtalkf FMT ARGS .. Unless `$quiet` is set, printf `FMT` `ARGS` 44 | nrvtalkf FMT ARGS .. If `$norun` or `$verbose`, printf `FMT` `ARGS` 45 | 46 | warn MSG Print all args on `STDERR` 47 | error [CODE] MSG Print `MSG` on `STDERR`, then exit with code `CODE` (or 2) 48 | die MSG Print `MSG` on `STDERR`, then die (with `kill -ABRT`) 49 | 50 | warnf FMT ARGS .. Printf `FMT` `ARGS` on `STDERR` 51 | errorf [CODE] FMT ARGS .. Printf `FMT` `ARGS` on `STDERR`, then exit `$CODE` [2] 52 | dief FMT ARGS .. Printf `FMT` `ARGS` on `STDERR`, then die (with `kill -ABRT`) 53 | 54 | EOF 55 | } 56 | 57 | talk_utils_help() { talk_help ; } 58 | help_talk() { talk_help ; } 59 | help_talk_utils() { talk_help ; } 60 | 61 | # All output goes to STDERR 62 | # 63 | # talk MSG show MSG 64 | # warn MSG alias for talk 65 | # vtalk MSG if $verbose or $norun, show MSG 66 | # votalk MSG if $verbose only (no $norun) show MSG 67 | # nrtalk MSG if $norun, show MSG 68 | # nvtalk MSG unless $verbose show MSG 69 | # nqtalk MSG unless $quiet show MSG 70 | # nrvtalk MSG if $verbose or $norun show MSG 71 | 72 | talk() { help_args_func talk_help $# 1 || return 1 ; __talk "$@" ; } 73 | warn() { help_args_func talk_help $# 1 || return 1 ; __warn "$@" ; } 74 | vtalk() { help_args_func talk_help $# 1 || return 1 ; __vtalk "$@" ; } 75 | votalk() { help_args_func talk_help $# 1 || return 1 ; __votalk "$@" ; } 76 | nrtalk() { help_args_func talk_help $# 1 || return 1 ; __nrtalk "$@" ; } 77 | nvtalk() { help_args_func talk_help $# 1 || return 1 ; __nvtalk "$@" ; } 78 | nqtalk() { help_args_func talk_help $# 1 || return 1 ; __nqtalk "$@" ; } 79 | nrvtalk() { help_args_func talk_help $# 1 || return 1 ; __nrvtalk "$@" ; } 80 | error() { help_args_func talk_help $# 1 || return 1 ; __error "$@" ; } 81 | 82 | __talk() { echo 1>&2 "$@" ; return 0 ;} 83 | __warn() { __talk "$@" ; } 84 | __vtalk() { [[ -n "$norun$verbose" ]] && __talk "$@" || return 1 ; } 85 | __votalk() { [[ -n "$verbose" && -z "$norun" ]] && __talk "$@" || return 1 ; } 86 | __nrtalk() { [[ -n "$norun" ]] && __talk "$@" || return 1 ; } 87 | __nvtalk() { [[ -z "$verbose" ]] && __talk "$@" || return 1 ; } 88 | __nqtalk() { [[ -z "$quiet" ]] && __talk "$@" || return 1 ; } 89 | __nrvtalk() { [[ -n "$verbose$norun" ]] && __talk "$@" || return 1 ; } 90 | 91 | # error [CODE] MSG - show MSG on STDERR then exit with error CODE [default 2] 92 | 93 | __error() { 94 | local code=2 95 | case "$1" in [0-9]*) code=$1 ; shift ;; esac 96 | talk "$@" 97 | exit $code 98 | } 99 | 100 | # talkf FMT ARGS.. printf FMT ARGS 101 | # warnf FMT ARGS.. alias for talkf 102 | # vtalkf FMT ARGS.. if $norun or $verbose set, printf FMT ARGS 103 | # votalkf FMT ARGS.. if $verbose & unless $norun, printf FMT ARGS 104 | # nrtalkf FMT ARGS.. if $norun, printf FMT ARGS 105 | # nvtalkf FMT ARGS.. unless $verbose is set, printf FMT ARGS 106 | # nqtalkf FMT ARGS.. unless $quiet is set, printf FMT ARGS 107 | # nrvtalkf FMT ARGS.. if $verbose or $norun, printf FMT ARGS 108 | 109 | talkf() { help_args_func talk_help $# 1 || return 1 ; __talkf "$@" ; } 110 | warnf() { help_args_func talk_help $# 1 || return 1 ; __warnf "$@" ; } 111 | vtalkf() { help_args_func talk_help $# 1 || return 1 ; __vtalkf "$@" ; } 112 | votalkf() { help_args_func talk_help $# 1 || return 1 ; __votalkf "$@" ; } 113 | nrtalkf() { help_args_func talk_help $# 1 || return 1 ; __nrtalkf "$@" ; } 114 | nvtalkf() { help_args_func talk_help $# 1 || return 1 ; __nvtalkf "$@" ; } 115 | nqtalkf() { help_args_func talk_help $# 1 || return 1 ; __nqtalkf "$@" ; } 116 | nrvtalkf() { help_args_func talk_help $# 1 || return 1 ; __nrvtalkf "$@" ; } 117 | errorf() { help_args_func talk_help $# 1 || return 1 ; __errorf "$@" ; } 118 | 119 | __talkf() { printf 1>&2 "$@" ; return 0 ; } 120 | __warnf() { __talkf "$@" ; } 121 | __vtalkf() { [[ -n "$norun$verbose" ]] && __talkf "$@" || return 1 ; } 122 | __votalkf() { [[ -n "$verbose" && -z "$norun" ]] && __talkf "$@" || return 1 ; } 123 | __nrtalkf() { [[ -n "$norun" ]] && __talkf "$@" || return 1 ; } 124 | __nvtalkf() { [[ -z "$verbose" ]] && __talkf "$@" || return 1 ; } 125 | __nqtalkf() { [[ -z "$quiet" ]] && __talkf "$@" || return 1 ; } 126 | __nrvtalkf() { [[ -n "$verbose$norun" ]] && __talkf "$@" || return 1 ; } 127 | 128 | # errorf [CODE] FMT ARGS .. print FMT ARGS on STDERR, then exit with CODE[2] 129 | 130 | __errorf() { 131 | local code=2 132 | case "$1" in [0-9]*) code=$1 ; shift ;; esac 133 | __talkf "$@" 134 | exit $code 135 | } 136 | 137 | # die "Error message" 138 | # dief FMT ARGS .. 139 | # 140 | # These functions are designed to be used within other bash scripts. Simply 141 | # exiting with an error code is not sufficient because many bash scripts don't 142 | # have very good exception handling. So.. our "die" function prints its error 143 | # message on STDERR, and then causes the current process to abort. 144 | 145 | die() { help_args_func talk_help $# 1 || return ; __die "$@" ; } 146 | dief() { help_args_func talk_help $# 1 || return ; __dief "$@" ; } 147 | 148 | __die() { __dief "%s\n" "$@" ; } 149 | 150 | __dief() { 151 | talkf "$@" 152 | talk "Call stack:" 153 | local _i 154 | for ((_i=1; _i<${#BASH_SOURCE[@]}; _i++)); do 155 | case "${FUNCNAME[$_i]}" in die|dief|__die|__dief) continue ;; esac 156 | __talkf "%s <%s:%s>\n" "${FUNCNAME[$_i]}" "${BASH_SOURCE[$_i]}" "${BASH_LINENO[$_i-1]}" 157 | done 158 | kill -ABRT $$ 159 | exit 2 160 | } 161 | 162 | # end of talk-utils.sh 163 | # vim: sw=2 ai 164 | -------------------------------------------------------------------------------- /test-all: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # test-all.sh [options] 3 | # 4 | # Run all tests with the name "test-*-*.sh" 5 | # 6 | # Copyright 2015-2022, Alan K. Stebbens 7 | 8 | lines="--------------" 9 | for test in test-*-*.sh ; do 10 | # don't run test-utils -- it's a library 11 | [[ "$test" = 'test-utils.sh' ]] && continue 12 | printf "\n%s %s %s\n" $lines $test $lines 13 | $test "$@" 14 | done 15 | exit 16 | -------------------------------------------------------------------------------- /test-arg-utils.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # test-arg-utils.sh 3 | # 4 | # Copyright 2006-2022 Alan K. Stebbens 5 | # 6 | # Test module for arg-utils.sh 7 | # 8 | export PATH=.:$PATH 9 | source arg-utils.sh 10 | source test-utils.sh 11 | 12 | 13 | wordlist=( the time has come to talk of ceiling wax and cabbages and kings ) 14 | 15 | numarg_test_func() { 16 | local arg=`numarg_or_input "$1"` 17 | echo "$arg" 18 | } 19 | 20 | args_test_func() { 21 | local args=( `args_or_input "$@"` ) 22 | echo "${args[@]}" 23 | } 24 | 25 | test_10_num_args_or_input() { 26 | start_test 27 | local num=`numarg_test_func 62` 28 | check_eq "$num" 62 "numarg_test_func failed on arg 62" 29 | local num=`echo 229 | numarg_test_func` 30 | check_eq "$num" 229 "numarg_test_func failed on stdin 229" 31 | local args=() 32 | testdata="foo bar bif baf" 33 | args=( `args_test_func $testdata ` ) 34 | check_size args 4 "args is the wrong size" 35 | check_equal "${args[*]}" "$testdata" "args_test_func failed on arg input" 36 | args=( ) 37 | args=( `echo foo bar bif baf | args_test_func` ) 38 | check_equal "${args[*]}" "$testdata" "args_test_func failed on stdin" 39 | end_test 40 | } 41 | 42 | test_11_append_args() { 43 | start_test 44 | local result=`echo 'foo' 'bar' | append_args 'bif' 'baf'` 45 | check_equal "$result" "foo bar bif baf" 46 | local result=`echo '' | append_arg 'bam'` 47 | check_equal "$result" 'bam' "Should be 'bam'; got '$result'" 48 | local result=`echo 'foo bar' | append_args ''` 49 | check_equal "$result" 'foo bar ' "Should be 'foo bar'; got '$result'" 50 | end_test 51 | } 52 | 53 | init_tests "$@" 54 | run_tests 55 | summarize_tests 56 | exit 57 | -------------------------------------------------------------------------------- /test-date-utils.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2006-2022, Alan K. Stebbens 3 | # 4 | # test date-utils.sh 5 | # 6 | # uses test-utils.sh 7 | 8 | PATH=.:$PATH:$HOME/lib 9 | 10 | source test-utils.sh 11 | 12 | . date-utils.sh 13 | 14 | testdata='test-dates.dat' 15 | 16 | 17 | test_01_conversion_equivalencies() { 18 | start_test 19 | last_adays='-1' 20 | for ((year=1; year<=2082; year+=500)) ; do 21 | for month in 1 2 3 6 9 12; do 22 | mdays=`last_day_of_month $year $month` 23 | for day in 1 15 25 $mdays ; do 24 | date1=`printd $year $month $day` 25 | adays=`date_to_adays $date1` 26 | check_value "$adays" "Empty days!?" # should never be empty 27 | check_lt $last_adays $adays "ADays should increase" 28 | last_adays=$adays 29 | date2=`adays_to_date $adays` 30 | check_value "$date1" "$date2" "ADays conversion failed: $date1 vs. $date2" 31 | jdays=`date_to_jdays $date1` 32 | date3=`jdays_to_date $jdays` 33 | check_equal "$date1" "$date3" "Bad conversion: $date1 vs. $date3" 34 | date4=`date_to_jdays $date1 | jdays_to_date` 35 | check_equal "$date1" "$date4" "Bad pipe conversion: $date1 vs. $date4" 36 | done 37 | done 38 | # echo '' 39 | done 40 | end_test 41 | } 42 | 43 | test_02_test_dates() { 44 | start_test 45 | check_true "test -f $testdata" "Test file '$testdata' does not exist" 46 | local tdate tadays tjdays 47 | while read tdate tadays tjdays ; do 48 | if [[ -z "$tdate" ]]; then break ; fi 49 | if (( verbose )) ; then 50 | echo '' 51 | echo "ref date = $tdate" 52 | echo "ref adays = $tadays" 53 | echo "ref jdays = $tjdays" 54 | fi 55 | jdays=`date_to_jdays $tdate` 56 | adays=`date_to_adays $tdate` 57 | check_equal "$jdays" "$tjdays" "Wrong jdays for $tdate: got $jdays; should be $tjdays" 58 | check_equal "$adays" "$adays" "Wrong adays for $tdate: got $adays; should be $tadays" 59 | done <$testdata 60 | end_test 61 | } 62 | 63 | # yyyy-mm-dd 64 | # 0123 56 89 65 | 66 | test_03_years_offset() { 67 | start_test 68 | start_date="2008-03-05" 69 | year=${start_date:0:4} 70 | mm=${start_date:5:2} 71 | for ((i=1; i<=5; i++)) ; do 72 | date_then=`get_date_x_years_before $i $start_date` 73 | check_value "$date_then" 74 | year2=${date_then:0:4} 75 | check_eq $(( year - i )) $year2 76 | check_eq $mm ${date_then:5:2} 77 | 78 | date_since=`get_date_x_years_since $i $start_date` 79 | check_value "$date_since" 80 | year3=${date_since:0:4} 81 | check_eq $(( year + i )) $year3 82 | check_eq $mm ${date_since:5:2} 83 | done 84 | today=`date +%F` 85 | odate1=`get_date_x_years_before 1` 86 | odate2=`get_date_x_years_before 1 $today` 87 | check_equal "$odate1" "$odate2" "get_date_x_years_before default doesn't match explicit" 88 | odate1=`get_date_x_years_after 10` 89 | odate2=`get_date_x_years_after 10 $today` 90 | check_equal "$odate1" "$odate2" "get_date_x_years_after default doesn't match explicit" 91 | end_test 92 | } 93 | 94 | # check_date 'mm/dd/yyyy' yyyy mm dd [ERROR] 95 | 96 | check_date() { 97 | parse_date "$1" 98 | check_eq $year $2 "Date test failed; got year $year, expected $2" 99 | check_eq $month $3 "Date test failed; got month $month, expected $3" 100 | check_eq $day $4 "Date test failed; got day $day, expected $4" 101 | } 102 | 103 | # check_date_time 'yyyymmddhhmm' yyyy mm dd hh mm 104 | 105 | check_date_time() { 106 | check_date "$1" $2 $3 $4 107 | check_eq $hour $5 108 | check_eq $minute $6 109 | } 110 | 111 | test_04_parse_date() { 112 | start_test 113 | check_date '1/1/2014' 2014 1 1 114 | check_date '2/28/1981' 1981 2 28 115 | check_date '3-31-1985' 1985 3 31 116 | check_date '4-15-2016' 2016 4 15 117 | check_date '2016-4-15' 2016 4 15 118 | check_date '2016/8/25' 2016 8 25 119 | check_date '8/25/2016' 2016 8 25 120 | end_test 121 | } 122 | 123 | check_dim() { 124 | local x y 125 | x=`days_in_month $1` 126 | check_eq "$x" "$2" 127 | } 128 | 129 | test_05_days_in_month_mmm() { 130 | start_test 131 | check_dim Jan 31 132 | check_dim Feb 28 133 | check_dim Mar 31 134 | check_dim Apr 30 135 | check_dim May 31 136 | check_dim Jun 30 137 | check_dim Jul 31 138 | check_dim Aug 31 139 | check_dim Sep 30 140 | check_dim Oct 31 141 | check_dim Nov 31 142 | check_dim Dec 31 143 | end_test 144 | } 145 | 146 | test_06_days_in_month_mmmm() { 147 | start_test 148 | check_dim January 31 149 | check_dim February 28 150 | check_dim March 31 151 | check_dim April 30 152 | check_dim May 31 153 | check_dim June 30 154 | check_dim July 31 155 | check_dim August 31 156 | check_dim September 30 157 | check_dim October 31 158 | check_dim November 31 159 | check_dim December 31 160 | end_test 161 | } 162 | test_07_days_in_month_num() { 163 | start_test 164 | check_dim 1 31 165 | check_dim 2 28 166 | check_dim 3 31 167 | check_dim 4 30 168 | check_dim 5 31 169 | check_dim 6 30 170 | check_dim 7 31 171 | check_dim 8 31 172 | check_dim 9 30 173 | check_dim 10 31 174 | check_dim 11 31 175 | check_dim 12 31 176 | end_test 177 | } 178 | 179 | check_leap_year() { 180 | local ly=n 181 | is_leap_year "$1" && { ly=y ; } 182 | check_equal "$ly" "$2" "is_leap_year failed for '$1'; should be '$2'" 183 | } 184 | 185 | test_08_leap_year() { 186 | start_test 187 | check_leap_year 2004 y 188 | check_leap_year 2003 n 189 | check_leap_year 2002 n 190 | check_leap_year 2001 n 191 | check_leap_year 2000 y 192 | end_test 193 | } 194 | 195 | test_09_last_day_of_month() { 196 | start_test 197 | x=`last_day_of_month 2000 1` 198 | check_eq $x 31 199 | x=`last_day_of_month 2001 1` 200 | check_eq $x 31 201 | x=`last_day_of_month 2000 2` 202 | check_eq $x 29 203 | x=`last_day_of_month 2001 2` 204 | check_eq $x 28 205 | end_test 206 | } 207 | 208 | # check_date_x_years_before X STARTDATE RESULTDATE 209 | 210 | check_date_x_years_before() { 211 | local dt 212 | dt=`get_date_x_years_before $1 "$2"` 213 | check_equal $dt `print_date "$3"` "get_date_x_years_before failed: $1 years from $2 should be \"$3\", got \"$dt\"" 214 | } 215 | check_date_x_years_after() { 216 | local dt 217 | dt=`get_date_x_years_after $1 "$2"` 218 | check_equal $dt `print_date "$3"` "get_date_x_years_after failed: $1 years after $2 should be \"$3\", got \"$dt\"" 219 | } 220 | 221 | test_10_date_x_years_before() { 222 | start_test 223 | check_date_x_years_before 1 2001/11/1 2000/11/1 224 | check_date_x_years_before 5 2011/8/5 2006/8/5 225 | check_date_x_years_after 1 2001/11/1 2002/11/1 226 | check_date_x_years_after 5 2011/8/5 2016/8/5 227 | end_test 228 | } 229 | 230 | test_11_date_parse_serials() { 231 | start_test 232 | check_date 20140101 2014 1 1 233 | check_date 20161231 2016 12 31 234 | end_test 235 | } 236 | 237 | test_12_datetime_parse_serials() { 238 | start_test 239 | check_date_time 201401010809 2014 1 1 8 9 240 | check_date_time 201612310708 2016 12 31 7 8 241 | check_date_time 201612312359 2016 12 31 23 59 242 | end_test 243 | } 244 | 245 | date_parts() { 246 | echo "${1:0:4} ${1:5:2} ${1:8:2}" 247 | } 248 | 249 | test_13_today() { 250 | start_test 251 | local tdate=`date +%F` 252 | local date=`today` 253 | check_date "$date" `date_parts $tdate` 254 | end_test 255 | } 256 | 257 | test_14_yesterday() { 258 | start_test 259 | local ydate=`gdate -d '-1 day' +%F` 260 | local date=`yesterday` 261 | check_date "$date" `date_parts $ydate` 262 | end_test 263 | } 264 | 265 | test_15_tomorrow() { 266 | start_test 267 | local tdate=`gdate -d '+1 day' +%F` 268 | local date=`tomorrow` 269 | check_date "$date" `date_parts $tdate` 270 | end_test 271 | } 272 | 273 | test_16_get_date_x_days_before() { 274 | start_test 275 | for offset in 1 3 5 7 15 ; do 276 | local tdate=`gdate -d "-${offset} days" +%F` 277 | local date=`get_date_x_days_before $offset` 278 | check_date "$date" `date_parts $tdate` 279 | done 280 | end_test 281 | } 282 | 283 | test_17_get_date_x_days_since() { 284 | start_test 285 | for offset in 1 3 5 7 15 ; do 286 | local tdate=`gdate -d "+${offset} days" +%F` 287 | local date=`get_date_x_days_since $offset` 288 | check_date "$date" `date_parts $tdate` 289 | done 290 | end_test 291 | } 292 | 293 | # run_test_data_on FUNCTION 294 | run_test_data_on() { 295 | local func="${1:?'Missing function'}" 296 | local x start_date offset target_date tdate 297 | for(( x=0; x < ${#test_data[*]}; x+=3 )) ; do 298 | start_date=${test_data[$x]} 299 | offset=${test_data[$x+1]} 300 | target_date=${test_data[$x+2]} 301 | tdate=`$func $start_date $offset` 302 | check_date "$target_date" `date_parts $tdate` 303 | done 304 | } 305 | 306 | test_20_date_adjust() { 307 | start_test 308 | test_data=( 2016-08-31 '+ 1' 2016-09-01 309 | 2016-08-31 '+ 1d' 2016-09-01 310 | 2016-08-31 '+ 1w' 2016-09-07 311 | 2016-08-31 '+ 1m' 2016-09-31 312 | 2016-08-31 '+ 1y' 2017-08-31 313 | 2016-09-01 '- 1d' 2016-08-31 314 | 2016-09-01 '- 1w' 2016-08-25 315 | 2016-09-01 '- 1m' 2016-08-01 316 | 2016-09-01 '- 1y' 2015-09-01 317 | ) 318 | run_test_data_on date_adjust 319 | end_test 320 | } 321 | 322 | test_21_date_add() { 323 | start_test 324 | test_data=( 2016-08-31 1 2016-09-01 325 | 2016-08-31 1d 2016-09-01 326 | 2016-08-31 1w 2016-09-07 327 | 2016-08-31 1m 2016-09-31 328 | 2016-08-31 1y 2017-08-31 329 | ) 330 | run_test_data_on date_add 331 | end_test 332 | } 333 | 334 | test_22_date_sub() { 335 | start_test 336 | test_data=( 2016-09-01 1 2016-08-31 337 | 2016-09-01 1d 2016-08-31 338 | 2016-09-01 1w 2016-08-25 339 | 2016-09-01 1m 2016-08-01 340 | 2016-09-01 1y 2015-09-01 341 | ) 342 | run_test_data_on date_sub 343 | end_test 344 | } 345 | 346 | test_23_days_between() { 347 | start_test 348 | test_data=( 2016-09-01 2016-09-02 1 349 | 2016-09-01 2016-09-03 2 350 | 2016-09-03 2016-09-01 2 351 | 2016-09-30 2016-10-01 1 352 | 2016-09-30 2016-10-02 2 353 | 2016-09-30 2016-10-03 3 354 | 355 | 2016-01-01 2016-02-01 31 # jan 356 | 2017-02-01 2017-03-01 28 # feb non-leap year 357 | 2016-02-01 2016-03-01 29 # feb leap year 358 | 2016-03-01 2016-04-01 31 # mar 359 | 2016-04-01 2016-05-01 30 # apr 360 | 2016-05-01 2016-06-01 31 # may 361 | 2016-06-01 2016-07-01 30 # jun 362 | 2016-07-01 2016-08-01 31 # jul 363 | 2016-08-01 2016-09-01 31 # aug 364 | 2016-09-01 2016-10-01 30 # sep 365 | 2016-10-01 2016-11-01 31 # oct 366 | 2016-11-01 2016-12-01 30 # nov 367 | 2016-12-01 2017-01-01 31 # dec 368 | 369 | 2015-01-01 2016-01-01 365 370 | 2016-01-01 2017-01-01 366 # leap year 371 | 2016-09-30 2017-09-30 365 372 | ) 373 | run_date_func_and_compare days_between 374 | end_test 375 | } 376 | 377 | # accepts a list of [DATE1 DATE2 RESULT] 378 | run_date_func_and_compare() { 379 | local func="${1:?'Missing function'}" 380 | local x date1 date2 expected_result 381 | for(( x=0; x < ${#test_data[*]}; x+=3 )) ; do 382 | date1=${test_data[$x]} 383 | date2=${test_data[$x+1]} 384 | expected_result=${test_data[$x+2]} 385 | actual_result=`$func "$date1" "$date2"` 386 | check_equal "$expected_result" "$actual_result" "date_func on '$date1 $date2': '$expected_result' does not equal '$actual_result' " 387 | done 388 | } 389 | 390 | # run_test_data_and_compare FUNCTION 391 | run_test_data_and_compare() { 392 | local func="${1:?'Missing function'}" 393 | local x input_date format expected_result actual_result target_date tdate 394 | for(( x=0; x < ${#test_data[*]}; x+=3 )) ; do 395 | input_date=${test_data[$x]} 396 | format=${test_data[$x+1]} 397 | expected_result=${test_data[$x+2]} 398 | date_parse "$input_date" 399 | actual_result=`$func "$format" "$input_date"` 400 | check_equal "$expected_result" "$actual_result" "date_format on '$format': '$expected_result' does not equal '$actual_result' " 401 | done 402 | } 403 | 404 | test_30_date_format() { 405 | start_test 406 | test_data=( 2018-02-08 "-%e-%d-" "- 2-08-" 407 | 2018-02-09 "-%e-%d-" "- 2-09-" 408 | 2018-10-10 "-%e-%d-" "-10-10-" 409 | ) 410 | run_test_data_and_compare date_format 411 | end_test 412 | } 413 | 414 | 415 | init_tests "$@" 416 | run_tests 417 | summarize_tests 418 | exit 419 | -------------------------------------------------------------------------------- /test-hash-utils.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2006-2022, Alan K. Stebbens 3 | # 4 | # Test module for hash-utils.sh 5 | # 6 | 7 | export PATH=.:$HOME/lib:$PATH 8 | 9 | source hash-utils.sh 10 | source test-utils.sh 11 | 12 | test_01_hash_init() { 13 | start_test 14 | hash_init hash1a 15 | check_output hash1a_init_test 'hash_info hash1a' "Hash1a init info not correct" 16 | hash_init hash1b NONE 17 | check_output hash1b_init_test 'hash_info hash1b' "Hash1b init info not correct" 18 | def=`hash_default hash1b` 19 | check_equal "$def" 'NONE' "Hash1b default result ($def) is incorrect; should be 'NONE'" 20 | # make sure hash1a and hash1b are distinct 21 | hash_set hash1a key1a val1a 22 | hash_set hash1b key1b val1b 23 | check_size hash1a 1 24 | check_size hash1b 1 25 | end_test 26 | } 27 | 28 | wordlist1="apple one banana two cherry three mango four pear five" 29 | 30 | test_02_hash_set() { 31 | start_test 32 | hash_init hash2 33 | hash_set hash2 $wordlist1 34 | 35 | check_output hash2_info "hash_info hash2" 36 | 37 | check_size hash2 5 "hash size is incorrect; `hash_size hash2` vs. 5" 38 | 39 | check_key hash2 apple 40 | check_key hash2 banana 41 | check_key hash2 cherry 42 | check_key hash2 mango 43 | check_key hash2 pear 44 | check_no_key hash2 foobar 45 | 46 | # add the same words again 47 | oldsize=`hash_size hash2` 48 | hash_set hash2 $wordlist1 49 | newsize=`hash_size hash2` 50 | check_size hash2 $oldsize "hash_set with same data changes size (old=$oldsize, new=$newsize)" 51 | end_test 52 | } 53 | 54 | test_03_hash_get() { 55 | start_test 56 | hash_init hash3 57 | 58 | words=( key1 val1 key2 val2 key3 val3 key4 val4 key5 val5 key6 val6 ) 59 | hash_set hash3 "${words[@]}" 60 | num_words=`list_size words` 61 | num_pairs=$(( num_words / 2 )) 62 | hsize=`hash_size hash3` 63 | check_size hash3 $num_pairs "hash3 size is wrong; is $num_pairs, should be: $hsize" 64 | 65 | for ((i=0; i<${#words}; i += 2)); do 66 | val1="${words[i+1]}" 67 | val2=`hash_get hash3 ${words[i]}` 68 | check_equal "$val1" "$val2" 69 | done 70 | 71 | end_test 72 | } 73 | 74 | test_04_hash_delete() { 75 | start_test 76 | words=( key1 val1 key2 val2 key3 val3 key4 val4 key5 val5 key6 val6 ) 77 | hash_init hash4 78 | hash_set hash4 "${words[@]}" 79 | check_size hash4 $(( ${#words[@]} / 2 )) 80 | 81 | # first check that the items are there 82 | for ((i=1; i<=6; i++)) ; do 83 | check_item hash4 "key$i" "val$i" 84 | done 85 | # now delete them and confirm their deletion 86 | count=`hash_size hash4` 87 | for ((i=1; i<=6; i++)); do 88 | hash_delete hash4 "key$i" 89 | check_no_key hash4 "key$i" 90 | (( count-- )) 91 | check_size hash4 $count 92 | done 93 | end_test 94 | } 95 | 96 | test_05_in_hash() { 97 | start_test 98 | hash_init hash5 99 | hash_set hash5 key1 val1 key2 val2 key3 val3 key4 val4 100 | check_size hash5 4 101 | check_true "in_hash hash5 key1" 102 | check_false "in_hash hash5 key9" 103 | check_true "in_hash hash5 key2" 104 | check_false "in_hash hash5 val2" 105 | check_true "in_hash hash5 key4" 106 | check_true "in_hash hash5 key3" 107 | check_false "in_hash hash5 key5" 108 | end_test 109 | } 110 | 111 | test_06_keys() { 112 | start_test 113 | hash_init hash6 114 | hash_set hash6 key1 val1 key2 val2 key3 val3 key4 val4 key5 val5 key6 val6 115 | keys=( `hash_keys hash6` ) 116 | for key in "${keys[@]}" ; do 117 | check_true "[[ \"$key\" =~ key[1-6] ]]" 118 | done 119 | end_test 120 | } 121 | 122 | test_07_values() { 123 | start_test 124 | hash_init hash7 125 | hash_set hash7 key1 val1 key2 val2 key3 val3 key4 val4 key5 val5 key6 val6 126 | values=( `hash_values hash7` ) 127 | for val in "${values[@]}" ; do 128 | check_true "[[ \"$val\" =~ val[1-6] ]]" 129 | done 130 | end_test 131 | } 132 | 133 | test_10_print_hash() { 134 | start_test 135 | words=( 136 | apple banana cherry dog elephant fox giraffe hawk indigo manzana milk november 137 | october december january february march april may june july august 138 | ) 139 | hash_init hash10 140 | for ((i=0; i<${#words[@]}; i++)) ; do 141 | hash_set hash10 ${words[i]} $i 142 | done 143 | check_output phout1 "hash_print hash10" 144 | check_output phout2 "hash_print hash10 i=1" 145 | check_output phout3 "hash_print hash10 i=2 c=4" 146 | check_output phout4 "hash_print hash10 i=3 c=3" 147 | check_output phout5 "hash_print hash10 i=1 c=2" 148 | end_test 149 | } 150 | 151 | if (( BASH_VERSINFO[0] < 4 )); then 152 | echo "The hash-utils library needs bash version 4 or greater" 153 | exit 154 | fi 155 | 156 | init_tests "$@" 157 | run_tests 158 | summarize_tests 159 | 160 | exit 161 | -------------------------------------------------------------------------------- /test-list-utils.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # test-list-utils.sh 3 | # 4 | # Copyright 2006-2022, Alan K. Stebbens 5 | # 6 | # Test module for list-utils.sh 7 | # 8 | 9 | export PATH=.:$HOME/lib:$PATH 10 | 11 | source list-utils.sh 12 | source test-utils.sh 13 | 14 | test_01_list_add() { 15 | start_test 16 | list1=() 17 | 18 | list_add list1 foo 19 | 20 | check_item list1 0 foo "list_add failed" 21 | check_size list1 1 "list_add size test failed" 22 | 23 | list_add list1 bar 24 | 25 | check_size list1 2 26 | check_item list1 0 foo 27 | check_item list1 1 bar 28 | 29 | end_test 30 | } 31 | 32 | test_02_list_sort() { 33 | start_test 34 | list2=() 35 | 36 | words="now is the time for all good men to come to the aid of their country" 37 | list_add list2 $words 38 | check_size list2 16 39 | 40 | sort_list list2 41 | check_size list2 16 42 | sorted_words="`sort_str \"$words\"`" 43 | sorted_words2=`join_list list2 nowrap ' '` 44 | check_equal "$sorted_words" "$sorted_words2" 45 | check_unequal "$words" "$sorted_words2" 46 | end_test 47 | } 48 | 49 | test_03_list_add_once() { 50 | start_test 51 | tlist=() 52 | 53 | words="now is the time for all good men to come to the aid of their country" 54 | list_add_once tlist $words 55 | check_size tlist 14 56 | 57 | list_add_once tlist $words 58 | check_size tlist 14 59 | end_test 60 | } 61 | 62 | test_04_list_insert() { 63 | start_test 64 | tlist=() 65 | list_add tlist banana cherry 66 | check_size tlist 2 67 | list_insert tlist apple 68 | check_size tlist 3 69 | check_item tlist 0 apple 70 | check_item tlist 1 banana 71 | check_item tlist 2 cherry 72 | end_test 73 | } 74 | 75 | test_05_list_insert_once() { 76 | start_test 77 | tlist=() 78 | list_add_once tlist banana cherry 79 | check_size tlist 2 80 | list_insert_once tlist apple 81 | check_size tlist 3 82 | check_item tlist 0 apple 83 | check_item tlist 1 banana 84 | check_item tlist 2 cherry 85 | list_insert_once tlist apple banana cherry 86 | check_size tlist 3 87 | list_insert_once tlist aardvark 88 | check_size tlist 4 89 | check_item tlist 0 aardvark 90 | check_item tlist 1 apple 91 | end_test 92 | } 93 | 94 | test_06_in_list() { 95 | start_test 96 | tlist=() 97 | tlist2=() 98 | list_add_once tlist foo bar baf 99 | list_add_once tlist2 "foo bar" "bif baf" "+" "-" "/" "*" 100 | check_size tlist 3 101 | check_true "in_list tlist foo" 102 | check_false "in_list tlist foo2" 103 | check_true "in_list tlist -all foo bar" 104 | check_false "in_list tlist -any foo1 bar2" 105 | check_true "in_list tlist -all foo bar baf" 106 | check_true "in_list tlist -any foo1 baf bar2" 107 | check_false "in_list tlist -all foo bar baf gonzo" 108 | check_true "in_list tlist foo1 baf bar2" 109 | check_true "in_list tlist -any foo1 baf bar2" 110 | check_false "in_list tlist -all foo1 baf bar2" 111 | check_false "in_list tlist foo1 baf2 bar2" 112 | # check weird matches 113 | check_true "in_list tlist2 '+'" 114 | check_false "in_list tlist2 '?'" 115 | check_false "in_list tlist2 'bif'" 116 | check_true "in_list tlist2 'bif baf'" 117 | end_test 118 | } 119 | 120 | test_07_lookup_list() { 121 | start_test 122 | tlist1=( now is the 'time' 'for' all good men to come to the aid of their country ) 123 | 124 | item=`lookup_list tlist1 now` 125 | code=$? 126 | check_eq $code 0 127 | check_equal "$item" 'now' 128 | 129 | item=`lookup_list tlist1 'time'` 130 | code=$? 131 | check_eq $code 0 132 | check_equal $item 'time' 133 | 134 | item=`lookup_list tlist1 notfound` 135 | code=$? 136 | check_eq $code 1 137 | check_equal "$item" '' 138 | 139 | item=`lookup_list tlist1 the` 140 | code=$? 141 | check_eq $code 2 142 | check_equal "$item" '' 143 | 144 | item=`lookup_list tlist1 to` 145 | code=$? 146 | check_eq $code 2 147 | check_equal "$item" '' 148 | 149 | item=`lookup_list tlist1 goo` 150 | code=$? 151 | check_eq $code 0 152 | check_equal "$item" 'good' 153 | 154 | item=`lookup_list tlist1 go` 155 | code=$? 156 | check_eq $code 0 157 | check_equal "$item" 'good' 158 | 159 | item=`lookup_list tlist1 t` 160 | code=$? 161 | check_eq $code 2 162 | check_equal "$item" '' 163 | 164 | end_test 165 | } 166 | 167 | test_08_grep_list() { 168 | start_test 169 | tlist1=( now is the 'time' 'for' all good men to come to the aid of their country ) 170 | 171 | items=( `grep_list tlist1 now` ) # 1 172 | code=$? 173 | check_eq $code 0 174 | check_size items 1 175 | check_item_equal items 0 'now' 176 | 177 | items=( `grep_list tlist1 'time'` ) # 2 178 | code=$? 179 | check_eq $code 0 180 | check_size items 1 181 | check_item_equal items 0 'time' 182 | 183 | items=( `grep_list tlist1 notfound` ) # 3 184 | code=$? 185 | check_eq $code 1 186 | check_size items 0 187 | 188 | items=( `grep_list tlist1 the` ) # 4 189 | code=$? 190 | check_eq $code 0 191 | check_size items 3 192 | check_item_equal items 0 'the' 193 | check_item_equal items 1 'the' 194 | check_item_equal items 2 'their' 195 | 196 | items=( `grep_list tlist1 to` ) # 5 197 | code=$? 198 | check_eq $code 0 199 | check_size items 2 200 | check_item_equal items 0 'to' 201 | check_item_equal items 1 'to' 202 | 203 | items=( `grep_list tlist1 goo` ) # 6 204 | code=$? 205 | check_eq $code 0 206 | check_size items 1 207 | check_item_equal items 0 'good' 208 | 209 | items=( `grep_list tlist1 go` ) # 7 210 | code=$? 211 | check_eq $code 0 212 | check_size items 1 213 | check_item_equal items 0 'good' 214 | 215 | items=( `grep_list tlist1 t` ) # 8 216 | code=$? 217 | check_eq $code 0 218 | check_size items 7 219 | check_item_equal items 0 'the' 220 | check_item_equal items 1 'time' 221 | check_item_equal items 2 'to' 222 | check_item_equal items 3 'to' 223 | check_item_equal items 4 'the' 224 | check_item_equal items 5 'their' 225 | check_item_equal items 6 'country' 226 | 227 | items=( `grep_list tlist1 e` ) 228 | code=$? 229 | check_eq $code 0 230 | check_size items 6 231 | check_item_equal items 0 'the' 232 | check_item_equal items 1 'time' 233 | check_item_equal items 2 'men' 234 | check_item_equal items 3 'come' 235 | check_item_equal items 4 'the' 236 | check_item_equal items 5 'their' 237 | 238 | end_test 239 | } 240 | 241 | test_09_push_pop_list() { 242 | start_test 243 | stack=() 244 | check_size stack 0 245 | push_list stack fubar 246 | check_size stack 1 247 | push_list stack tarfu 248 | check_size stack 2 249 | push_list stack snafu 250 | check_size stack 3 251 | 252 | pop_list stack 253 | check_size stack 2 254 | check_equal "$item" 'snafu' 255 | 256 | pop_list stack 257 | check_size stack 1 258 | check_equal "$item" 'tarfu' 259 | 260 | pop_list stack 261 | check_size stack 0 262 | check_equal "$item" 'fubar' 263 | 264 | pop_list stack 265 | code=$? 266 | check_eq $code 1 267 | check_equal "$item" '' 268 | 269 | end_test 270 | } 271 | 272 | test_10_print_list() { 273 | start_test 274 | words=( 275 | apple banana cherry dog elephant fox giraffe hawk indigo manzana milk november 276 | october december january february march april may june july august 277 | ) 278 | check_output plout1 "print_list words" 279 | check_output plout2 "print_list words i=1" 280 | check_output plout3 "print_list words i=2 c=4" 281 | check_output plout4 "print_list words i=3 c=3" 282 | check_output plout5 "print_list words i=1 c=2" 283 | end_test 284 | } 285 | 286 | test_10a_print_list() { 287 | start_test 288 | workfiles=( '.environment*' '.cshrc*' '.aliases*' '.prompts*' '.bashrc*' '.inputrc' 289 | '.profile' '.vim*' '.git-bash-prompt' 'src/github/aks/maximum-awesome' ) 290 | check_output plouta1 "print_list workfiles" 291 | check_output plouta2 "print_list workfiles i=1" 292 | check_output plouta3 "print_list workfiles i=1 c=2" 293 | check_output plouta4 "print_list workfiles i=1 c=3" 294 | check_output plouta5 "print_list workfiles i=2 c=1" 295 | check_output plouta6 "print_list workfiles i=4 c=1" 296 | end_test 297 | } 298 | 299 | test_11_join_list() { 300 | start_test 301 | list_init tlist 302 | list_add tlist apple banana cherry 303 | check_size tlist 3 304 | check_output join_list_3 "join_list tlist" 305 | check_output join_list_3sp "join_list tlist ' '" 306 | check_output join_list_3tab "join_list tlist TAB" 307 | check_output join_list_3and "join_list tlist AND" 308 | check_output join_list_3or "join_list tlist OR" 309 | check_output join_list_3nl "join_list tlist NL" 310 | words=( 311 | apple banana cherry dog elephant fox giraffe hawk indigo manzana milk november 312 | october december january february march april may june july august 313 | ) 314 | check_output join_list_words "join_list words" 315 | check_output join_list_words_nowrap "join_list words NOWRAP" 316 | end_test 317 | } 318 | 319 | test_12_map_list() { 320 | start_test 321 | words=( now is the 'time' 'for' all good men to come to the aid of their country ) 322 | num_words=`list_size words` 323 | 324 | items=( `map_list words 'echo ${#item}'` ) 325 | check_size items $num_words 326 | check_item_equal items 0 3 327 | check_item_equal items 1 2 328 | check_item_equal items 2 3 329 | check_item_equal items 3 4 330 | check_item_equal items 4 3 331 | check_item_equal items 5 3 332 | check_item_equal items 14 5 333 | check_item_equal items 15 7 334 | end_test 335 | } 336 | 337 | test_13_map_list_func_expr() { 338 | start_test 339 | # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 340 | words=( now is the 'time' 'for' all good men to come to the aid of their country ) 341 | num_words=`list_size words` 342 | 343 | function count_vowels() { 344 | local word="$1" 345 | echo "$word" | sed -Ee $'s/(.)/\\1\\\n/g' | egrep -c '[aeiouy]' 346 | } 347 | 348 | items=( `map_list words count_vowels` ) 349 | check_size items $num_words 350 | check_item_equal items 0 1 351 | check_item_equal items 1 1 352 | check_item_equal items 2 1 353 | check_item_equal items 3 2 354 | check_item_equal items 4 1 355 | check_item_equal items 5 1 356 | check_item_equal items 6 2 357 | check_item_equal items 7 1 358 | check_item_equal items 8 1 359 | check_item_equal items 9 2 360 | check_item_equal items 10 1 361 | check_item_equal items 12 2 362 | check_item_equal items 14 2 363 | check_item_equal items 15 3 364 | end_test 365 | 366 | } 367 | 368 | test_14_map_list_joinstr() { 369 | start_test 370 | words=( now is the "time" "for" all good men to come to the aid of their country ) 371 | gen_echo() { echo "unset \"$1\"" ; } 372 | check_output map_list_joinstr "map_list words gen_echo \"\$CHAR_NL\"" 373 | end_test 374 | } 375 | 376 | 377 | test_16_map_list_exprs() { 378 | start_test 379 | numbers=( 0 1 2 3 4 5 6 7 8 9 ) 380 | squares=( `list_map numbers '( x * x )'` ) 381 | check_size numbers `list_size numbers` 382 | check_item_equal squares 2 4 383 | check_item_equal squares 3 9 384 | check_item_equal squares 4 16 385 | check_item_equal squares 5 25 386 | check_item_equal squares 6 36 387 | check_item_equal squares 7 49 388 | check_item_equal squares 8 64 389 | check_item_equal squares 9 81 390 | end_test 391 | } 392 | 393 | test_18_reductions() { 394 | start_test 395 | words=( now is the 'time' 'for' all good men to come to the aid of their country ) 396 | num_words=`list_size words` 397 | items=( `map_list words 'echo ${#item}'` ) 398 | count_chars_orig=`echo "${words[@]}" | wc -c` 399 | count_chars=$(( `sum_list items` + num_words )) 400 | check_eq $count_chars $count_chars_orig "sum_list count error; got $count_chars; should be $count_chars_orig" 401 | min=`min_list items` 402 | check_eq $min 2 "min test failed; got $min, should be 2" 403 | max=`max_list items` 404 | check_eq $max 7 "max test failed; got $max, should be 7" 405 | avg=`avg_list items` 406 | check_eq $avg 3 "avg test failed; got $avg, should be 3" 407 | end_test 408 | } 409 | 410 | test_19_list_help_func() { 411 | start_test 412 | check_output list_help "list_help" 413 | check_output list_init_help "list_init" 414 | check_output list_init_nohelp "list_init foo" 415 | check_output list_add_help "list_add" 416 | check_output list_add_nohelp "list_add foo bar" 417 | check_output list_add_once_help "list_add_once" 418 | check_output list_get_help "list_get" 419 | check_output list_get2_help "list_get nolist" 420 | check_output list_item_help "list_item" 421 | check_output list_item2_help "list_item nolist" 422 | check_output list_push_help "list_push" 423 | check_output list_push2_help "list_push nolist" 424 | end_test 425 | } 426 | 427 | # test to make sure IFS is not changed by list_items and list_join, which alters it 428 | test_20_list_IFS_check() { 429 | start_test 430 | local saveIFS="$IFS" 431 | list_init foo 432 | list_add foo now is the 'time' 'for' all good men to come to the aid 433 | local tmpfile="/tmp/foo.$$" 434 | list_items foo >$tmpfile 435 | check_equal "$IFS" "$saveIFS" 436 | join_list foo >$tmpfile 437 | check_equal "$IFS" "$saveIFS" 438 | end_test 439 | } 440 | 441 | # test the list_remove function 442 | test_21_list_remove() { 443 | start_test 444 | list_init words 445 | list_add words now is the 'time' 'for' all good men to come to the aid of their country 446 | check_size words 16 447 | list_remove words 'is' 448 | # now the time for all good men to come to the aid of their country 449 | check_size words 15 450 | check_item words 1 'the' 451 | list_remove words 'the' 452 | # now time for all good men to come to aid of their country 453 | check_size words 13 454 | check_item words 1 'time' 455 | list_remove words 'to' 456 | # now time for all good men come aid of their country 457 | check_size words 11 458 | check_item words 6 'come' 459 | end_test 460 | } 461 | 462 | 463 | init_tests "$@" 464 | run_tests 465 | summarize_tests 466 | 467 | exit 468 | -------------------------------------------------------------------------------- /test-real-utils.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # test-real-utils.sh 3 | # 4 | # Copyright 2014-2022, Alan K. Stebbens 5 | # 6 | # test-real-utils.sh -- test the real-utils script 7 | 8 | PATH=.:$PATH:$HOME/lib 9 | 10 | source test-utils.sh 11 | source real-utils.sh 12 | 13 | echo "Testing real-utils .." 14 | 15 | # test_cond EXPR [TRUE|FALSE] [SCALE] 16 | function TEST_cond() { 17 | local scale=$real_scale expect=true # set defaults 18 | local expr="$1" ; shift 19 | while [[ $# -gt 0 ]]; do 20 | case "$1" in 21 | true|false) expect=$1 ;; 22 | [0-9]*) scale="$1" ;; 23 | esac 24 | shift 25 | done 26 | if real_cond "$expr" $scale ; then 27 | res=true 28 | else 29 | res=false 30 | fi 31 | check_equal "$res" "$expect" "'$expr' evaluated to '$res', not '$expect'" 32 | } 33 | 34 | # TEST_math 'MATH_EXPRESSION' [SCALE] ANSWER 35 | 36 | function TEST_math() { 37 | local expr="$1" 38 | local scale= answer= 39 | if (( $# > 2 )) ; then 40 | scale="$2" answer="$3" 41 | else 42 | answer="$2" 43 | fi 44 | local res=`real_eval "$expr" $scale` 45 | check_equal "$res" "$answer" "'$expr' evaluated to '$res', not '$answer'" 46 | } 47 | 48 | # Use command line arguments if there are any. 49 | 50 | test_01_basic_arithmetic() { 51 | start_test 52 | TEST_math "scale=3 ; 21.5 / 6.4" "3.359" 53 | TEST_math "scale=2 ; 200.5 / 5.3 + 3.6 * 7.2" "63.75" 54 | TEST_math "scale=1; 12.0 / 3.0" "4.0" 55 | end_test 56 | } 57 | 58 | test_02_cond_evals() { 59 | start_test 60 | TEST_cond "10.1 > 10.0" 61 | TEST_cond "10.1 >= 10.1" 62 | TEST_cond "10.1 < 10.1" false 63 | TEST_cond "10.0 < 9.9" false 64 | TEST_cond "10.0 > 10.01" false 65 | TEST_cond "0.1 >= 0.1" 66 | TEST_cond "0.1 > 0.09" 67 | TEST_cond "0.1 < 0.11" 68 | end_test 69 | } 70 | 71 | test_03_powers() { 72 | start_test 73 | TEST_cond "2^2 == 4" 74 | TEST_cond "2^3 == 8" 75 | TEST_cond "2^4 == 16" 76 | TEST_cond "2^5 == 32" 77 | TEST_cond "2.2^2 == 4.84" 3 78 | TEST_cond "5.2^3 == 140.608" 3 79 | end_test 80 | } 81 | 82 | test_04_int_frac_funcs() { 83 | start_test 84 | TEST_math "int(3)" "3" 85 | TEST_math "int(3.1)" "3" 86 | TEST_math "int(3.99)" "3" 87 | TEST_math "frac(3.99)" ".99" 88 | TEST_math "frac(123.456)" ".456" 89 | TEST_math "int(3)/2" 1 "1.5" 90 | TEST_math "int(3.2)/2" 1 "1.5" 91 | TEST_math "frac(3.2)*2" 1 ".4" 92 | end_test 93 | } 94 | 95 | test_05_trig_funcs() { 96 | start_test 97 | export real_scale=8 98 | TEST_math "sin(0)" "0" 99 | TEST_math "sin(0.1)" ".09983341" 100 | TEST_math "sin(0.2)" ".19866933" 101 | TEST_math "sin(0.3)" ".29552020" 102 | TEST_math "sin(0.4)" ".38941834" 103 | TEST_math "sin(0.5)" ".47942553" 104 | TEST_math "sin(0.6)" ".56464247" 105 | TEST_math "sin(0.7)" ".64421768" 106 | TEST_math "sin(0.8)" ".71735609" 107 | TEST_math "sin(0.9)" ".78332690" 108 | TEST_math "sin(1.0)" ".84147098" 109 | TEST_math "sin(1.1)" ".89120736" 110 | TEST_math "sin(1.2)" ".93203908" 111 | TEST_math "sin(1.3)" ".96355818" 112 | TEST_math "sin(1.4)" ".98544973" 113 | TEST_math "sin(1.5)" ".99749498" 114 | TEST_math "sin(1.57)" ".99999968" 115 | TEST_math "sin(1.6)" ".99957360" 116 | end_test 117 | } 118 | 119 | test_06_conversions() { 120 | start_test 121 | for ((deg = 0; deg <= 360; deg += 5)); do 122 | # test conversion functions 123 | rad=`rad $deg 2` 124 | TEST_math "rad($deg)" 2 "$rad" 125 | deg2=`deg $rad 2` 126 | TEST_math "deg($rad)" 2 "$deg2" 127 | diff=`real_eval '$deg - $deg2'` 128 | # we must allow for a single digit rounding error 129 | TEST_cond "abs($deg - $deg2) <= 1" 130 | # Now test for normalized degrees 131 | # Not ready yet 132 | #rad=`rad $deg 10` # get radians 133 | #sin=`sin $rad 10` # get sine 134 | #asin=`asin $sin 10` # arcsine of sine 135 | #deg3=`ndeg $asin` # convert to normalized degrees 136 | #TEST_cond "abs($deg3 - $deg) < 1" 137 | done 138 | end_test 139 | } 140 | 141 | test_07_round() { 142 | start_test 143 | TEST_math "round(1.4,0)" "1" 144 | TEST_math "round(1.5,0)" "2" 145 | TEST_math "round(1.4,1)" "1.4" 146 | TEST_math "round(1.5,1)" "1.5" 147 | TEST_math "round(1.99,1)" "2.0" 148 | TEST_math "round(1.99,2)" "1.99" 149 | end_test 150 | } 151 | 152 | test_08_real_help() { 153 | start_test 154 | check_output real_help "real_help" 155 | end_test 156 | } 157 | 158 | #if (( BASH_VERSINFO[0] < 4 )); then 159 | # echo "The real-utils library needs bash version 4 or greater" 160 | # exit 2 161 | #fi 162 | 163 | init_tests "$@" 164 | run_tests 165 | summarize_tests 166 | 167 | exit 168 | 169 | # vim: ts=2: sw=2: expandtab 170 | 171 | -------------------------------------------------------------------------------- /test-sh-utils.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # test-sh-utils.sh 3 | # 4 | # Copyright 2006-2022 Alan K. Stebbens 5 | # 6 | # Test module for sh-utils.sh 7 | # 8 | export PATH=.:$PATH 9 | 10 | source sh-utils.sh 11 | source test-utils.sh 12 | 13 | test_01_fn_exists() { 14 | start_test 15 | check_false "fn_exists TEST_test_func" 16 | # create the test func 17 | TEST_test_func() { my_test_ok=1 ; } 18 | check_true 'fn_exists TEST_test_func' 19 | unset -f TEST_test_func 20 | check_false 'fn_exists TEST_test_func' 21 | end_test 22 | } 23 | 24 | test_10_add_trap() { 25 | start_test 26 | local x=$( my_test_ok=2 27 | add_trap 'my_test_ok=3' USR1 28 | kill -USR1 $BASHPID 29 | echo "$my_test_ok" 30 | ) 31 | check_equal $x 3 32 | local y=$( my_test_ok=4 my_new_test=1 33 | add_trap 'my_test_ok=3' USR1 34 | add_trap 'my_new_test=2' USR1 35 | kill -USR1 $BASHPID 36 | sleep .5 37 | echo "$my_test_ok $my_new_test" 38 | ) 39 | check_equal "$y" "3 2" 40 | end_test 41 | } 42 | 43 | init_tests "$@" 44 | run_tests 45 | summarize_tests 46 | exit 47 | -------------------------------------------------------------------------------- /test-talk-utils.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # test-talk-utils.sh 3 | # 4 | # Copyright 2006-2015 Alan K. Stebbens 5 | # 6 | # Test module for talk-utils.sh 7 | # 8 | export PATH=.:$PATH 9 | source talk-utils.sh 10 | source test-utils.sh 11 | 12 | 13 | test_01_talk_basic() { 14 | start_test 15 | check_output talk1 'talk "one"' 16 | check_output talk2 'talk "two"' 17 | check_output talk3 'talk "three"' 18 | end_test 19 | } 20 | 21 | test_02_talk_advanced() { 22 | start_test 23 | check_output talk_w_2_args 'talk "one two three"' 24 | check_output talk_w_2_args2 'talk one two three words' 25 | end_test 26 | } 27 | 28 | test_03_talk_varying_norun_verbose_quiet() { 29 | check_output talk_functions talk_with_varying_norun_verbose_quiet 30 | } 31 | 32 | talk_with_varying_norun_verbose_quiet() { 33 | local norun verbose quiet func 34 | for norun in '' 1 ; do 35 | for verbose in '' 1 ; do 36 | for quiet in '' 1 ; do 37 | for func in vtalk votalk nrtalk nqtalk ; do 38 | echo -n 1>&2 "norun=$norun verbose=$verbose quiet=$quiet : $func says: " 39 | $func "There is output" || echo 1>&2 "" 40 | done 41 | echo 1>&2 "" # separate function test groups 42 | done 43 | done 44 | done 45 | } 46 | 47 | test_04_talkf_basic() { 48 | start_test 49 | check_output talkf_basic_test 50 | end_test 51 | } 52 | 53 | wordlist=( the time has come to talk of ceiling wax and cabbages and kings ) 54 | 55 | talkf_basic_test() { 56 | local x=0 57 | for (( x=0; x < ${#wordlist[*]}; x++ )) ; do 58 | word=${wordlist[$x]} 59 | talkf "The %d word is '%s'\n" $x "$word" 60 | done 61 | } 62 | 63 | test_05_vtalkf_basic() { 64 | start_test 65 | check_output vtalkf_basic_test 66 | end_test 67 | } 68 | 69 | vtalkf_basic_test() { 70 | verobse= norun= 71 | for verbose in '' 1 ; do 72 | vtalkf "Verbose = %s\n" $verbose 73 | done 74 | } 75 | 76 | init_tests "$@" 77 | run_tests 78 | summarize_tests 79 | exit 80 | -------------------------------------------------------------------------------- /test-template.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2006-2022, Alan K. Stebbens 3 | # 4 | # test-template.sh -- a template on which to create new test cases 5 | 6 | PATH=.:$PATH:$HOME/lib 7 | 8 | source test-utils.sh 9 | 10 | test_1_NAME1() { 11 | start_test 12 | 13 | # ... do some operations to be tested 14 | # ... now test the results of the operations 15 | 16 | # check_value VAR ERROR 17 | # check_empty VAR ERROR 18 | # 19 | # array size and item tests 20 | # 21 | # check_size LIST SIZE ERROR # same as check_size_eq 22 | # check_size_XX LIST SIZE ERROR 23 | # check_item VAR INDEX VAL ERROR 24 | # check_item_equal VAR INDEX VAL ERROR 25 | # check_item_unequal VAR INDEX NONVAL ERROR 26 | # 27 | # String value tests 28 | # 29 | # check_equal VAL1 VAL2 ERROR 30 | # check_unequal VAL1 VAL2 ERROR 31 | # check_match VAL1 REGEXP ERROR 32 | # check_nomatch VAL1 REGEXP ERROR 33 | # 34 | # Numeric value tests 35 | # 36 | # check_lt N1 N2 ERROR 37 | # check_le N1 N2 ERROR 38 | # check_eq N1 N2 ERROR 39 | # check_ne N1 N2 ERROR 40 | # check_ge N1 N2 ERROR 41 | # check_gt N1 N2 ERROR 42 | # 43 | # ERROR is optional 44 | #... 45 | #... more operations 46 | 47 | end_test 48 | } 49 | 50 | test_2_NAME2() { 51 | start_test 52 | #.... 53 | end_test 54 | } 55 | 56 | # ... more tests 57 | 58 | # If you pass arguments to init_tests, they will be processed just like 59 | # command-line args (ie? -v == verbose, -e == verbose_errors, -n == norun, 60 | # -h == help) 61 | 62 | init_tests "$@" 63 | run_tests 64 | summarize_tests 65 | exit 66 | -------------------------------------------------------------------------------- /test-text-utils.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # test-text-utils.sh 3 | # 4 | # Copyright 2006-2022, Alan K. Stebbens 5 | # 6 | # test-template.sh -- a template on which to create new test cases 7 | 8 | PATH=.:$PATH:$HOME/lib 9 | 10 | source test-utils.sh 11 | source text-utils.sh 12 | 13 | # lowercase STRING # return the lowercase string 14 | # uppercase STRING # return the uppercase string 15 | # trim STRING # trim blanks surrounding string 16 | # ltrim STRING # trim left-side blanks from STRING 17 | # rtrim STRING # trim right-side blanks from STRING 18 | # squeeze STRING # squeeze multiple blanks in string 19 | # split_str STRING SEP # split STRING using SEP 20 | # html_encode STRING # encode STRING for HTML 21 | 22 | # run_filter_tests FUNC INPUT OUTPUT ERROR 23 | 24 | do_test1() { do_filter_test 1 "$@" ; } 25 | do_test2() { do_filter_test 2 "$@" ; } 26 | do_test() { do_test2 "$@" ; } 27 | 28 | do_filter_test() { 29 | local howmany="$1" 30 | local func="$2" 31 | local input="$3" 32 | local output="$4" 33 | local error="$5" 34 | 35 | if [[ -n "$error" ]]; then 36 | error="Failed $func test with $error" 37 | else 38 | error="Failed $func test with '$input'" 39 | fi 40 | # test function arg call first 41 | local out="`$func \"$input\"`" 42 | check_and_show_results "$output" "$out" "$error" 43 | 44 | if (( howmany > 1 )); then 45 | # test pipe with no arg call next 46 | out="`echo -n \"$input\" | $func`" 47 | check_and_show_results "$output" "$out" "$error in pipe" 48 | fi 49 | } 50 | 51 | # check_and_show_results "$OUTPUT" "$GOT" "$ERROR" 52 | 53 | check_and_show_results() { 54 | if ! check_equal "$1" "$2" "$3" ; then 55 | echo 1>&2 '' 56 | echo 1>&2 "expected: $1" 57 | echo 1>&2 " got: $2" 58 | fi 59 | } 60 | 61 | test_01_lowercase() { 62 | start_test 63 | do_test lowercase 'ABC' 'abc' 64 | do_test lowercase 'AbC' 'abc' 65 | do_test lowercase 'Now Is The Time For All Good Men' \ 66 | 'now is the time for all good men' \ 67 | "long sentence" 68 | end_test 69 | } 70 | 71 | test_02_uppercase() { 72 | start_test 73 | do_test uppercase 'abc' 'ABC' 74 | do_test uppercase 'AbC' 'ABC' 75 | do_test uppercase 'Now Is The Time For All Good Men' \ 76 | 'NOW IS THE TIME FOR ALL GOOD MEN' \ 77 | "long sentence" 78 | end_test 79 | } 80 | 81 | # trim STRING # trim blanks surrounding string 82 | test_03_trim() { 83 | start_test 84 | do_test trim "no blanks to trim" "no blanks to trim" 85 | do_test trim " one blank in front" "one blank in front" 86 | do_test trim "one blank at end " "one blank at end" 87 | do_test trim " blanks at either end " "blanks at either end" 88 | end_test 89 | } 90 | 91 | # ltrim STRING # trim left-side blanks from STRING 92 | test_04_ltrim() { 93 | start_test 94 | do_test ltrim "no blanks to trim" "no blanks to trim" 95 | do_test ltrim " one blank in front" "one blank in front" 96 | do_test ltrim " two blanks in front" "two blanks in front" 97 | do_test ltrim "one blank at end " "one blank at end " 98 | do_test ltrim " blanks at either end " "blanks at either end " 99 | do_test ltrim " blanks all over " "blanks all over " 100 | end_test 101 | } 102 | 103 | # rtrim STRING # trim right-side blanks from STRING 104 | test_05_rtrim() { 105 | start_test 106 | do_test rtrim "no blanks to trim" "no blanks to trim" 107 | do_test rtrim " one blank in front test" " one blank in front test" 108 | do_test rtrim " one blank at end test " " one blank at end test" 109 | do_test rtrim " blanks at either end test " " blanks at either end test" 110 | do_test rtrim " blanks all over " " blanks all over" 111 | end_test 112 | } 113 | 114 | # squeeze STRING # squeeze multiple blanks in string 115 | test_06_squeeze() { 116 | start_test 117 | do_test squeeze "no extra blanks to squeeze" "no extra blanks to squeeze" 118 | do_test squeeze " blank in front test" " blank in front test" 119 | do_test squeeze "blank at end test " "blank at end test " 120 | do_test squeeze " blanks at either end test " " blanks at either end test " 121 | do_test squeeze " blanks all over " " blanks all over " 122 | end_test 123 | } 124 | 125 | # split_str STRING SEP # split STRING using SEP 126 | # __split_str STRING SEP 127 | test_07_split_str() { 128 | local func 129 | start_test 130 | 131 | for func in split_str __split_str ; do 132 | out=( `$func "A list of words to split" ' '` ) 133 | 134 | check_split_data() { 135 | check_size out 6 136 | check_item out 0 'A' 137 | check_item out 1 'list' 138 | check_item out 2 'of' 139 | check_item out 3 'words' 140 | check_item out 4 'to' 141 | check_item out 5 'split' 142 | out=() 143 | } 144 | check_split_data 145 | 146 | out=( `$func "A,list,of,words,to,split" ','` ) 147 | check_split_data 148 | 149 | out=( `$func "A:list:of:words:to:split" ':'` ) 150 | check_split_data 151 | 152 | out=( `$func "A, list, of, words, to, split" ", "` ) 153 | check_split_data 154 | done 155 | 156 | end_test 157 | } 158 | 159 | test_08_url_encode() { 160 | start_test 161 | do_test url_encode "this_is_a_plain_string" "this_is_a_plain_string" 162 | do_test url_encode "this is a plain string" "this%20is%20a%20plain%20string" 163 | do_test url_encode "this(is)[a]{plain}'string'" "this%28is%29%5Ba%5D%7Bplain%7D%27string%27" 164 | end_test 165 | } 166 | 167 | test_09_url_decode() { 168 | start_test 169 | 170 | for string in "this_is_a_plain_string" \ 171 | "this is a plain string" \ 172 | "this(is)[a]{plain}'string'" \ 173 | "~!@#$%^&*()_+1234567890-=qwertyuiop[]\\{}|asdfghjkl;':zxcvbbbnnm,./<>?" ; do 174 | enc_out=`url_encode "$string"` 175 | dec_out=`url_decode "$enc_out"` 176 | check_equal "$string" "$dec_out" "Failed encode/decode test on '$string'" 177 | done 178 | 179 | end_test 180 | } 181 | 182 | test_10_html_encode() { 183 | start_test 184 | do_test html_encode "this_is_a_plain_string" "this_is_a_plain_string" 185 | do_test html_encode "" "<this is a token>" 186 | do_test html_encode "" "<token1><token2>" 187 | end_test 188 | } 189 | 190 | test_11_html_decode() { 191 | start_test 192 | 193 | for string in "this_is_a_plain_string" \ 194 | "this is a plain string" \ 195 | "this(is)[a]{plain}'string'" \ 196 | "~!@#$%^&*()_+1234567890-=qwertyuiop[]\\{}|asdfghjkl;':zxcvbbbnnm,./<>?" ; do 197 | enc_out=`html_encode "$string"` 198 | dec_out=`html_decode "$enc_out"` 199 | check_equal "$string" "$dec_out" "Failed html encode/decode test on '$string'" 200 | done 201 | 202 | end_test 203 | } 204 | 205 | test_20_args2lines() { 206 | start_test 207 | do_test args2lines "this is a list of words" $'this\nis\na\nlist\nof\nwords' 208 | end_test 209 | } 210 | 211 | test_22_sort_str2lines() { 212 | start_test 213 | do_test1 sort_str2lines "this is a list of words" $'a\nis\nlist\nof\nthis\nwords' 214 | end_test 215 | } 216 | 217 | test_24_sort_str() { 218 | start_test 219 | words="now is the time for all good men to come to the aid of their country" 220 | sorted_words=`sort_str $words` 221 | check_unequal "$sorted_words" "$words" 222 | more_sorted_words=`str_sort $words` 223 | check_unequal "$more_sorted_words" "$words" 224 | end_test 225 | } 226 | 227 | init_tests "$@" 228 | run_tests 229 | summarize_tests 230 | exit 231 | -------------------------------------------------------------------------------- /test-time-utils.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2014-2022, Alan K. Stebbens 3 | # 4 | # test time-utils.sh 5 | # 6 | # uses test-utils.sh 7 | 8 | PATH=.:$PATH:$HOME/lib 9 | 10 | source test-utils.sh 11 | 12 | . time-utils.sh 13 | 14 | testdata='test-times.dat' 15 | 16 | # check_time TIMESTRING HH MM SS 17 | 18 | check_time() { 19 | time_parse "$1" 20 | check_eq $hours $2 21 | check_eq $mins $3 22 | check_eq $secs $4 23 | } 24 | 25 | test_01_parse_time() { 26 | start_test 27 | check_time '00:00:00' 0 0 0 28 | check_time '12:00:00' 12 0 0 29 | check_time '12:01:02' 12 1 2 30 | check_time '23:22:11' 23 22 11 31 | check_time '23:59:59' 23 59 59 32 | check_time '12:01' 12 01 00 33 | end_test 34 | } 35 | 36 | # check_t2secs TIMESTRING SECS 37 | 38 | check_t2secs() { 39 | local secs=`time2secs "$1"` 40 | check_eq $secs "$2" 41 | } 42 | 43 | test_10_time2secs() { 44 | start_test 45 | check_t2secs '00:00:01' 1 46 | check_t2secs '00:00:59' 59 47 | check_t2secs '00:01:00' 60 48 | check_t2secs '00:01:59' 119 49 | check_t2secs '00:11:22' 682 # 11*60+22 50 | check_t2secs '00:59:59' 3599 51 | check_t2secs '01:00:00' 3600 52 | check_t2secs '01:02:03' 3723 53 | check_t2secs '12:34:56' 45296 54 | check_t2secs '23:59:59' 86399 55 | end_test 56 | } 57 | 58 | init_tests "$@" 59 | run_tests 60 | summarize_tests 61 | exit 62 | -------------------------------------------------------------------------------- /test/hash1_init_test.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/hash1_init_test.err.ref -------------------------------------------------------------------------------- /test/hash1_init_test.out.ref: -------------------------------------------------------------------------------- 1 | hash: hash1 2 | size: 0 3 | -------------------------------------------------------------------------------- /test/hash1a_init_test.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/hash1a_init_test.err.ref -------------------------------------------------------------------------------- /test/hash1a_init_test.out.ref: -------------------------------------------------------------------------------- 1 | hash: hash1a 2 | size: 0 3 | -------------------------------------------------------------------------------- /test/hash1b_init_test.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/hash1b_init_test.err.ref -------------------------------------------------------------------------------- /test/hash1b_init_test.out.ref: -------------------------------------------------------------------------------- 1 | hash: hash1b 2 | size: 0 3 | default: NONE 4 | -------------------------------------------------------------------------------- /test/hash2_info.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/hash2_info.err.ref -------------------------------------------------------------------------------- /test/hash2_info.out.ref: -------------------------------------------------------------------------------- 1 | hash: hash2 2 | size: 5 3 | -------------------------------------------------------------------------------- /test/hash2_init_test.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/hash2_init_test.err.ref -------------------------------------------------------------------------------- /test/hash2_init_test.out.ref: -------------------------------------------------------------------------------- 1 | hash: hash2 2 | size: 0 3 | default: NONE 4 | -------------------------------------------------------------------------------- /test/hash3_info.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/hash3_info.err.ref -------------------------------------------------------------------------------- /test/hash3_info.out.ref: -------------------------------------------------------------------------------- 1 | hash: hash3 2 | size: 5 3 | -------------------------------------------------------------------------------- /test/join_list_3.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/join_list_3.err.ref -------------------------------------------------------------------------------- /test/join_list_3.out.ref: -------------------------------------------------------------------------------- 1 | apple, banana, cherry 2 | -------------------------------------------------------------------------------- /test/join_list_3and.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/join_list_3and.err.ref -------------------------------------------------------------------------------- /test/join_list_3and.out.ref: -------------------------------------------------------------------------------- 1 | apple 2 | and banana 3 | and cherry 4 | -------------------------------------------------------------------------------- /test/join_list_3nl.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/join_list_3nl.err.ref -------------------------------------------------------------------------------- /test/join_list_3nl.out.ref: -------------------------------------------------------------------------------- 1 | apple 2 | banana 3 | cherry 4 | -------------------------------------------------------------------------------- /test/join_list_3or.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/join_list_3or.err.ref -------------------------------------------------------------------------------- /test/join_list_3or.out.ref: -------------------------------------------------------------------------------- 1 | apple 2 | or banana 3 | or cherry 4 | -------------------------------------------------------------------------------- /test/join_list_3sp.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/join_list_3sp.err.ref -------------------------------------------------------------------------------- /test/join_list_3sp.out.ref: -------------------------------------------------------------------------------- 1 | apple banana cherry 2 | -------------------------------------------------------------------------------- /test/join_list_3tab.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/join_list_3tab.err.ref -------------------------------------------------------------------------------- /test/join_list_3tab.out.ref: -------------------------------------------------------------------------------- 1 | apple banana cherry 2 | -------------------------------------------------------------------------------- /test/join_list_words.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/join_list_words.err.ref -------------------------------------------------------------------------------- /test/join_list_words.out.ref: -------------------------------------------------------------------------------- 1 | apple, banana, cherry, dog, elephant, fox, giraffe, hawk, indigo, manzana, milk 2 | , november, october, december, january, february, march, april, may, june, july 3 | , august 4 | -------------------------------------------------------------------------------- /test/join_list_words_nowrap.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/join_list_words_nowrap.err.ref -------------------------------------------------------------------------------- /test/join_list_words_nowrap.out.ref: -------------------------------------------------------------------------------- 1 | apple, banana, cherry, dog, elephant, fox, giraffe, hawk, indigo, manzana, milk, november, october, december, january, february, march, april, may, june, july, august 2 | -------------------------------------------------------------------------------- /test/list_add_help.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/list_add_help.err.ref -------------------------------------------------------------------------------- /test/list_add_help.out.ref: -------------------------------------------------------------------------------- 1 | list_add VAR VAL1 [VAL2 ...] # add VAL1.. to the end of VAR 2 | -------------------------------------------------------------------------------- /test/list_add_nohelp.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/list_add_nohelp.err.ref -------------------------------------------------------------------------------- /test/list_add_nohelp.out.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/list_add_nohelp.out.ref -------------------------------------------------------------------------------- /test/list_add_once_help.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/list_add_once_help.err.ref -------------------------------------------------------------------------------- /test/list_add_once_help.out.ref: -------------------------------------------------------------------------------- 1 | list_add_once VAR VAL1 [VAL2 ..] # add VAL1.. uniquely to the end of VAR 2 | -------------------------------------------------------------------------------- /test/list_get2_help.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/list_get2_help.err.ref -------------------------------------------------------------------------------- /test/list_get2_help.out.ref: -------------------------------------------------------------------------------- 1 | list_get VAR N # get the Nth item of VAR to stdout 2 | -------------------------------------------------------------------------------- /test/list_get_help.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/list_get_help.err.ref -------------------------------------------------------------------------------- /test/list_get_help.out.ref: -------------------------------------------------------------------------------- 1 | list_get VAR N # get the Nth item of VAR to stdout 2 | -------------------------------------------------------------------------------- /test/list_help.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/list_help.err.ref -------------------------------------------------------------------------------- /test/list_help.out.ref: -------------------------------------------------------------------------------- 1 | These are the list utilities: 2 | 3 | list_init VAR # initialize VAR as an empty list 4 | 5 | list_add VAR VAL1 [VAL2 ...] # add VAL1.. to the end of VAR 6 | 7 | add_list VAR VAL1 [VAL2 ...] # alias to list_add 8 | 9 | list_add_once VAR VAL1 [VAL2 ..] # add VAL1.. uniquely to the end of VAR 10 | 11 | add_list_once VAR VAL ... # alias to list_add_once 12 | 13 | list_remove VAR VAL1 [VAL2 ..] # remove VAL1 .. from the VAR list 14 | 15 | remove_list_item VAR VAL1 [VAL2 ..] # alias to list_remove 16 | 17 | list_push VAR VAL ... # alias to list_add 18 | 19 | push_list VAR VAL ... # alias to list_add 20 | 21 | list_insert VAR VAL ... # insert VAL.. at the front of VAR 22 | 23 | list_insert_once VAR VAL ... # insert VAL.. at the front of VAR 24 | 25 | insert_list VAR VAL ... # alias to list_insert 26 | 27 | insert_list_once VAR VAL ... # alias to list_insert_once 28 | 29 | list_pop VAR # removes top VAL on VAR and returns in variable "item" 30 | 31 | pop_list VAR # alias to list_pop 32 | 33 | list_get VAR N # get the Nth item of VAR to stdout 34 | 35 | get_list_item VAR N # alias to list_get 36 | 37 | list_item VAR N # set 'item' to the Nth item of VAR 38 | 39 | list_set VAR N VAL # set the Nth item of VAR to VAL 40 | 41 | list_set_item VAR N VAL # alias to list_set 42 | 43 | set_list_item VAR N VAL # alias to "list_set" 44 | 45 | list_items VAR [START [END]] # return list items from START to END (or all) 46 | 47 | list_copy LIST NEWLIST [START [END]] # copy list LIST to NEWLIST, from START to END 48 | 49 | copy_list LIST NEWLIST [START [END]] # alias to list_copy 50 | 51 | in_list VAR [-any|-all] VAL ... # return true if one or more values are in a list 52 | 53 | list_member VAR [-any|-all] VAL ... # same as in_list 54 | 55 | list_size VAR # returns the number of items 56 | 57 | list_dump VAR # dump the list VAR, showing indexes and values 58 | 59 | dump_list VAR # alias to list_dump 60 | 61 | list_sort VAR # sort the contents of VAR (a list) in place 62 | 63 | sort_list VAR # alias to list_sort 64 | 65 | list_sorted VAR # output the items of list VAR sorted 66 | 67 | sorted_list VAR # alias to list_sorted 68 | 69 | sort_list2lines LIST # sort LIST with each item in a separate line 70 | 71 | split_into VAR "STRING" SEP # split "STRING" by SEP into VAR 72 | 73 | list_join VAR [SEP] .. # join the items in VAR into a list, separated by SEP, 74 | SEP can be 75 | AND -- separate with " and " 76 | OR -- separate with " or " 77 | KEYS -- enclose each item with X' and ', follwed by ',' 78 | TAB -- use tabs to separate items 79 | NL -- separate each item with newline (and some spaces) 80 | NOWRAP -- do not auto-wrap long lines (default is WRAP) 81 | ',' -- separate items with a comma (default) 82 | str -- separate each item with an given string. 83 | 84 | join_list VAR [SEP] .. # alias to list_join 85 | 86 | join_lines # read STDIN and catenate lines; remove trailing NL 87 | 88 | list_lookup LISTVAR KEY # lookup KEY in LISTVAR 89 | 90 | lookup_list LISTVAR KEY # alias to list_lookup 91 | 92 | list_grep LISTVAR PAT # grep PAT across the contents of LISTVAR 93 | 94 | grep_list LISTVAR PAT # alias to list_grep 95 | 96 | list_map LISTVAR EXPR [JOINSTR] # create a list of EXPR applied to each item in LISTVAR 97 | 98 | map_list LISTVAR EXPR [JOINSTR] # alias to list_map 99 | 100 | list_reduce LISTVAR EXPR [INIT] # reduce LISTVAR using EXPR, with initial value INIT 101 | 102 | reduce_list LISTVAR EXPR [INIT] # alias to list_reduce 103 | 104 | list_sum LISTVAR # sum the items in LISTVAR 105 | 106 | sum_list LISTVAR # alias to list_sum 107 | 108 | list_max LISTVAR # return the maximum item in LISTVAR 109 | 110 | max_list LISTVAR # alias to list_max 111 | 112 | list_min LISTVAR # return the minimum item in LISTVAR 113 | 114 | min_list LISTVAR # alias to list_min 115 | 116 | list_avg LISTVAR # return the average of the items in LISTVAR 117 | 118 | avg_list LISTVAR # alias to list_avg 119 | 120 | list_print LISTVAR [indent=INDENT] [width=WIDTH] [sep=SEP] [cols=COLS] 121 | 122 | list_print LISTVAR [i=INDENT] [w=WIDTH] [s=SEP] [c=COLS] 123 | 124 | print the items in LIST in vertically-sorted columns. Use COLS if given, 125 | otherwise the number of columns is computed from WIDTH (defaults to 80) and 126 | the maximum width of all the items in LISTVAR 127 | 128 | print_list LISTVAR [indent=INDENT] [widht=WIDTH] [sep=SEP] [cols=COLS] 129 | 130 | print_list LISTVAR [i=INDENT] [w=WIDTH] [s=SEP] [c=COLS] 131 | 132 | aliases to list_print 133 | 134 | list_help # describe the list functions 135 | 136 | There are many aliases to the functions above: for a given "list_XXX" function, 137 | there exist an alias "XXX_list". For example, "max_list" => "list_max", 138 | "dump_list" => "list_dump", "help_list" => "list_help", etc. 139 | 140 | -------------------------------------------------------------------------------- /test/list_init_help.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/list_init_help.err.ref -------------------------------------------------------------------------------- /test/list_init_help.out.ref: -------------------------------------------------------------------------------- 1 | list_init VAR # initialize VAR as an empty list 2 | -------------------------------------------------------------------------------- /test/list_init_nohelp.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/list_init_nohelp.err.ref -------------------------------------------------------------------------------- /test/list_init_nohelp.out.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/list_init_nohelp.out.ref -------------------------------------------------------------------------------- /test/list_item2_help.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/list_item2_help.err.ref -------------------------------------------------------------------------------- /test/list_item2_help.out.ref: -------------------------------------------------------------------------------- 1 | list_item VAR N # set 'item' to the Nth item of VAR 2 | -------------------------------------------------------------------------------- /test/list_item_help.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/list_item_help.err.ref -------------------------------------------------------------------------------- /test/list_item_help.out.ref: -------------------------------------------------------------------------------- 1 | list_item VAR N # set 'item' to the Nth item of VAR 2 | -------------------------------------------------------------------------------- /test/list_push2_help.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/list_push2_help.err.ref -------------------------------------------------------------------------------- /test/list_push2_help.out.ref: -------------------------------------------------------------------------------- 1 | list_add VAR VAL1 [VAL2 ...] # add VAL1.. to the end of VAR 2 | -------------------------------------------------------------------------------- /test/list_push_help.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/list_push_help.err.ref -------------------------------------------------------------------------------- /test/list_push_help.out.ref: -------------------------------------------------------------------------------- 1 | list_add VAR VAL1 [VAL2 ...] # add VAL1.. to the end of VAR 2 | -------------------------------------------------------------------------------- /test/map_list_joinstr.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/map_list_joinstr.err.ref -------------------------------------------------------------------------------- /test/map_list_joinstr.out.ref: -------------------------------------------------------------------------------- 1 | unset now 2 | unset is 3 | unset the 4 | unset time 5 | unset for 6 | unset all 7 | unset good 8 | unset men 9 | unset to 10 | unset come 11 | unset to 12 | unset the 13 | unset aid 14 | unset of 15 | unset their 16 | unset country 17 | -------------------------------------------------------------------------------- /test/phout1.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/phout1.err.ref -------------------------------------------------------------------------------- /test/phout1.out.ref: -------------------------------------------------------------------------------- 1 | hash10['apple']='0' hash10['february']='15' hash10['manzana']='9' 2 | hash10['april']='17' hash10['fox']='5' hash10['march']='16' 3 | hash10['august']='21' hash10['giraffe']='6' hash10['may']='18' 4 | hash10['banana']='1' hash10['hawk']='7' hash10['milk']='10' 5 | hash10['cherry']='2' hash10['indigo']='8' hash10['november']='11' 6 | hash10['december']='13' hash10['january']='14' hash10['october']='12' 7 | hash10['dog']='3' hash10['july']='20' 8 | hash10['elephant']='4' hash10['june']='19' 9 | -------------------------------------------------------------------------------- /test/phout2.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/phout2.err.ref -------------------------------------------------------------------------------- /test/phout2.out.ref: -------------------------------------------------------------------------------- 1 | hash10['apple']='0' hash10['february']='15' hash10['manzana']='9' 2 | hash10['april']='17' hash10['fox']='5' hash10['march']='16' 3 | hash10['august']='21' hash10['giraffe']='6' hash10['may']='18' 4 | hash10['banana']='1' hash10['hawk']='7' hash10['milk']='10' 5 | hash10['cherry']='2' hash10['indigo']='8' hash10['november']='11' 6 | hash10['december']='13' hash10['january']='14' hash10['october']='12' 7 | hash10['dog']='3' hash10['july']='20' 8 | hash10['elephant']='4' hash10['june']='19' 9 | -------------------------------------------------------------------------------- /test/phout3.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/phout3.err.ref -------------------------------------------------------------------------------- /test/phout3.out.ref: -------------------------------------------------------------------------------- 1 | hash10['apple']='0' hash10['dog']='3' hash10['indigo']='8' hash10['may']='18' 2 | hash10['april']='17' hash10['elephant']='4' hash10['january']='14' hash10['milk']='10' 3 | hash10['august']='21' hash10['february']='15' hash10['july']='20' hash10['november']='11' 4 | hash10['banana']='1' hash10['fox']='5' hash10['june']='19' hash10['october']='12' 5 | hash10['cherry']='2' hash10['giraffe']='6' hash10['manzana']='9' 6 | hash10['december']='13' hash10['hawk']='7' hash10['march']='16' 7 | -------------------------------------------------------------------------------- /test/phout4.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/phout4.err.ref -------------------------------------------------------------------------------- /test/phout4.out.ref: -------------------------------------------------------------------------------- 1 | hash10['apple']='0' hash10['february']='15' hash10['manzana']='9' 2 | hash10['april']='17' hash10['fox']='5' hash10['march']='16' 3 | hash10['august']='21' hash10['giraffe']='6' hash10['may']='18' 4 | hash10['banana']='1' hash10['hawk']='7' hash10['milk']='10' 5 | hash10['cherry']='2' hash10['indigo']='8' hash10['november']='11' 6 | hash10['december']='13' hash10['january']='14' hash10['october']='12' 7 | hash10['dog']='3' hash10['july']='20' 8 | hash10['elephant']='4' hash10['june']='19' 9 | -------------------------------------------------------------------------------- /test/phout5.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/phout5.err.ref -------------------------------------------------------------------------------- /test/phout5.out.ref: -------------------------------------------------------------------------------- 1 | hash10['apple']='0' hash10['hawk']='7' 2 | hash10['april']='17' hash10['indigo']='8' 3 | hash10['august']='21' hash10['january']='14' 4 | hash10['banana']='1' hash10['july']='20' 5 | hash10['cherry']='2' hash10['june']='19' 6 | hash10['december']='13' hash10['manzana']='9' 7 | hash10['dog']='3' hash10['march']='16' 8 | hash10['elephant']='4' hash10['may']='18' 9 | hash10['february']='15' hash10['milk']='10' 10 | hash10['fox']='5' hash10['november']='11' 11 | hash10['giraffe']='6' hash10['october']='12' 12 | -------------------------------------------------------------------------------- /test/plout1.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/plout1.err.ref -------------------------------------------------------------------------------- /test/plout1.out.ref: -------------------------------------------------------------------------------- 1 | apple banana dog fox indigo june may october 2 | april cherry elephant giraffe january manzana milk 3 | august december february hawk july march november 4 | -------------------------------------------------------------------------------- /test/plout2.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/plout2.err.ref -------------------------------------------------------------------------------- /test/plout2.out.ref: -------------------------------------------------------------------------------- 1 | apple banana dog fox indigo june may october 2 | april cherry elephant giraffe january manzana milk 3 | august december february hawk july march november 4 | -------------------------------------------------------------------------------- /test/plout3.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/plout3.err.ref -------------------------------------------------------------------------------- /test/plout3.out.ref: -------------------------------------------------------------------------------- 1 | apple dog indigo may 2 | april elephant january milk 3 | august february july november 4 | banana fox june october 5 | cherry giraffe manzana 6 | december hawk march 7 | -------------------------------------------------------------------------------- /test/plout4.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/plout4.err.ref -------------------------------------------------------------------------------- /test/plout4.out.ref: -------------------------------------------------------------------------------- 1 | apple february manzana 2 | april fox march 3 | august giraffe may 4 | banana hawk milk 5 | cherry indigo november 6 | december january october 7 | dog july 8 | elephant june 9 | -------------------------------------------------------------------------------- /test/plout5.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/plout5.err.ref -------------------------------------------------------------------------------- /test/plout5.out.ref: -------------------------------------------------------------------------------- 1 | apple hawk 2 | april indigo 3 | august january 4 | banana july 5 | cherry june 6 | december manzana 7 | dog march 8 | elephant may 9 | february milk 10 | fox november 11 | giraffe october 12 | -------------------------------------------------------------------------------- /test/plouta1.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/plouta1.err.ref -------------------------------------------------------------------------------- /test/plouta1.out.ref: -------------------------------------------------------------------------------- 1 | .aliases* .inputrc 2 | .bashrc* .profile 3 | .cshrc* .prompts* 4 | .environment* .vim* 5 | .git-bash-prompt src/github/aks/maximum-awesome 6 | -------------------------------------------------------------------------------- /test/plouta2.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/plouta2.err.ref -------------------------------------------------------------------------------- /test/plouta2.out.ref: -------------------------------------------------------------------------------- 1 | .aliases* .inputrc 2 | .bashrc* .profile 3 | .cshrc* .prompts* 4 | .environment* .vim* 5 | .git-bash-prompt src/github/aks/maximum-awesome 6 | -------------------------------------------------------------------------------- /test/plouta3.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/plouta3.err.ref -------------------------------------------------------------------------------- /test/plouta3.out.ref: -------------------------------------------------------------------------------- 1 | .aliases* .inputrc 2 | .bashrc* .profile 3 | .cshrc* .prompts* 4 | .environment* .vim* 5 | .git-bash-prompt src/github/aks/maximum-awesome 6 | -------------------------------------------------------------------------------- /test/plouta4.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/plouta4.err.ref -------------------------------------------------------------------------------- /test/plouta4.out.ref: -------------------------------------------------------------------------------- 1 | .aliases* .git-bash-prompt .vim* 2 | .bashrc* .inputrc src/github/aks/maximum-awesome 3 | .cshrc* .profile 4 | .environment* .prompts* 5 | -------------------------------------------------------------------------------- /test/plouta5.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/plouta5.err.ref -------------------------------------------------------------------------------- /test/plouta5.out.ref: -------------------------------------------------------------------------------- 1 | .aliases* 2 | .bashrc* 3 | .cshrc* 4 | .environment* 5 | .git-bash-prompt 6 | .inputrc 7 | .profile 8 | .prompts* 9 | .vim* 10 | src/github/aks/maximum-awesome 11 | -------------------------------------------------------------------------------- /test/plouta6.err.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/plouta6.err.ref -------------------------------------------------------------------------------- /test/plouta6.out.ref: -------------------------------------------------------------------------------- 1 | .aliases* 2 | .bashrc* 3 | .cshrc* 4 | .environment* 5 | .git-bash-prompt 6 | .inputrc 7 | .profile 8 | .prompts* 9 | .vim* 10 | src/github/aks/maximum-awesome 11 | -------------------------------------------------------------------------------- /test/real_help.err.ref: -------------------------------------------------------------------------------- 1 | real-utils.sh is a bash library that enables real number arithmetic in bash 2 | scripts. Real numbers are managed as flaoting point strings in the format 3 | "X.Y", where X is the integer portion, and "Y" is the fractional part. 4 | 5 | Usage: 6 | 7 | source real-utils.sh 8 | 9 | real_compute "EXPRESSIN" [SCALE] 10 | 11 | real_eval "EXPRESSION" [SCALE] 12 | 13 | real_cond EXPRESSION [SCALE] 14 | 15 | real_int REAL 16 | 17 | real_frac REAL 18 | 19 | real_help 20 | 21 | Descriptions: 22 | 23 | real_compute "EXPRESSION" [SCALE] 24 | 25 | The `real_compute` bash function evaluates `EXPRESSION` using syntax, operators 26 | and functions as described in the `bc` manual. All numbers and variables 27 | within `EXPRESSION` are interpreted by `bc`. The result of the computation is 28 | output to `STDOUT`. 29 | 30 | If an error occurs, there is no indication. This function does not set a 31 | return code, nor does it set the shell status variable `$?`. Use `real_eval` 32 | for those effects. 33 | 34 | In addition to the operators and functions defined by `bc`, the following 35 | additional functions are also made available within the `EXPRESSION`: 36 | 37 | abs(x) deg(x) log10(x) rad(x) 38 | acos(x) exp(x) logn(x) round(x,s) 39 | asin(x) frac(x) ndeg(x) sin(x) 40 | atan(x) int(x) pi() tan(x) 41 | cos(x) log(x) pow(x,y) 42 | 43 | To see the `bc` definitions of these functions, use the `real_functions` 44 | function. 45 | 46 | real_eval "EXPRESSION" [SCALE] 47 | 48 | The `real_eval` bash function invokes `real_compute` on the arguments, prints 49 | the result on `STDOUT`, and returns with the `bc` return code `$?` (0 or 1, for 50 | success or error, respectively). 51 | 52 | real_cond "EXPRESSION" [SCALE] 53 | 54 | `EXPRESSION` is a real number conditional which should evaluate to 1 or 0. The 55 | return status is 0 for true, 1 for false. Example usage: 56 | 57 | if real_cond "$num < $max" 2 ; then 58 | ... 59 | fi 60 | 61 | 62 | real_scale=NUM 63 | 64 | Set the precision of subsequent real number arithmetic results. The 65 | default is 2. 66 | 67 | real_int REAL -- outputs the integer portion of a REAL number 68 | real_frac REAL -- outputs the fractional portion of a REAL number 69 | 70 | sin R, cos R, tan R -- trig functions on radians R 71 | asin X, acos X, atan X -- inverse trig functions 72 | cotan X, sec X, cosec X -- cotangent, secant, cosecant 73 | arccot X -- arc-cotangent 74 | hypot X Y -- hypotenuse X, Y [sqrt(X^2 + Y^2)] 75 | sqrt X -- square-root of X 76 | logn X, log X -- natural log, log base 10 77 | exp X -- exponent X of E (e.g., e^X) 78 | pow X Y -- power function [X^Y] 79 | rad D -- convert degrees D to radians 80 | deg R -- convert radians R to degrees 81 | ndeg R -- convert radians R to natural degrees (0..360) 82 | round X S -- Round X to S decimals. When S=0, rounds to the nearest integer. 83 | real_int X -- outputs integer portion of X 84 | real_frac X -- outputs fractional portion of X 85 | abs X -- Return the absolute value of X. 86 | 87 | PI = 3.141592653589793 88 | TAU = 6.283185307179586 # 2*PI 89 | E = 2.718281828459045 90 | 91 | -------------------------------------------------------------------------------- /test/real_help.out.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/real_help.out.ref -------------------------------------------------------------------------------- /test/talk1.err.ref: -------------------------------------------------------------------------------- 1 | one 2 | -------------------------------------------------------------------------------- /test/talk1.out.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/talk1.out.ref -------------------------------------------------------------------------------- /test/talk2.err.ref: -------------------------------------------------------------------------------- 1 | two 2 | -------------------------------------------------------------------------------- /test/talk2.out.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/talk2.out.ref -------------------------------------------------------------------------------- /test/talk3.err.ref: -------------------------------------------------------------------------------- 1 | three 2 | -------------------------------------------------------------------------------- /test/talk3.out.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/talk3.out.ref -------------------------------------------------------------------------------- /test/talk_functions.err.ref: -------------------------------------------------------------------------------- 1 | norun= verbose= quiet= : vtalk says: 2 | norun= verbose= quiet= : votalk says: 3 | norun= verbose= quiet= : nrtalk says: 4 | norun= verbose= quiet= : nqtalk says: There is output 5 | 6 | norun= verbose= quiet=1 : vtalk says: 7 | norun= verbose= quiet=1 : votalk says: 8 | norun= verbose= quiet=1 : nrtalk says: 9 | norun= verbose= quiet=1 : nqtalk says: 10 | 11 | norun= verbose=1 quiet= : vtalk says: There is output 12 | norun= verbose=1 quiet= : votalk says: There is output 13 | norun= verbose=1 quiet= : nrtalk says: 14 | norun= verbose=1 quiet= : nqtalk says: There is output 15 | 16 | norun= verbose=1 quiet=1 : vtalk says: There is output 17 | norun= verbose=1 quiet=1 : votalk says: There is output 18 | norun= verbose=1 quiet=1 : nrtalk says: 19 | norun= verbose=1 quiet=1 : nqtalk says: 20 | 21 | norun=1 verbose= quiet= : vtalk says: There is output 22 | norun=1 verbose= quiet= : votalk says: 23 | norun=1 verbose= quiet= : nrtalk says: There is output 24 | norun=1 verbose= quiet= : nqtalk says: There is output 25 | 26 | norun=1 verbose= quiet=1 : vtalk says: There is output 27 | norun=1 verbose= quiet=1 : votalk says: 28 | norun=1 verbose= quiet=1 : nrtalk says: There is output 29 | norun=1 verbose= quiet=1 : nqtalk says: 30 | 31 | norun=1 verbose=1 quiet= : vtalk says: There is output 32 | norun=1 verbose=1 quiet= : votalk says: 33 | norun=1 verbose=1 quiet= : nrtalk says: There is output 34 | norun=1 verbose=1 quiet= : nqtalk says: There is output 35 | 36 | norun=1 verbose=1 quiet=1 : vtalk says: There is output 37 | norun=1 verbose=1 quiet=1 : votalk says: 38 | norun=1 verbose=1 quiet=1 : nrtalk says: There is output 39 | norun=1 verbose=1 quiet=1 : nqtalk says: 40 | 41 | -------------------------------------------------------------------------------- /test/talk_functions.out.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/talk_functions.out.ref -------------------------------------------------------------------------------- /test/talk_w_2_args.err.ref: -------------------------------------------------------------------------------- 1 | one two three 2 | -------------------------------------------------------------------------------- /test/talk_w_2_args.out.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/talk_w_2_args.out.ref -------------------------------------------------------------------------------- /test/talk_w_2_args2.err.ref: -------------------------------------------------------------------------------- 1 | one two three words 2 | -------------------------------------------------------------------------------- /test/talk_w_2_args2.out.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/talk_w_2_args2.out.ref -------------------------------------------------------------------------------- /test/talkf_basic_test.err.ref: -------------------------------------------------------------------------------- 1 | The 0 word is 'the' 2 | The 1 word is 'time' 3 | The 2 word is 'has' 4 | The 3 word is 'come' 5 | The 4 word is 'to' 6 | The 5 word is 'talk' 7 | The 6 word is 'of' 8 | The 7 word is 'ceiling' 9 | The 8 word is 'wax' 10 | The 9 word is 'and' 11 | The 10 word is 'cabbages' 12 | The 11 word is 'and' 13 | The 12 word is 'kings' 14 | -------------------------------------------------------------------------------- /test/talkf_basic_test.out.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/talkf_basic_test.out.ref -------------------------------------------------------------------------------- /test/vtalkf_basic_test.err.ref: -------------------------------------------------------------------------------- 1 | Verbose = 1 2 | -------------------------------------------------------------------------------- /test/vtalkf_basic_test.out.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aks/bash-lib/abb4fa2ae5d404a632fa29f95dd12ddbd598ae7c/test/vtalkf_basic_test.out.ref -------------------------------------------------------------------------------- /text-utils.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # text-utils.sh -- utilities for processing text 3 | # 4 | # Copyright 2006-2022 Alan K. Stebbens 5 | 6 | TEXT_UTILS_VERSION="text-utils.sh v1.8" 7 | [[ "$TEXT_UTILS_SH" = "$TEXT_UTILS_VERSION" ]] && return 8 | TEXT_UTILS_SH="$TEXT_UTILS_VERSION" 9 | 10 | source help-util.sh 11 | source arg-utils.sh 12 | 13 | text_help() { 14 | help_pager <<'EOF' 15 | str_len STRING # string length (multi-byte unaware) 16 | 17 | mstr_len STRING # string length (multi-byte aware) 18 | 19 | byte_len STRING # byte length (multi-byte unaware) 20 | 21 | char_len STRING # character length (possibly multi-byte) 22 | 23 | lowercase STRING # return the lowercase string 24 | 25 | uppercase STRING # return the uppercase string 26 | 27 | trim STRING # trim blanks surrounding string 28 | 29 | ltrim STRING # trim left-side blanks from STRING 30 | 31 | rtrim STRING # trim right-side blanks from STRING 32 | 33 | squeeze STRING # squeeze multiple blanks in string 34 | 35 | split_str STRING [SEP] # split STRING using SEP [default: ' \t'] 36 | 37 | split_input [SEP] # split STDIN using SEP [default: ' \t'] 38 | 39 | args2lines [ARG ..] # echo each ARG (or STDIN) on a separate line 40 | 41 | sort_str2lines "STRING .." # output the sorted words in STRING on separate lines 42 | 43 | join_lines # join lines on STDIN together with newlines 44 | 45 | sort_str [WORDS TO BE SORTED] # return the sorted list of WORDS 46 | str_sort [WORDS TO BE SORTED] # an alias for 'sort_str' 47 | 48 | html_encode [STRING] # encode STRING (or STDIN) for html 49 | 50 | url_encode [STRING] # encode STRING (or STDIN) for url 51 | 52 | html_decode [STRING] # decode STRING (or STDIN) from HTML encoding 53 | 54 | url_decode [STRING] # decode STRING (or STDIN) from URL encoding 55 | 56 | Most functions, except 'split_str' and 'sort_str', can be used in a pipe 57 | without an argument. For example: 58 | 59 | echo "This is a string" | uppercase => "THIS IS A STRING" 60 | echo "" 61 | html_encode html-file 62 | EOF 63 | } 64 | 65 | help_text() { text_help ; } 66 | 67 | str_len() { __args_or_stdin "$@" | wc -c ; } 68 | mstr_len() { __args_or_stdin "$@" | wc -m ; } 69 | 70 | byte_len() { __args_or_stdin "$@" | wc -c ; } 71 | char_len() { __args_or_stdin "$@" | wc -m ; } 72 | 73 | 74 | lowercase() { __args_or_stdin "$@" | tr 'A-Z' 'a-z' ; } 75 | uppercase() { __args_or_stdin "$@" | tr 'a-z' 'A-Z' ; } 76 | 77 | ltrim() { __args_or_stdin "$@" | sed -Ee 's/^[[:space:]]*//' ; } 78 | rtrim() { __args_or_stdin "$@" | sed -Ee 's/[[:space:]]*$//' ; } 79 | trim() { __args_or_stdin "$@" | sed -Ee 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' ; } 80 | squeeze() { __args_or_stdin "$@" | tr '\t' ' ' | tr -s ' ' ; } 81 | 82 | # split_str "STRING" [SEP] 83 | # 84 | # outputs the split of STRING into parts using a separator SEP (defaulting 85 | # to space/tab). 86 | # 87 | # If SEP is anything but " ", care is taken to avoid removing whitespace from 88 | # the split values. 89 | # 90 | # SEP can be multiple characters; for example ' ,' will split using both space 91 | # and comma. By default, splitting is done by tabs. 92 | 93 | split_str() { 94 | echo "$1" | split_input "$2" 95 | } 96 | __split_str() { split_str "$@" ; } 97 | 98 | # split_input [SEP] 99 | # 100 | # Splits STDIN by SEP, or by whitespace if SEP not given 101 | # 102 | split_input() { 103 | local sep="${1:-$' \t'}" 104 | tr "$sep" '\n' | 105 | sed -Ee 's/^(.*[ ,?*].*)$/"\1"/' | 106 | tr '\n' ' ' 107 | } 108 | 109 | # args2lines ARG .. 110 | # 111 | # echo each ARG on a separate line 112 | 113 | args2lines() { __args2lines "$@" ; } 114 | 115 | __args2lines() { args_or_stdin "$@" | tr ' \t' '\n' ; } 116 | 117 | # sort_str2lines "STRING ..." -- output the words of STRING on separate lines, sorted 118 | 119 | sort_str2lines() { 120 | help_args_func text_help $# 1 || return 121 | __sort_str2lines "$@" 122 | } 123 | 124 | __sort_str2lines() { __args2lines "$@" | sort -f ; } 125 | 126 | # join_lines -- join lines on STDIN together with newlines 127 | 128 | join_lines() { __join_lines "$@" ; } 129 | 130 | __join_lines() { tr '\n' ' ' | sed -e 's/ $//' ; } 131 | 132 | # sort_str [WORDS TO BE SORTED] 133 | # str_sort 134 | 135 | sort_str() { 136 | help_args_func text_help $# 1 || return 137 | __sort_str "$@" 138 | } 139 | 140 | str_sort() { sort_str "$@" ; } 141 | 142 | __str_sort() { __sort_str "$@" ; } 143 | 144 | __sort_str() { __sort_str2lines "$@" | __join_lines ; } 145 | 146 | # url_encode [STRING] -- encode STRING for URLs 147 | # url_encode -- encode STDIN for URLs 148 | 149 | # Note: must convert % first, otherwise the other entities will 150 | # get double-converted. 151 | 152 | url_encode() { 153 | __args_or_stdin "$@" | 154 | sed -Ee "\ 155 | s/\%/%25/g ; 156 | s/ /%20/g ; s/\\\$/%24/g ; s/\>/%3E/g ;\ 157 | s/#/%23/g ; ; s/\[/%5B/g ;\ 158 | s/'/%27/g ; s/\&/%26/g ; s/\]/%5D/g ;\ 159 | s/,/%2C/g ; s/\(/%28/g ; s/\^/%5E/g ;\ 160 | s/-/%2D/g ; s/\)/%29/g ; s/\`/%60/g ;\ 161 | s/=/%3D/g ; s/\*/%2A/g ; s/\{/%7B/g ;\ 162 | s/[\]/%5C/g ; s/\+/%2B/g ; s/\|/%7C/g ;\ 163 | s/\!/%21/g ; s/\//%2F/g ; s/\}/%7D/g ;\ 164 | s/\"/%22/g ; s/\/g ; s/%7E/~/g" 182 | } 183 | 184 | # html_encode STRING -- encode STRING for HTML presentation 185 | # html_encode -- encode STDIN for HTML presentation 186 | # 187 | # converts '&' => & '>' => > '<' => < 188 | 189 | html_encode() { 190 | __args_or_stdin "$@" | 191 | sed -e "s/\&/\&/g ; s/[<]/\</g ; s/[>]/\>/g" 192 | } 193 | 194 | # html_decode STRING -- decode STRING from HTML presentation 195 | # html_decode -- decode STDIN from HTML presentation 196 | 197 | html_decode() { 198 | __args_or_stdin "$@" | 199 | sed -Ee "s/\<//g ; s/\&/\&/g" 200 | } 201 | 202 | 203 | # end text-utils.sh 204 | # vim: set sw=2 ai 205 | -------------------------------------------------------------------------------- /time-utils.sh: -------------------------------------------------------------------------------- 1 | # bash 2 | # time-utils.sh -- time management utility for bash 3 | # 4 | # Copyright 2014-2022 Alan K. Stebbens 5 | 6 | TIME_UTILS_VERSION="time-utils.sh v1.8" 7 | [[ "$TIME_UTILS_SH" = "$TIME_UTILS_VERSION" ]] && return 8 | TIME_UTILS_SH="$TIME_UTILS_VERSION" 9 | 10 | export TIME_FORMAT="%T" 11 | 12 | source help-util.sh 13 | source talk-utils.sh 14 | 15 | help_time() { 16 | help_pager 1>&2 <<'EOF' 17 | The `time-utils` library enables easy management of timestamps, with hour, 18 | minute, seconds, and timezone components. A variety of time formats are 19 | supported both on input parsing, and on output formatting. 20 | 21 | time_parse [TIMESTRING] 22 | time_arg [TIMESTRING] 23 | 24 | Parse TIMESTRING in one of several recognized formats: `HH:MM:SS`, 25 | `HH:MM:SS.ssss`, If the TIMESTRING is successfully parsed, the variables 26 | `hours`, `mins`, and `secs` are set the corresponding numbers. `time_arg` is 27 | another name for the same function. 28 | 29 | If TIMESTRING is empty, a line of input from STDIN is read and used instead. 30 | 31 | time2secs [TIMESTRING] 32 | 33 | Parse TIMESTRING (or STDIN) and convert to seconds. 34 | 35 | time_format [FORMAT] HOURS MINS SECS 36 | time_format [FORMAT] TIMESTRING 37 | 38 | The `time_format` function accepts an optional format string, followed by three 39 | numeric arguments, for the hour, minutes, and seconds, or a single string 40 | argument in any of the parsable date formats, and reformats into the default 41 | time format, given by TIME_FORMAT. If TIME_FORMAT is not defined, the format 42 | `%T` is used. (See `man strftime` for details). 43 | 44 | time_add TIME1 TIME2 45 | 46 | Add TIME1 to TIME2 and produce a `time_format` result. 47 | 48 | time_sub TIME1 TIME2 49 | 50 | Subtract TIME2 from TIME1 and produce a `time_format` result. 51 | 52 | EOF 53 | 54 | } 55 | time_help() { help_time ; } 56 | 57 | ## FIXME -- maybe don't need this 58 | ## source real-utils.sh 59 | 60 | # functions for time management 61 | # 62 | # time_parse HH:MM:SS 63 | # HH:MM:SS.SSS 64 | # HH MM SS 65 | # HH MM SS SSS 66 | 67 | time_parse() { 68 | case ${#@} in 69 | 0|1) time_parse_str "$1" ;; 70 | 2|3) time_parse_hms "$@" ;; 71 | 4) time_parse_hmss "$@" ;; 72 | esac 73 | } 74 | 75 | # time_parse_str TIMESTRING 76 | # 77 | # parse the TIMESTRING. It can be in one of several formats: 78 | # HH:MM:SS 79 | # HH:MM:SS.sss 80 | # HH:MM ( could also be HH:SS, but his is less common ) 81 | # 82 | # Sets the variables: hours, mins, secs, unless there was an error. 83 | 84 | time_parse_str() { 85 | local time="${1:-`date +%T`}" 86 | hours= mins= secs= msecs= 87 | 88 | # HH:MM:SS.sss 89 | if [[ "$time" =~ ([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})\.([0-9]{1,3}) ]]; then # hh:mm:ss 90 | time_parse_hmss ${BASH_REMATCH[1]} ${BASH_REMATCH[2]} ${BASH_REMATCH[3]} ${BASH_REMATCH[4]} 91 | elif [[ "$time" =~ ([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}) ]]; then # hh:mm:ss 92 | time_parse_hms ${BASH_REMATCH[1]} ${BASH_REMATCH[2]} ${BASH_REMATCH[3]} 93 | elif [[ "$time" =~ ([0-9]{1,2}):([0-9]{1,2}) ]]; then # hh:mm 94 | time_parse_hms ${BASH_REMATCH[1]} ${BASH_REMATCH[2]} 95 | else # failure -- leave no variables set 96 | error "Cannot parse: '$1'; unknown time format" 97 | fi 98 | } 99 | 100 | # time_parse_hms HH MM SS 101 | # time_parse_hmss HH MM SS SSS 102 | # Set three global variables 'hours', 'mins', 'secs', and maybe 'msecs' 103 | 104 | # checks for appropriate time values. 105 | 106 | check_parsed_time_values() { 107 | (( hours >= 0 && hours < 24 )) || error "Bad hours value: $hours" 108 | (( mins >= 0 && mins < 60 )) || error "Bad minutes value: $mins" 109 | (( secs >= 0 && secs < 60 )) || error "Bad seconds value: $secs" 110 | (( msecs >= 0 && msecs < 1000 )) || error "Bad milleseconds value: $msecs" 111 | } 112 | 113 | time_parse_hms() { 114 | hours=$(( 10#$1 )) mins=$(( 10#${2:-0} )) secs=$(( 10#${3:-0} )) msecs=0 115 | check_parsed_time_values 116 | } 117 | 118 | time_parse_hmss() { 119 | hours=$(( 10#$1 )) mins=$(( 10#${2:-0} )) secs=$(( 10#${3:-0} )) msecs=$(( 10#${4:-0} )) 120 | check_parsed_time_values 121 | } 122 | 123 | # time2secs TIMESTRING 124 | # time2secs HH MM SS 125 | 126 | time2secs() { 127 | local hours= mins= secs= 128 | time_parse $1 129 | echo $(( $secs + ( 60 * ( $mins + ( 60 * $hours ) ) ) )) 130 | } 131 | 132 | # time_add TIME1 TIME2 133 | 134 | time_add() { 135 | local t1=`time2secs $1` 136 | local t2=`time2secs $2` 137 | local t3=$(( t1 + t2 )) 138 | time_format $t3 139 | } 140 | 141 | # time_sub TIME1 TIME2 142 | 143 | time_sub() { 144 | local t1=`time2secs $1` 145 | local t2=`time2secs $2` 146 | if (( t1 > t2 )) ; then 147 | time_format $(( t1 - t2 )) 148 | else 149 | time_format $(( t2 - t1 )) 150 | fi 151 | } 152 | 153 | # time_format SECONDS 154 | # 155 | # If seconds exceeds 24 hours in value, a days value will be included: 156 | # DD:HH:MM:SS. Otherwise, the result is HH:MM:SS 157 | 158 | time_format() { 159 | local format secs_in 160 | if (( $# > 1 )); then 161 | format="$1" secs_in="$2" 162 | else 163 | format="%T" secs_in="$1" 164 | fi 165 | local days hours mins secs 166 | secs2hms $secs_in 167 | if (( days > 0 )); then 168 | printf "%d:%02d:%02d:%02d\n" "$days" "$hours" "$mins" "$secs" 169 | else 170 | printf "%02d:%02d:%02d\n" "$hours" "$mins" "$secs" 171 | fi 172 | } 173 | 174 | # convert seconds to HMS values 175 | # sets days, hours, mins secs 176 | 177 | secs2hms() { 178 | days= hours= mins= secs="$1" 179 | if (( secs >= 60 )); then 180 | mins=$(( secs / 60 )) 181 | secs=$(( secs % 60 )) 182 | fi 183 | if (( mins >= 60 )); then 184 | hours=$(( mins / 60 )) 185 | mins=$(( mins % 60 )) 186 | fi 187 | if (( hours >= 24 )); then 188 | days=$(( hours / 24 )) 189 | hours=$(( hours % 24 )) 190 | fi 191 | } 192 | 193 | 194 | # end of time-utils.sh 195 | # vim: set ai sw=2 196 | -------------------------------------------------------------------------------- /words: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cat <