├── .ecrc ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── config.yml └── workflows │ ├── ci.yaml │ ├── local.yaml │ └── release.yaml ├── .gitignore ├── .gitmodules ├── .ignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── NOTICE ├── README.md ├── README.zh.md ├── VERSION ├── bin └── lobash ├── build ├── config.example ├── dist └── .gitkeep ├── docs ├── CONTRIBUTING.md ├── CONTRIBUTING.zh.md ├── build.md ├── differences-between-bash-and-zsh.md ├── faq.md ├── feature_request.md ├── how-to-write-functions.md ├── how-to-write-modules.md ├── imgs │ └── lobash.svg ├── install-bash.md ├── internal-modules.md ├── lint-rules.md ├── lobash-command.md ├── module-metadata.md ├── module-usages │ ├── README.md │ ├── arithmetic.md │ ├── array.md │ ├── color.md │ ├── condition.md │ ├── console.md │ ├── debug.md │ ├── file.md │ ├── path.md │ ├── prompt.md │ ├── string.md │ ├── system.md │ ├── terminal.md │ ├── time.md │ ├── util.md │ └── variable.md ├── pipeline-module.md ├── publish.md ├── test.md └── with-lower-version-bash.md ├── example ├── demo └── modules │ ├── ask │ ├── ask_with_cancel │ ├── choose │ ├── count_lines │ ├── cur_function_name │ ├── cursor_pos │ ├── init.bash │ ├── parse_args │ ├── parse_params │ ├── sort │ ├── trace │ ├── trace_stack │ ├── trap_error │ └── trim ├── makefile-utils ├── changelog.mk ├── help.mk ├── markdown.mk ├── md5.mk └── semver.mk ├── src ├── internals │ ├── basic_internals.bash │ ├── basic_meta_types.bash │ ├── categories │ │ ├── arithmetic │ │ ├── array │ │ ├── color │ │ ├── condition │ │ ├── console │ │ ├── debug │ │ ├── file │ │ ├── path │ │ ├── prompt │ │ ├── string │ │ ├── system │ │ ├── terminal │ │ ├── time │ │ ├── util │ │ └── variable │ ├── check_support.bash │ ├── consts.bash │ ├── debug.bash │ ├── detect_os.bash │ ├── dirname.bash │ ├── erase_line.bash │ ├── error.bash │ ├── helpers.bash │ ├── import.bash │ ├── is_bash.bash │ ├── is_gnu_sed.bash │ ├── is_tty_available.bash │ ├── module_meta.bash │ ├── rm.bash │ ├── set.bash │ ├── streamable.bash │ ├── warn.bash │ └── with_ifs.bash ├── load_internals.bash └── modules │ ├── array_has_key.bash │ ├── array_include.bash │ ├── array_include.s.bash │ ├── array_reverse.bash │ ├── array_size.bash │ ├── ask.bash │ ├── ask_input.bash │ ├── ask_with_cancel.bash │ ├── basename.bash │ ├── basename.p.bash │ ├── benchmark.bash │ ├── choose.bash │ ├── compose.bash │ ├── count_file_lines.bash │ ├── count_files.bash │ ├── count_lines.p.bash │ ├── cur_function_name.bash │ ├── cursor_col.bash │ ├── cursor_pos.bash │ ├── cursor_row.bash │ ├── date.bash │ ├── detect_os.bash │ ├── dirname.bash │ ├── dirname.p.bash │ ├── each.bash │ ├── each.p.bash │ ├── echo.bash │ ├── echo.p.bash │ ├── echo_array.bash │ ├── echo_screen.bash │ ├── end_with.bash │ ├── end_with.s.bash │ ├── extname.bash │ ├── extname.p.bash │ ├── extract.bash │ ├── first.bash │ ├── has.bash │ ├── has.s.bash │ ├── has_not.bash │ ├── has_not.s.bash │ ├── head.bash │ ├── hex_to_rgb.bash │ ├── hostname.bash │ ├── if.bash │ ├── inc.bash │ ├── is_array.bash │ ├── is_array.s.bash │ ├── is_associative_array.bash │ ├── is_associative_array.s.bash │ ├── is_bash.bash │ ├── is_bash.s.bash │ ├── is_dir.bash │ ├── is_dir.s.bash │ ├── is_empty_dir.bash │ ├── is_executable.bash │ ├── is_executable.s.bash │ ├── is_executable_file.bash │ ├── is_executable_file.s.bash │ ├── is_exported.bash │ ├── is_falsy.bash │ ├── is_falsy.s.bash │ ├── is_file.bash │ ├── is_file.s.bash │ ├── is_float.bash │ ├── is_float.s.bash │ ├── is_function.bash │ ├── is_function.s.bash │ ├── is_gnu_sed.bash │ ├── is_integer.bash │ ├── is_integer.s.bash │ ├── is_link.bash │ ├── is_link.s.bash │ ├── is_number.bash │ ├── is_number.s.bash │ ├── is_readable.bash │ ├── is_readable.s.bash │ ├── is_truthy.bash │ ├── is_truthy.s.bash │ ├── is_tty_available.bash │ ├── is_tty_available.s.bash │ ├── is_ubuntu.bash │ ├── is_undefined.bash │ ├── is_writable.bash │ ├── is_writable.s.bash │ ├── join.bash │ ├── keys.bash │ ├── last.bash │ ├── lower_case.bash │ ├── lower_case.p.bash │ ├── lower_first.bash │ ├── lower_first.p.bash │ ├── match.bash │ ├── match_list.bash │ ├── normalize.bash │ ├── normalize.p.bash │ ├── not.s.bash │ ├── not.s.p.bash │ ├── now.bash │ ├── now_s.bash │ ├── parse_args.bash │ ├── parse_params.bash │ ├── pwd.bash │ ├── random.bash │ ├── read_array.bash │ ├── relative.bash │ ├── repeat.bash │ ├── rgb_to_hex.bash │ ├── sedi.bash │ ├── seq.bash │ ├── sleep.bash │ ├── sort.bash │ ├── split.bash │ ├── start_with.bash │ ├── start_with.s.bash │ ├── str_include.bash │ ├── str_include.s.bash │ ├── str_len.bash │ ├── str_replace.bash │ ├── str_replace_all.bash │ ├── str_replace_last.bash │ ├── str_size.bash │ ├── sub.bash │ ├── term_size.bash │ ├── trace_count.bash │ ├── trace_end.bash │ ├── trace_stack.bash │ ├── trace_start.bash │ ├── trace_time.bash │ ├── trap_error.bash │ ├── trim.bash │ ├── trim.p.bash │ ├── trim_color.bash │ ├── trim_color.p.bash │ ├── trim_end.bash │ ├── trim_end.p.bash │ ├── trim_start.bash │ ├── trim_start.p.bash │ ├── union_array.bash │ ├── upper_case.bash │ ├── upper_case.p.bash │ ├── upper_first.bash │ ├── upper_first.p.bash │ ├── var_attrs.bash │ ├── with_ifs.bash │ └── xdg_config_home.bash ├── test ├── tests ├── fixture │ ├── asserts.bash │ └── setup.bash ├── internals │ ├── import.bats │ └── module_meta.bats └── modules │ ├── array_has_key.bats │ ├── array_include.bats │ ├── array_include.s.bats │ ├── array_reverse.bats │ ├── array_size.bats │ ├── ask.bats │ ├── ask_input.bats │ ├── ask_with_cancel.bats │ ├── basename.bats │ ├── basename.p.bats │ ├── benchmark.bats │ ├── choose.bats │ ├── compose.bats │ ├── count_file_lines.bats │ ├── count_files.bats │ ├── count_lines.bats.bak │ ├── count_lines.p.bats │ ├── cur_function_name.bats │ ├── cursor_pos.bats │ ├── date.bats │ ├── detect_os.bats │ ├── dirname.bats │ ├── dirname.p.bats │ ├── each.bats │ ├── each.p.bats │ ├── echo.bats │ ├── echo.p.bats │ ├── echo_array.bats │ ├── echo_screen.bats │ ├── end_with.bats │ ├── end_with.s.bats │ ├── extname.bats │ ├── extname.p.bats │ ├── extract.bats │ ├── first.bats │ ├── has.bats │ ├── has.s.bats │ ├── has_not.bats │ ├── has_not.s.bats │ ├── head.bats │ ├── hex_to_rgb.bats │ ├── if.bats │ ├── inc.bats │ ├── is_array.bats │ ├── is_array.s.bats │ ├── is_bash.bats │ ├── is_bash.s.bats │ ├── is_dir.bats │ ├── is_dir.s.bats │ ├── is_empty_dir.bats │ ├── is_executable.bats │ ├── is_executable.s.bats │ ├── is_executable_file.bats │ ├── is_executable_file.s.bats │ ├── is_exported.bats │ ├── is_falsy.bats │ ├── is_falsy.s.bats │ ├── is_file.bats │ ├── is_file.s.bats │ ├── is_float.bats │ ├── is_float.s.bats │ ├── is_function.bats │ ├── is_function.s.bats │ ├── is_integer.bats │ ├── is_integer.s.bats │ ├── is_number.bats │ ├── is_number.s.bats │ ├── is_readable.bats │ ├── is_readable.s.bats │ ├── is_truthy.bats │ ├── is_truthy.s.bats │ ├── is_ubuntu.bats │ ├── is_undefined.bats │ ├── is_writable.bats │ ├── is_writable.s.bats │ ├── join.bats │ ├── keys.bats │ ├── last.bats │ ├── lower_case.bats │ ├── lower_case.p.bats │ ├── lower_first.bats │ ├── lower_first.p.bats │ ├── match.bats │ ├── match_list.bats │ ├── normalize.bats │ ├── normalize.p.bats │ ├── not.s.bats │ ├── not.s.p.bats │ ├── now.bats │ ├── now_s.bats │ ├── parse_args.bats │ ├── parse_params.bats │ ├── random.bats │ ├── read_array.bats │ ├── relative.bats │ ├── repeat.bats │ ├── rgb_to_hex.bats │ ├── sedi.bats │ ├── seq.bats │ ├── sleep.bats │ ├── sort.bats │ ├── split.bats │ ├── start_with.bats │ ├── start_with.s.bats │ ├── str_include.bats │ ├── str_include.s.bats │ ├── str_len.bats │ ├── str_replace.bats │ ├── str_replace_all.bats │ ├── str_replace_last.bats │ ├── str_size.bats │ ├── sub.bats │ ├── term_size.bats │ ├── trace_count.bats │ ├── trace_stack.bats │ ├── trace_start.bats │ ├── trace_time.bats │ ├── trap_error.bats │ ├── trim.bats │ ├── trim.p.bats │ ├── trim_color.bats │ ├── trim_color.p.bats │ ├── trim_end.bats │ ├── trim_end.p.bats │ ├── trim_start.bats │ ├── trim_start.p.bats │ ├── union_array.bats │ ├── upper_case.bats │ ├── upper_case.p.bats │ ├── upper_first.bats │ ├── upper_first.p.bats │ ├── var_attrs.bats │ ├── with_ifs.bats │ └── xdg_config_home.bats ├── tmp └── .gitkeep └── tools ├── build-build-image ├── build-test-image ├── build-test-images ├── build.dockerfile ├── check_module ├── colors.bash ├── gen ├── gen-categories ├── gen-changelog ├── gen-configs ├── gen-module-docs ├── install-bash ├── module-meta.bash ├── module-metadata ├── push-test-images ├── release ├── replace-since-version ├── run-in-docker ├── test ├── test-dist-in-docker ├── test-image-versions ├── test-in-docker └── test.dockerfile /.ecrc: -------------------------------------------------------------------------------- 1 | { 2 | "Exclude": [ 3 | "LICENSE", "NOTICE" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | end_of_line = lf 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.{diff,md}] 12 | trim_trailing_whitespace = false 13 | 14 | [Makefile] 15 | indent_style = tab 16 | 17 | [.gitmodules] 18 | indent_style = tab 19 | 20 | [{Dockerfile,Dockerfile.*,*.dockerfile}] 21 | indent_style = space 22 | indent_size = 2 23 | 24 | [{/bin/*,/tools/*,**/*.{bats,bash,sh}}] 25 | 26 | ################################################################################ 27 | # Below are shfmt options. # 28 | # See https://github.com/mvdan/sh/blob/master/cmd/shfmt/shfmt.1.scd#examples # 29 | ################################################################################ 30 | # --language-variant 31 | shell_variant = auto 32 | binary_next_line = false 33 | # --case-indent 34 | switch_case_indent = true 35 | space_redirects = false 36 | keep_padding = false 37 | # --func-next-line 38 | function_next_line = false 39 | 40 | [{Makefile,*.mk}] 41 | indent_style = tab 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | 3 | contact_links: 4 | - name: Feature Request 5 | url: https://github.com/adoyle-h/lobash/discussions 6 | about: Open a discussion, describe your demand concisely and clearly. 7 | 8 | - name: Feature Submit 9 | url: https://github.com/adoyle-h/lobash/pulls 10 | about: Open a PR, describe your demand and design concisely and clearly. 11 | 12 | - name: Bug Fix 13 | url: https://github.com/adoyle-h/lobash/pulls 14 | about: Open a PR, concisely and clearly describe what you fixed. 15 | 16 | - name: Question and Suggestion 17 | url: https://github.com/adoyle-h/lobash/discussions 18 | about: Open a discussion, describe your demand concisely and clearly. 19 | 20 | - name: Anything not mentioned above 21 | url: https://github.com/adoyle-h/lobash/discussions 22 | about: Open a discussion, describe your demand concisely and clearly. 23 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | - "v*.*.*-*" 8 | 9 | permissions: 10 | contents: write 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-22.04 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Release 20 | uses: softprops/action-gh-release@v1 21 | with: 22 | body: Please read the [CHANGELOG](https://github.com/${{github.repository}}/blob/master/CHANGELOG.md#${{github.ref_name}}). 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | /tmp/* 3 | /config 4 | !**/.gitkeep 5 | 6 | 7 | # Created by https://www.gitignore.io/api/vim,osx 8 | 9 | ### Vim ### 10 | # swap 11 | [._]*.s[a-v][a-z] 12 | [._]*.sw[a-p] 13 | [._]s[a-v][a-z] 14 | [._]sw[a-p] 15 | # session 16 | Session.vim 17 | # temporary 18 | .netrwhist 19 | *~ 20 | # auto-generated tag files 21 | tags 22 | 23 | 24 | ### OSX ### 25 | *.DS_Store 26 | .AppleDouble 27 | .LSOverride 28 | 29 | # Icon must end with two \r 30 | Icon 31 | # Thumbnails 32 | ._* 33 | # Files that might appear in the root of a volume 34 | .DocumentRevisions-V100 35 | .fseventsd 36 | .Spotlight-V100 37 | .TemporaryItems 38 | .Trashes 39 | .VolumeIcon.icns 40 | .com.apple.timemachine.donotpresent 41 | # Directories potentially created on remote AFP share 42 | .AppleDB 43 | .AppleDesktop 44 | Network Trash Folder 45 | Temporary Items 46 | .apdisk 47 | 48 | # End of https://www.gitignore.io/api/vim,osx 49 | 50 | /makefile-utils/* 51 | !/makefile-utils/*.mk 52 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tests/fixture/bats"] 2 | path = tests/fixture/bats 3 | url = https://github.com/bats-core/bats-core.git 4 | shallow = true 5 | [submodule "tests/fixture/assert"] 6 | path = tests/fixture/assert 7 | ; TODO: Waiting merged: https://github.com/bats-core/bats-assert/pull/52 8 | url = https://github.com/adoyle-h/bats-assert.git 9 | branch = feat/stderr 10 | shallow = true 11 | [submodule "tests/fixture/bats-file"] 12 | path = tests/fixture/bats-file 13 | url = https://github.com/bats-core/bats-file.git 14 | shallow = true 15 | [submodule "tests/fixture/support"] 16 | path = tests/fixture/support 17 | url = https://github.com/bats-core/bats-support.git 18 | shallow = true 19 | -------------------------------------------------------------------------------- /.ignore: -------------------------------------------------------------------------------- 1 | /tests/fixture/assert 2 | /tests/fixture/support 3 | /tests/fixture/bats 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include ./makefile-utils/*.mk 2 | .DEFAULT_GOAL := help 3 | 4 | .PHONY: check-links 5 | check-links: 6 | # https://github.com/tcort/markdown-link-check 7 | ag --md -l --ignore-dir tests | xargs -n1 markdown-link-check 8 | 9 | .PHONY: check-style 10 | check-style: 11 | editorconfig-checker 12 | 13 | .PHONY: test 14 | test: 15 | ./test 16 | 17 | .PHONY: build 18 | build: 19 | ./build -y ./dist/lobash.bash 20 | 21 | .PHONY: gen 22 | gen: 23 | ./tools/gen 24 | 25 | # @target bump-major bump major version (x) 26 | # @target bump-minor bump minor version (y) 27 | # @target bump-patch bump patch version (z) 28 | BUMP_TARGETS := $(addprefix bump-,major minor patch) 29 | .PHONY: $(BUMP_TARGETS) 30 | $(BUMP_TARGETS): 31 | @$(MAKE) $(subst bump-,semver-,$@) > VERSION 32 | 33 | .PHONY: changelog 34 | # @desc Generate and update the CHANGELOG file 35 | changelog: 36 | $(MAKE) CHANGELOG NEXT_VERSION=$(shell cat VERSION) 37 | 38 | # @target release-major release major version (x) 39 | # @target release-minor release minor version (y) 40 | # @target release-patch release patch version (z) 41 | RELEASE_TARGETS := $(addprefix release-,major minor patch) 42 | .PHONY: $(RELEASE_TARGETS) 43 | $(RELEASE_TARGETS): 44 | ./tools/release $(subst release-,,$@) 45 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2019-2024 ADoyle (adoyle.h@gmail.com). Some Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.7.0 2 | -------------------------------------------------------------------------------- /dist/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adoyle-h/lobash/cfdeb7265922ed63934156a1651c715f7574a438/dist/.gitkeep -------------------------------------------------------------------------------- /docs/differences-between-bash-and-zsh.md: -------------------------------------------------------------------------------- 1 | # Differences between Bash and Zsh 2 | 3 | ## export -f is not supported in Zsh 4 | 5 | There is no equivalent for `export -f` in Zsh. 6 | 7 | ## path is interrelated to PATH in Zsh 8 | 9 | ```zsh 10 | path=123 11 | echo $PATH 12 | # => 123 13 | ``` 14 | 15 | Do not use `path` variable in Zsh. 16 | 17 | ## read -a is not supported in Zsh 18 | 19 | The equivalent of `read -a var <<<'string'` is `read -A var <<<'string'` in Zsh. 20 | 21 | ## read -p is not supported in Zsh 22 | 23 | https://superuser.com/a/556006 24 | 25 | ## ${!var} is not supported in Zsh 26 | 27 | The equivalent of `${!var}` is `${(P)var}` in Zsh. 28 | 29 | ## The array index starts from 1 in Zsh, and 0 in Bash 30 | 31 | > Virtually all shell arrays (Bourne, csh, tcsh, fish, rc, es, yash) start at 1. ksh is the only exception that I know (bash just copied ksh). 32 | 33 | https://unix.stackexchange.com/questions/252368/is-there-a-reason-why-the-first-element-of-a-zsh-array-is-indexed-by-1-instead-o 34 | 35 | ## local -n not supported in Zsh 36 | 37 | The equivalent of `local -n var=$1` is `local var=${(e)1}` in Zsh. 38 | -------------------------------------------------------------------------------- /docs/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: feature 6 | assignees: adoyle-h 7 | --- 8 | 9 | 10 | 11 | ## Is your feature request related to a problem? Please describe. 12 | 13 | 14 | ## Describe the solution you'd like 15 | 16 | 17 | ## Additional context 18 | 19 | 20 | ----- 21 | 22 | 23 | 24 | ## 你想要的新特性跟什么问题相关?请描述 25 | 26 | 27 | ## 描述你期望的解决方案 28 | 29 | ## 附加信息 30 | 31 | -------------------------------------------------------------------------------- /docs/install-bash.md: -------------------------------------------------------------------------------- 1 | # Install Bash 2 | 3 | `BASHVER=4.4 ./tools/install-bash` to install bash in "./tmp/bash-$BASHVER" 4 | 5 | ## Install Bash from other mirrors 6 | 7 | ```sh 8 | MIRROR=https://ftp.gnu.org/gnu ./tools/install-bash 9 | ``` 10 | 11 | Mirrors list in https://www.gnu.org/prep/ftp.html 12 | -------------------------------------------------------------------------------- /docs/internal-modules.md: -------------------------------------------------------------------------------- 1 | # Internal Modules 2 | 3 | The internal modules must put in `src/internals/` folder. 4 | 5 | ## Internal Functions 6 | 7 | Internal functions must starts with `_lobash.`. Like that, 8 | 9 | ```sh 10 | _lobash.dirname() { 11 | printf '%s\n' "${1%/*}/" 12 | } 13 | ``` 14 | 15 | ## Use internal modules 16 | 17 | Invoke `source ./src/load_internals.bash` to load basic internal modules first. 18 | And use `_lobash.import_internal` to import internal modules. 19 | 20 | **Attention**: When invoke `source ./src/load_internals.bash` in a function scope not global scope, 21 | some internal functions will be wrong. Because `_LOBASH_MOD_META_CACHE` variable must be in global scope. 22 | 23 | `LOBASH_MOD_META_CACHE` is used by internals/module_meta.bash. 24 | `declare -A LOBASH_MOD_META_CACHE` before `_lobash.import_internals module_meta`. 25 | 26 | `declare -g` can solve this problem, but it is not supported in Bash 4.1 and lower version. 27 | -------------------------------------------------------------------------------- /docs/lint-rules.md: -------------------------------------------------------------------------------- 1 | # Lint Rules 2 | 3 | ## check_no_echo 4 | 5 | Use printf instead of echo. Refer to this [link](https://github.com/anordal/shellharden/blob/master/how_to_do_things_safely_in_bash.md#echo--printf). 6 | -------------------------------------------------------------------------------- /docs/lobash-command.md: -------------------------------------------------------------------------------- 1 | # Lobash Command 2 | 3 | While Lobash is a library for development, it also provides a command `./bin/lobash`. 4 | 5 | Enter `./bin/lobash` show help. 6 | 7 | ```sh 8 | > ./bin/lobash 9 | 10 | Usage: 11 | lobash [help|-h|--help] 12 | lobash mod []... 13 | lobash meta 14 | 15 | Sub-Command: 16 | help Show help 17 | mod Invoke a Lobash module 18 | mods Show available module names 19 | meta Query metadatas of Lobash module 20 | github Open Lobash github page in your browser 21 | 22 | Description: 23 | The "lobash mod" command is only used for certain scenarios. Many modules not work as command. 24 | ``` 25 | 26 | ## lobash meta 27 | 28 | ```sh 29 | > ./bin/lobash meta normalize 30 | Module: normalize 31 | Usage: l.normalize 32 | Description: 33 | - Normalize the given path which can be an unexisted path. 34 | - Trailing `/` always be removed. 35 | Dependent: split, join 36 | Deprecated: false 37 | Since: 0.1.0 38 | Bash: 4.0 39 | Status: tested 40 | ``` 41 | 42 | ## lobash mod 43 | 44 | Note: The "lobash mod" command is only used for certain scenarios. Many modules not work as command. 45 | 46 | ```sh 47 | > ./bin/lobash mod ask 'Is it OK?' 48 | Is it OK? ([Y]es/No) 49 | YES 50 | ``` 51 | -------------------------------------------------------------------------------- /docs/module-usages/arithmetic.md: -------------------------------------------------------------------------------- 1 | # Category: Arithmetic 2 | 3 | [⬅️ Back to previous page](./README.md) 4 | 5 | ## TOC 6 | 7 | - [inc](#inc) 8 | - [sub](#sub) 9 | 10 | ## Modules 11 | 12 | ### inc 13 | 14 | - Usage: `l.inc [=1]` 15 | - Description: Increase number with addend. 16 | - Since: 0.1.0 17 | - Bash: 4.0+ 18 | - Test Cases: [tests/modules/inc.bats](../../tests/modules/inc.bats) 19 | - Source Code: [src/modules/inc.bash](../../src/modules/inc.bash) 20 | 21 | ### sub 22 | 23 | - Usage: `l.sub [=1]` 24 | - Description: Subtract number with subtrahend. 25 | - Since: 0.1.0 26 | - Bash: 4.0+ 27 | - Test Cases: [tests/modules/sub.bats](../../tests/modules/sub.bats) 28 | - Source Code: [src/modules/sub.bash](../../src/modules/sub.bash) 29 | 30 | [⬆️ Back up to TOC](#toc) 31 | -------------------------------------------------------------------------------- /docs/module-usages/color.md: -------------------------------------------------------------------------------- 1 | # Category: Color 2 | 3 | [⬅️ Back to previous page](./README.md) 4 | 5 | ## TOC 6 | 7 | - [hex_to_rgb](#hex_to_rgb) 8 | - [rgb_to_hex](#rgb_to_hex) 9 | 10 | ## Modules 11 | 12 | ### hex_to_rgb 13 | 14 | - Usage: `l.hex_to_rgb ` 15 | - Description: It prints `$r\n$g\n$b\n`. 16 | - Since: 0.1.0 17 | - Bash: 4.0+ 18 | - Test Cases: [tests/modules/hex_to_rgb.bats](../../tests/modules/hex_to_rgb.bats) 19 | - Source Code: [src/modules/hex_to_rgb.bash](../../src/modules/hex_to_rgb.bash) 20 | 21 | ### rgb_to_hex 22 | 23 | - Usage: `l.rgb_to_hex ` 24 | - Description: 25 | - ``, ``, `` must be positive integer (0~255). 26 | - It prints hex string. Like '#ffffff' 27 | - Dependent: [`is_number`](./condition.md#is_number) 28 | - Since: 0.1.0 29 | - Bash: 4.0+ 30 | - Test Cases: [tests/modules/rgb_to_hex.bats](../../tests/modules/rgb_to_hex.bats) 31 | - Source Code: [src/modules/rgb_to_hex.bash](../../src/modules/rgb_to_hex.bash) 32 | 33 | [⬆️ Back up to TOC](#toc) 34 | -------------------------------------------------------------------------------- /docs/pipeline-module.md: -------------------------------------------------------------------------------- 1 | # Pipeline Module 2 | 3 | The pipeline module provides function which can be used in pipeline. 4 | Its name must be named suffixed with `.p`. 5 | 6 | ``` 7 | # --- 8 | # Category: Path 9 | # Since: 0.1.0 10 | # Usage: echo | l.basename.p 11 | # Description: The pipeline version of l.basename 12 | # Dependent: basename 13 | # --- 14 | 15 | l.basename.p() { 16 | local str 17 | IFS='' read -r str 18 | 19 | l.basename "$str" 20 | } 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/publish.md: -------------------------------------------------------------------------------- 1 | # Publish 2 | 3 | 1. `git checkout develop` 4 | 2. `make release-patch` or `make release-minor` or `make release-major` 5 | 3. `git push` and `git push --tags` 6 | -------------------------------------------------------------------------------- /example/demo: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | set -o errtrace 7 | (shopt -p inherit_errexit &>/dev/null) && shopt -s inherit_errexit 8 | 9 | SCRIPT_DIR="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" 10 | readonly SCRIPT_DIR 11 | 12 | # shellcheck source=../dist/lobash.bash 13 | source "$SCRIPT_DIR"/../dist/lobash.bash 14 | 15 | arr1=(a 'b c') 16 | arr2=(d e) 17 | echo "# arr1=(a 'b c')" 18 | echo "# arr2=(d e)" 19 | 20 | arr=( $(l.union_array arr1 arr2) ) 21 | echo "# l.union_array arr1 arr2" "=> ${arr[*]}" 22 | 23 | str=$(l.join arr ',') 24 | echo "# l.join ',' a 'b c' d e" "=> $str" 25 | 26 | declare -a list 27 | l.split "$str" list ',' 28 | echo "# l.split '$str' ','" "=> ${list[*]}" 29 | echo "# list size" "=> ${#list[@]}" 30 | 31 | first_item=$(l.first list) 32 | echo "# l.first list" "=> ${first_item}" 33 | 34 | last_item=$(l.last list) 35 | echo "# l.last list" "=> ${last_item}" 36 | -------------------------------------------------------------------------------- /example/modules/ask: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | set -o errtrace 7 | (shopt -p inherit_errexit &>/dev/null) && shopt -s inherit_errexit 8 | 9 | SCRIPT_DIR="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" 10 | readonly SCRIPT_DIR 11 | 12 | # shellcheck source=./init.bash 13 | source "$SCRIPT_DIR"/init.bash 14 | 15 | result=$(l.ask 'ask with default value Yes' Y) 16 | echo "result=$result" 17 | 18 | if [[ $result == YES ]]; then 19 | echo "# do something if YES" 20 | else 21 | echo "# do something if NO" 22 | fi 23 | 24 | result=$(l.ask 'ask without default value') 25 | echo "result=$result" 26 | -------------------------------------------------------------------------------- /example/modules/ask_with_cancel: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | set -o errtrace 7 | (shopt -p inherit_errexit &>/dev/null) && shopt -s inherit_errexit 8 | 9 | SCRIPT_DIR="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" 10 | readonly SCRIPT_DIR 11 | 12 | # shellcheck source=./init.bash 13 | source "$SCRIPT_DIR"/init.bash 14 | 15 | result=$(l.ask_with_cancel 'ask with default value Yes' Y) 16 | echo "result=$result" 17 | 18 | if [[ $result == CANCEL ]]; then 19 | echo "# do something if CANCEL" 20 | else 21 | if [[ $result == YES ]]; then 22 | echo "# do something if YES" 23 | else 24 | echo "# do something if NO" 25 | fi 26 | fi 27 | result=$(l.ask 'ask without default value') 28 | echo "result=$result" 29 | -------------------------------------------------------------------------------- /example/modules/choose: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | set -o errtrace 7 | (shopt -p inherit_errexit &>/dev/null) && shopt -s inherit_errexit 8 | 9 | SCRIPT_DIR="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" 10 | readonly SCRIPT_DIR 11 | # shellcheck source=./init.bash 12 | source "$SCRIPT_DIR"/init.bash 13 | 14 | result=$(l.choose a b c d e f g h i j k l m n o p q r s t) 15 | echo "result=${result}" 16 | -------------------------------------------------------------------------------- /example/modules/count_lines: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # shellcheck disable=SC2119 3 | 4 | set -o errexit 5 | set -o nounset 6 | set -o pipefail 7 | set -o errtrace 8 | (shopt -p inherit_errexit &>/dev/null) && shopt -s inherit_errexit 9 | 10 | SCRIPT_DIR="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" 11 | readonly SCRIPT_DIR 12 | # shellcheck source=./init.bash 13 | source "$SCRIPT_DIR"/init.bash 14 | 15 | # It is same to wc -l 16 | printf 'a' | l.count_lines.p 17 | printf 'a\n' | l.count_lines.p 18 | printf 'a\nb' | l.count_lines.p 19 | printf 'a\nb\n' | l.count_lines.p 20 | printf 'a\nb\n\n' | l.count_lines.p 21 | -------------------------------------------------------------------------------- /example/modules/cur_function_name: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | set -o errtrace 7 | (shopt -p inherit_errexit &>/dev/null) && shopt -s inherit_errexit 8 | 9 | SCRIPT_DIR="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" 10 | readonly SCRIPT_DIR 11 | # shellcheck source=./init.bash 12 | source "$SCRIPT_DIR"/init.bash 13 | 14 | foo() { 15 | l.cur_function_name 16 | } 17 | 18 | bar() { 19 | foo 20 | } 21 | 22 | foo 23 | bar 24 | -------------------------------------------------------------------------------- /example/modules/cursor_pos: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | set -o errtrace 7 | (shopt -p inherit_errexit &>/dev/null) && shopt -s inherit_errexit 8 | 9 | SCRIPT_DIR="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" 10 | readonly SCRIPT_DIR 11 | 12 | # shellcheck source=./init.bash 13 | source "$SCRIPT_DIR"/init.bash 14 | 15 | tput sc # Save cursor position 16 | tput cup 5 10 # Move to row 6 col 11 17 | pos1=() 18 | l.cursor_pos pos1 19 | tput cup 10 2 # Move to row 11 col 3 20 | 21 | pos2=() 22 | l.read_array pos2 < <(l.cursor_pos) 23 | tput rc # Restore cursor position 24 | 25 | echo "cursor position 1: ${pos1[*]}" # print 6 11 26 | echo "cursor position 2: ${pos2[*]}" # print 11 3 27 | -------------------------------------------------------------------------------- /example/modules/init.bash: -------------------------------------------------------------------------------- 1 | DIST_LOBASH="$SCRIPT_DIR"/../../dist/lobash.bash 2 | if [[ ! -f "$DIST_LOBASH" ]]; then 3 | echo "Not found $DIST_LOBASH. To build it." 4 | "$SCRIPT_DIR/../../build" -y -- "$DIST_LOBASH" 5 | fi 6 | 7 | # shellcheck source=../../dist/lobash.bash 8 | source "$DIST_LOBASH" 9 | unset DIST_LOBASH 10 | -------------------------------------------------------------------------------- /example/modules/parse_params: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | set -o errtrace 7 | (shopt -p inherit_errexit &>/dev/null) && shopt -s inherit_errexit 8 | 9 | SCRIPT_DIR="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" 10 | readonly SCRIPT_DIR 11 | # shellcheck source=./init.bash 12 | source "$SCRIPT_DIR"/init.bash 13 | 14 | declare -A opts=() 15 | declare -a args=() 16 | 17 | foo() { 18 | l.parse_params opts args "$@" 19 | } 20 | 21 | foo -a 3 -b12 -c=5 foo -d= bar -e -3 -4 a -F=abc -gh w -ij=5 -km= -5n -xzy --beep=boop baz --no-wow 22 | 23 | echo "[${#opts[@]} Options]" 24 | for key in "${!opts[@]}"; do 25 | echo "$key: ${opts[$key]}" 26 | done 27 | 28 | echo 29 | echo "[${#args[@]} Arguments]" 30 | declare i=0 31 | for key in "${args[@]}"; do 32 | echo "$i: $key" 33 | (( i+=1 )) 34 | done 35 | 36 | echo "------Use Options------" 37 | 38 | # NOTE: Use ${opts[c]:-} instead of ${opts[c]}. If `set -o nounset`, ${opts[c]} will throw error when option omitted. 39 | if [[ -n ${opts[c]:-} ]]; then 40 | echo "c=${opts[c]}" 41 | fi 42 | 43 | echo "------Use Arguments------" 44 | echo "${args[0]} ${args[1]} ${args[2]}" 45 | -------------------------------------------------------------------------------- /example/modules/sort: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | set -o errtrace 7 | (shopt -p inherit_errexit &>/dev/null) && shopt -s inherit_errexit 8 | 9 | SCRIPT_DIR="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" 10 | readonly SCRIPT_DIR 11 | # shellcheck source=./init.bash 12 | source "$SCRIPT_DIR"/init.bash 13 | 14 | before=(B a C d E b 8 1 2 4) 15 | readarray -t after < <(l.sort before) 16 | echo "Before size: ${#before[@]}" 17 | echo "Before: ${before[*]}" 18 | echo "After size: ${#after[@]}" 19 | echo "After: ${after[*]}" 20 | -------------------------------------------------------------------------------- /example/modules/trace: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # shellcheck disable=SC2119 3 | 4 | set -o errexit 5 | set -o nounset 6 | set -o pipefail 7 | set -o errtrace 8 | (shopt -p inherit_errexit &>/dev/null) && shopt -s inherit_errexit 9 | 10 | SCRIPT_DIR="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" 11 | readonly SCRIPT_DIR 12 | # shellcheck source=./init.bash 13 | source "$SCRIPT_DIR"/init.bash 14 | 15 | l.trace_start 16 | echo ' 123 ' | l.trim 17 | l.trace_end 18 | 19 | l.trace_start 1 lv-1 20 | l.trim ' abc ' 21 | l.trace_end 22 | 23 | l.trace_start 2 lv-2 24 | l.trim ' abc ' 25 | l.trace_end 26 | 27 | l.trace_start 3 lv-3 28 | l.trim ' abc ' 29 | l.trace_end 30 | -------------------------------------------------------------------------------- /example/modules/trace_stack: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | set -o errtrace 7 | (shopt -p inherit_errexit &>/dev/null) && shopt -s inherit_errexit 8 | 9 | SCRIPT_DIR="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" 10 | readonly SCRIPT_DIR 11 | # shellcheck source=./init.bash 12 | source "$SCRIPT_DIR"/init.bash 13 | 14 | foo() { 15 | l.trace_stack foo 16 | } 17 | 18 | # It will print: 19 | # Trace Function Stack: 20 | # # Function (File:Line) 21 | # - main (./example/trace:0) 22 | l.trace_stack 23 | 24 | # It will print: 25 | # Trace Function Stack: foo 26 | # # Function (File:Line) 27 | # - foo (./example/trace:36) 28 | # - main (./example/trace:0) 29 | foo 30 | -------------------------------------------------------------------------------- /example/modules/trap_error: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | set -o errtrace 7 | (shopt -p inherit_errexit &>/dev/null) && shopt -s inherit_errexit 8 | 9 | SCRIPT_DIR="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" 10 | readonly SCRIPT_DIR 11 | # shellcheck source=./init.bash 12 | source "$SCRIPT_DIR"/init.bash 13 | 14 | l.trap_error 15 | 16 | foo() { 17 | return 12 18 | } 19 | 20 | bar() { 21 | foo 22 | } 23 | 24 | wat() { 25 | bar 26 | } 27 | 28 | wat 29 | -------------------------------------------------------------------------------- /example/modules/trim: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | set -o errtrace 7 | (shopt -p inherit_errexit &>/dev/null) && shopt -s inherit_errexit 8 | 9 | SCRIPT_DIR="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" 10 | readonly SCRIPT_DIR 11 | # shellcheck source=./init.bash 12 | source "$SCRIPT_DIR"/init.bash 13 | 14 | a=$(echo ' 123 ' | l.trim.p) 15 | b=$(l.trim ' abcd ') 16 | 17 | echo "a=$a and a.length=${#a}" 18 | echo "b=$b and b.length=${#b}" 19 | -------------------------------------------------------------------------------- /makefile-utils/markdown.mk: -------------------------------------------------------------------------------- 1 | .PHONY: md-check-links 2 | # @desc Check dead links in markdown files 3 | md-check-links: 4 | ifeq (, $(shell which lychee)) 5 | $(error "Not found lychee in PATH. Please install it by yourself. See https://github.com/lycheeverse/lychee") 6 | endif 7 | 8 | ifdef GITHUB_TOKEN 9 | lychee --github-token ${GITHUB_TOKEN} . 10 | else 11 | @lychee . 12 | endif 13 | -------------------------------------------------------------------------------- /makefile-utils/md5.mk: -------------------------------------------------------------------------------- 1 | # Source Code: https://github.com/adoyle-h/makefile-utils 2 | 3 | .PHONY: md5-check 4 | # @desc Check md5sum in $DIST/*.md5 [$DIST defaults to ./dist] 5 | md5-check: 6 | $(eval DIST ?= ./dist) 7 | @cd ${DIST} && md5sum -c ./*.md5 8 | 9 | .PHONY: md5 10 | # @desc Create .md5 files for each file in $DIST/ [$DIST defaults to ./dist] 11 | md5: 12 | $(eval DIST ?= ./dist) 13 | @rm -f ${DIST}/*.md5 14 | @cd ${DIST} && for file in *; do [ -f "$$file" ] && md5sum -- "$$file" > "$$file.md5"; done 15 | -------------------------------------------------------------------------------- /makefile-utils/semver.mk: -------------------------------------------------------------------------------- 1 | # Source Code: https://github.com/adoyle-h/makefile-utils 2 | 3 | SEMVER_BIN ?= makefile-utils/semver 4 | 5 | # @hide @target makefile-utils/semver Download semver tool 6 | ${SEMVER_BIN}: 7 | @mkdir -p $$(dirname '$@') 8 | @echo '[makefile-utils] To download semver-tool' 9 | @curl -Lo '$@' https://raw.githubusercontent.com/fsaintjacques/semver-tool/master/src/semver 10 | @chmod +x '$@' 11 | 12 | .PHONY: GIT_VERSION 13 | # @desc Print current version (Most recent "v*.*.*" in git tag list) 14 | GIT_VERSION: 15 | @VERSION="$$(git describe --tags --abbrev=0 --match 'v*.*.*' 2>/dev/null)";\ 16 | echo "$${VERSION:-v0.0.0}" 17 | 18 | # @target semver-major Print next major version (x) based on current git tag 19 | # @target semver-minor Print next minor version (y) based on current git tag 20 | # @target semver-patch Print next patch version (z) based on current git tag 21 | .PHONY: $(addprefix semver-,major minor patch) 22 | $(addprefix semver-,major minor patch): ${SEMVER_BIN} 23 | @${SEMVER_BIN} bump $(subst semver-,,$@) "$(shell $(MAKE) -s GIT_VERSION)" 24 | -------------------------------------------------------------------------------- /src/internals/basic_internals.bash: -------------------------------------------------------------------------------- 1 | # shellcheck source=./check_shell.bash 2 | # shellcheck source=./dirname.bash 3 | # shellcheck source=./detect_os.bash 4 | # shellcheck source=./is_bash.bash 5 | # shellcheck source=./consts.bash 6 | # shellcheck source=./debug.bash 7 | # shellcheck source=./warn.bash 8 | # shellcheck source=./error.bash 9 | # shellcheck source=./import.bash 10 | # shellcheck source=./is_tty_available.bash 11 | _LOBASH_BASIC_INTERNALS=( 12 | detect_os 13 | consts 14 | is_bash 15 | check_support 16 | dirname 17 | debug 18 | warn 19 | error 20 | import 21 | with_ifs 22 | is_tty_available 23 | is_gnu_sed 24 | ) 25 | 26 | # for build Lobash 27 | _LOBASH_DIST_INTERNALS=( 28 | # detect_os must be put before consts 29 | detect_os 30 | # consts must be second 31 | consts 32 | is_bash 33 | check_support 34 | dirname 35 | with_ifs 36 | is_tty_available 37 | is_gnu_sed 38 | ) 39 | -------------------------------------------------------------------------------- /src/internals/basic_meta_types.bash: -------------------------------------------------------------------------------- 1 | # NOTE: The fields and order effect printing the module doc 2 | _LOBASH_BASIC_META_TYPES=( 3 | Usage 4 | Description 5 | Dependent 6 | Deprecated 7 | Since 8 | Bash 9 | Status 10 | Notice 11 | ) 12 | -------------------------------------------------------------------------------- /src/internals/categories/arithmetic: -------------------------------------------------------------------------------- 1 | inc 2 | sub 3 | -------------------------------------------------------------------------------- /src/internals/categories/array: -------------------------------------------------------------------------------- 1 | array_has_key 2 | array_include 3 | array_include.s 4 | array_reverse 5 | array_size 6 | each 7 | each.p 8 | first 9 | head 10 | keys 11 | last 12 | read_array 13 | seq 14 | sort 15 | union_array 16 | -------------------------------------------------------------------------------- /src/internals/categories/color: -------------------------------------------------------------------------------- 1 | hex_to_rgb 2 | rgb_to_hex 3 | -------------------------------------------------------------------------------- /src/internals/categories/condition: -------------------------------------------------------------------------------- 1 | end_with 2 | end_with.s 3 | has 4 | has.s 5 | has_not 6 | has_not.s 7 | if 8 | is_array 9 | is_array.s 10 | is_associative_array 11 | is_associative_array.s 12 | is_bash 13 | is_bash.s 14 | is_dir 15 | is_dir.s 16 | is_empty_dir 17 | is_executable 18 | is_executable.s 19 | is_executable_file 20 | is_executable_file.s 21 | is_exported 22 | is_falsy 23 | is_falsy.s 24 | is_file 25 | is_file.s 26 | is_float 27 | is_float.s 28 | is_function 29 | is_function.s 30 | is_gnu_sed 31 | is_integer 32 | is_integer.s 33 | is_link 34 | is_link.s 35 | is_number 36 | is_number.s 37 | is_readable 38 | is_readable.s 39 | is_truthy 40 | is_truthy.s 41 | is_tty_available 42 | is_tty_available.s 43 | is_ubuntu 44 | is_undefined 45 | is_writable 46 | is_writable.s 47 | not.s 48 | not.s.p 49 | start_with 50 | start_with.s 51 | str_include 52 | str_include.s 53 | -------------------------------------------------------------------------------- /src/internals/categories/console: -------------------------------------------------------------------------------- 1 | echo 2 | echo.p 3 | echo_array 4 | echo_screen 5 | -------------------------------------------------------------------------------- /src/internals/categories/debug: -------------------------------------------------------------------------------- 1 | trace_count 2 | trace_end 3 | trace_stack 4 | trace_start 5 | trace_time 6 | -------------------------------------------------------------------------------- /src/internals/categories/file: -------------------------------------------------------------------------------- 1 | count_file_lines 2 | count_files 3 | extname 4 | extname.p 5 | -------------------------------------------------------------------------------- /src/internals/categories/path: -------------------------------------------------------------------------------- 1 | basename 2 | basename.p 3 | dirname 4 | dirname.p 5 | normalize 6 | normalize.p 7 | pwd 8 | relative 9 | -------------------------------------------------------------------------------- /src/internals/categories/prompt: -------------------------------------------------------------------------------- 1 | ask 2 | ask_input 3 | ask_with_cancel 4 | choose 5 | -------------------------------------------------------------------------------- /src/internals/categories/string: -------------------------------------------------------------------------------- 1 | count_lines.p 2 | extract 3 | join 4 | lower_case 5 | lower_case.p 6 | lower_first 7 | lower_first.p 8 | match 9 | match_list 10 | split 11 | str_len 12 | str_replace 13 | str_replace_all 14 | str_replace_last 15 | str_size 16 | trim 17 | trim.p 18 | trim_color 19 | trim_color.p 20 | trim_end 21 | trim_end.p 22 | trim_start 23 | trim_start.p 24 | upper_case 25 | upper_case.p 26 | upper_first 27 | upper_first.p 28 | -------------------------------------------------------------------------------- /src/internals/categories/system: -------------------------------------------------------------------------------- 1 | detect_os 2 | hostname 3 | sleep 4 | -------------------------------------------------------------------------------- /src/internals/categories/terminal: -------------------------------------------------------------------------------- 1 | cursor_col 2 | cursor_pos 3 | cursor_row 4 | term_size 5 | -------------------------------------------------------------------------------- /src/internals/categories/time: -------------------------------------------------------------------------------- 1 | date 2 | now 3 | now_s 4 | -------------------------------------------------------------------------------- /src/internals/categories/util: -------------------------------------------------------------------------------- 1 | benchmark 2 | compose 3 | parse_args 4 | parse_params 5 | repeat 6 | sedi 7 | trap_error 8 | with_ifs 9 | -------------------------------------------------------------------------------- /src/internals/categories/variable: -------------------------------------------------------------------------------- 1 | cur_function_name 2 | random 3 | var_attrs 4 | xdg_config_home 5 | -------------------------------------------------------------------------------- /src/internals/check_support.bash: -------------------------------------------------------------------------------- 1 | _lobash.check_os() { 2 | if [[ ! $_LOBASH_OS =~ ^(Linux|MacOS|BSD)$ ]]; then 3 | echo "Not support current system: $_LOBASH_OS" >&2 4 | return 5 5 | fi 6 | } 7 | 8 | _lobash.check_shell() { 9 | if ! _lobash.is_bash; then 10 | echo 'Lobash only work in Bash.' >&2 11 | return 6 12 | fi 13 | } 14 | 15 | _lobash.check_supported_bash_version() { 16 | local info 17 | read -r -d '.' -a info <<< "$_LOBASH_MIN_BASHVER" 18 | if (( BASH_VERSINFO[0] < info[0] )) \ 19 | || ( (( BASH_VERSINFO[0] == info[0] )) && (( BASH_VERSINFO[1] < info[1] )) ); then 20 | echo "Bash $BASH_VERSION is not supported. Upgrade your Bash to $_LOBASH_MIN_BASHVER or higher version." >&2 21 | return 7 22 | fi 23 | } 24 | 25 | _lobash.check_support() { 26 | _lobash.check_os 27 | _lobash.check_shell 28 | # _lobash.check_supported_bash_version 29 | } 30 | 31 | _lobash.check_support 32 | -------------------------------------------------------------------------------- /src/internals/consts.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Dependent_Internal: detect_os 3 | # --- 4 | 5 | # shellcheck disable=SC2034 6 | 7 | # Prevent multiple executions 8 | [[ -n ${_LOBASH_INTERNAL_FUNC_PREFIX:-} ]] && return 9 | 10 | readonly _LOBASH_INTERNAL_FUNC_PREFIX=_lobash. 11 | readonly _LOBASH_INTERNAL_CONST_PREFIX=_LOBASH_ 12 | readonly _LOBASH_PRIVATE_FUNC_PREFIX=_l. 13 | readonly _LOBASH_PRIVATE_CONST_PREFIX=_L_ 14 | readonly _LOBASH_PUBLIC_FUNC_PREFIX=l. 15 | readonly _LOBASH_PUBLIC_CONST_PREFIX=L_ 16 | 17 | # _LOBASH_PREFIX, _LOBASH_PUBLIC_DEPTH, _LOBASH_MIN_BASHVER will be reassigned when ./build 18 | readonly _LOBASH_PREFIX=l. 19 | _LOBASH_PUBLIC_DEPTH=1 # NOTE: _LOBASH_PUBLIC_DEPTH should not be readonly 20 | readonly _LOBASH_MIN_BASHVER=4.0 21 | 22 | _LOBASH_OS=$(_lobash.detect_os) 23 | readonly _LOBASH_OS 24 | -------------------------------------------------------------------------------- /src/internals/debug.bash: -------------------------------------------------------------------------------- 1 | # Usage: _lobash.debug ... 2 | # Print logs to stdout. 3 | # If LOBASH_DEBUG set, print Lobash debug logs to stdout 4 | # If LOBASH_DEBUG_OUTPUT set, logs pipe to $LOBASH_DEBUG_OUTPUT instead of stdout 5 | # 6 | # It will not print logs to stdout in below scenario: 7 | # 8 | # LOBASH_DEBUG=1 9 | # foo() { debug hello; } 10 | # bar=$(foo) # => Not see any log in shell 11 | # 12 | # LOBASH_DEBUG_OUTPUT can resolve the problem. 13 | # 14 | # LOBASH_DEBUG=1 15 | # LOBASH_DEBUG_OUTPUT=/tmp/log 16 | # foo() { debug2 hello; } 17 | # bar=$(foo) # => Still not see any log in shell, but logs records in file /tmp/log 18 | _lobash.debug() { 19 | [[ -z ${LOBASH_DEBUG:-} ]] && return 20 | 21 | local func=${FUNCNAME[1]} 22 | 23 | if [[ -z ${LOBASH_DEBUG_OUTPUT:-} ]]; then 24 | echo "[DEBUG:LOBASH:$func] $*" 25 | else 26 | echo "[DEBUG:LOBASH:$func] $*" >> "$LOBASH_DEBUG_OUTPUT" 27 | fi 28 | } 29 | -------------------------------------------------------------------------------- /src/internals/detect_os.bash: -------------------------------------------------------------------------------- 1 | _lobash.detect_os() { 2 | local kernel_name 3 | kernel_name="$(uname -s)" 4 | 5 | case "$kernel_name" in 6 | "Darwin") echo MacOS ;; 7 | "SunOS") echo Solaris ;; 8 | "Haiku") echo Haiku ;; 9 | "MINIX") echo MINIX ;; 10 | "AIX") echo AIX ;; 11 | "IRIX"*) echo IRIX ;; 12 | "FreeMiNT") echo FreeMiNT ;; 13 | "Linux" | "GNU"*) echo Linux ;; 14 | *"BSD" | "DragonFly" | "Bitrig") echo BSD ;; 15 | "CYGWIN"* | "MSYS"* | "MINGW"*) echo Windows ;; 16 | *) echo Unknown_OS "$kernel_name" ;; 17 | esac 18 | } 19 | -------------------------------------------------------------------------------- /src/internals/dirname.bash: -------------------------------------------------------------------------------- 1 | _lobash.dirname() { 2 | local str=${1:-} 3 | [[ $str == '/' ]] && echo '/' && return 0 4 | [[ $str =~ ^'../' ]] && echo '.' && return 0 5 | [[ ! $str =~ / ]] && echo '.' && return 0 6 | 7 | printf '%s\n' "${str%/*}" 8 | } 9 | -------------------------------------------------------------------------------- /src/internals/erase_line.bash: -------------------------------------------------------------------------------- 1 | _lobash.erase_line() { 2 | printf '%b' $'\e[1K\r' 3 | } 4 | -------------------------------------------------------------------------------- /src/internals/error.bash: -------------------------------------------------------------------------------- 1 | # Usage: _lobash.error ... 2 | # Print logs to stdrr. 3 | # If LOBASH_WARN_OUTPUT set, logs pipe to $LOBASH_WARN_OUTPUT instead of stdrr 4 | # 5 | # It will not print logs to stdout in below scenario: 6 | # 7 | # foo() { error hello; } 8 | # bar=$(foo) # => Not see any log in shell 9 | # 10 | # LOBASH_WARN_OUTPUT can resolve the problem. 11 | # 12 | # LOBASH_WARN_OUTPUT=/tmp/log 13 | # foo() { warn2 hello; } 14 | # bar=$(foo) # => Still not see any log in shell, but logs records in file /tmp/log 15 | _lobash.error() { 16 | if [[ -z ${LOBASH_WARN_OUTPUT:-} ]]; then 17 | echo "[ERROR:LOBASH] $*" >&2 18 | else 19 | echo "[ERROR:LOBASH] $*" >> "$LOBASH_WARN_OUTPUT" 20 | fi 21 | } 22 | -------------------------------------------------------------------------------- /src/internals/helpers.bash: -------------------------------------------------------------------------------- 1 | _lobash.prefix() { 2 | printf '%s\n' "$_LOBASH_PREFIX" 3 | } 4 | 5 | _lobash.public_funcs() { 6 | compgen -A function "$_LOBASH_PUBLIC_FUNC_PREFIX" 7 | } 8 | 9 | _lobash.private_funcs() { 10 | compgen -A function "$_LOBASH_PRIVATE_FUNC_PREFIX" 11 | } 12 | 13 | _lobash.internal_funcs() { 14 | compgen -A function "$_LOBASH_INTERNAL_FUNC_PREFIX" 15 | } 16 | 17 | _lobash.public_consts() { 18 | compgen -A variable "$_LOBASH_PUBLIC_CONST_PREFIX" 19 | } 20 | 21 | _lobash.private_consts() { 22 | compgen -A variable "$_LOBASH_PRIVATE_CONST_PREFIX" 23 | } 24 | 25 | _lobash.internal_consts() { 26 | compgen -A variable "$_LOBASH_INTERNAL_CONST_PREFIX" 27 | } 28 | -------------------------------------------------------------------------------- /src/internals/is_bash.bash: -------------------------------------------------------------------------------- 1 | _lobash.is_bash() { 2 | [[ -n "${BASH_VERSION:-}" ]] 3 | } 4 | -------------------------------------------------------------------------------- /src/internals/is_gnu_sed.bash: -------------------------------------------------------------------------------- 1 | _lobash.is_gnu_sed() { 2 | local out 3 | out=$(${1:-sed} --version 2>/dev/null) 4 | [[ $out =~ 'GNU sed' ]] 5 | } 6 | -------------------------------------------------------------------------------- /src/internals/is_tty_available.bash: -------------------------------------------------------------------------------- 1 | _lobash.is_tty_available() { 2 | { : >/dev/tty ; } &>/dev/null 3 | } 4 | -------------------------------------------------------------------------------- /src/internals/rm.bash: -------------------------------------------------------------------------------- 1 | _lobash.rm() { 2 | local path=$1 3 | if [[ $path == / ]]; then 4 | echo "Dangerous! Do not rm $path" 5 | return 3 6 | fi 7 | 8 | if [[ $(dirname "$path") == / ]]; then 9 | echo "Dangerous! Do not rm $path" 10 | return 3 11 | fi 12 | 13 | rm -rf -- "$path" 14 | } 15 | -------------------------------------------------------------------------------- /src/internals/set.bash: -------------------------------------------------------------------------------- 1 | _lobash.set() { 2 | local module=$1 3 | local option=$2 4 | read -r "_LOBASH_SETTING_${module}_${option}" <<< "$3" 5 | } 6 | -------------------------------------------------------------------------------- /src/internals/streamable.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Stream 3 | # Since: 0.1.0 4 | # Status: not work 5 | # --- 6 | 7 | # Usage: _lobash.streamable function_name parameter_size "$@" 8 | # Arguments: 9 | # - function_name: Call the function_name if not a stream invoke 10 | # - parameter_size: The parameter size of function_name 11 | # 12 | # - reducer: Defaults to "_$function_name.streamable". Call the reducer if it is a stream invoke 13 | # The reducer function should be ($prev_value, $line, $index) => $new_value 14 | # Return: echo the result return from reducer or function_name. 15 | _lobash.streamable() { 16 | local function_name=$1 17 | local parameter_size=$2 18 | local reducer=_$function_name.streamable 19 | local unstreamable=_$function_name.unstreamable 20 | local index=0 21 | local prev='' 22 | 23 | if [[ $# == $(( "$parameter_size" + 1 )) ]]; then 24 | local line 25 | while read -r line; do 26 | prev=$($reducer "$prev" "$line" "$index") 27 | index=$(( "$index" + 1 )) 28 | done 29 | 30 | printf '%s\n' "$prev" 31 | else 32 | $unstreamable "$@" 33 | fi 34 | } 35 | -------------------------------------------------------------------------------- /src/internals/warn.bash: -------------------------------------------------------------------------------- 1 | # Usage: _lobash.warn ... 2 | # Print logs to stdrr. 3 | # If LOBASH_WARN_OUTPUT set, logs pipe to $LOBASH_WARN_OUTPUT instead of stdrr 4 | # 5 | # It will not print logs to stdout in below scenario: 6 | # 7 | # foo() { warn hello; } 8 | # bar=$(foo) # => Not see any log in shell 9 | # 10 | # LOBASH_WARN_OUTPUT can resolve the problem. 11 | # 12 | # LOBASH_WARN_OUTPUT=/tmp/log 13 | # foo() { warn2 hello; } 14 | # bar=$(foo) # => Still not see any log in shell, but logs records in file /tmp/log 15 | _lobash.warn() { 16 | local func=${FUNCNAME[1]} 17 | 18 | if [[ -z ${LOBASH_WARN_OUTPUT:-} ]]; then 19 | echo "[WARN:LOBASH:$func] $*" >&2 20 | else 21 | echo "[WARN:LOBASH:$func] $*" >> "$LOBASH_WARN_OUTPUT" 22 | fi 23 | } 24 | -------------------------------------------------------------------------------- /src/internals/with_ifs.bash: -------------------------------------------------------------------------------- 1 | # Usage: _lobash.with_IFS 2 | # Description: run `` with `` effects 3 | _lobash.with_IFS() { 4 | local IFS=$1 5 | shift 6 | eval "$*" 7 | } 8 | -------------------------------------------------------------------------------- /src/load_internals.bash: -------------------------------------------------------------------------------- 1 | # This line is important, do not remove. See docs/internal-modules.md 2 | if ! declare -p _LOBASH_MOD_META_CACHE &>/dev/null; then 3 | declare -A _LOBASH_MOD_META_CACHE 4 | fi 5 | 6 | _lobash.import_internal() { 7 | local src_dir 8 | if [[ -n ${IS_LOBASH_TEST:-} ]]; then 9 | src_dir="$LOBASH_ROOT_DIR/src/internals" 10 | else 11 | src_dir="$(dirname "${BASH_SOURCE[0]}")/internals" 12 | fi 13 | # shellcheck disable=1090 14 | source "$src_dir/$1.bash" 15 | } 16 | 17 | _lobash.import_internals() { 18 | local name 19 | for name in "$@"; do 20 | _lobash.import_internal "$name" 21 | done 22 | } 23 | 24 | _lobash.import_basic_internals() { 25 | if [[ -n ${IS_LOBASH_TEST:-} ]]; then 26 | # shellcheck source=./internals/basic_internals.bash 27 | source "$LOBASH_ROOT_DIR/src/internals/basic_internals.bash" 28 | else 29 | # shellcheck source=./internals/basic_internals.bash 30 | source "$(dirname "${BASH_SOURCE[0]}")/internals/basic_internals.bash" 31 | fi 32 | 33 | _lobash.import_internals "${_LOBASH_BASIC_INTERNALS[@]}" 34 | unset -v _LOBASH_BASIC_INTERNALS 35 | } 36 | 37 | _lobash.import_basic_internals 38 | unset -f _lobash.import_basic_internals 39 | -------------------------------------------------------------------------------- /src/modules/array_has_key.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Array 3 | # Since: 0.7.0 4 | # Usage: l.array_has_key 5 | # Description: Check key whether defined in array or associative array. 6 | # Description: Return 0 (true) or 1 (false). This function should never throw exception error. 7 | # --- 8 | 9 | l.array_has_key() { 10 | eval "[[ \${$1[$2]+_} == _ ]]" 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/array_include.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Array 3 | # Since: 0.3.1 4 | # Usage: l.array_include 5 | # Description: Return 0 (true) or 1 (false). This function should never throw exception error. 6 | # --- 7 | 8 | l.array_include() { 9 | local _exit_code_ 10 | eval "(( \${#${1}[@]} == 0 )) && _exit_code_=1 || true" 11 | [[ -n ${_exit_code_:-} ]] && return "$_exit_code_" 12 | 13 | local _e_ 14 | 15 | eval "for _e_ in \"\${${1}[@]}\"; do [[ \"\$_e_\" == \"$2\" ]] && _exit_code_=0 && return 0; done" 16 | [[ -n ${_exit_code_:-} ]] && return "$_exit_code_" || return 1 17 | } 18 | -------------------------------------------------------------------------------- /src/modules/array_include.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Array 3 | # Since: 0.3.1 4 | # Usage: l.array_include.s 5 | # Description: This function always echo `true` or `false` and exit code always be 0. 6 | # --- 7 | 8 | l.array_include.s() { 9 | local array_name=$1 10 | local exit_code 11 | eval "(( \${#${array_name}[@]} == 0 )) && echo false && exit_code=1 || true" 12 | [[ -n ${exit_code:-} ]] && return 0 13 | 14 | local match="$2" 15 | local e 16 | shift 17 | 18 | eval "for e in \"\${${array_name}[@]}\"; do [[ \"\$e\" == \"\$match\" ]] && echo true && exit_code=0 && return 0; done || true" 19 | 20 | [[ -z ${exit_code:-} ]] && echo "false" || return 0 21 | } 22 | -------------------------------------------------------------------------------- /src/modules/array_reverse.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Array 3 | # Since: 0.6.0 4 | # Usage: l.array_reverse 5 | # Usage: l.read_array < <(l.array_reverse ) 6 | # Description: Reverse array ``. 7 | # Description: If `` set, store the reversed results in ``. 8 | # Description: Otherwise, print the reversed results to stdout. 9 | # Description: You can use `l.read_array out < <(l.array_reverse )` to read array. 10 | # Notice: When in subshell, you must call `l.array_reverse `. 11 | # Notice: The `l.array_reverse ` not works in subshell. 12 | # --- 13 | 14 | l.array_reverse() { 15 | # shellcheck disable=2034 16 | local _size_ _idx_ 17 | _size_=$(eval "echo \${#$1[@]}") 18 | 19 | if (( $# > 1 )); then 20 | eval "for _idx_ in {0..$(( _size_ - 1 ))}; do ${2}[\$(( $(( _size_ - 1 )) - _idx_ ))]=\${${1}[\$_idx_]}; done" 21 | else 22 | eval "for _idx_ in {$(( _size_ - 1 ))..0..-1}; do echo \${${1}[\$_idx_]}; done" 23 | fi 24 | } 25 | -------------------------------------------------------------------------------- /src/modules/array_size.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Array 3 | # Since: 0.7.0 4 | # Usage: l.array_size 5 | # Description: Return the actual size of array and associative array 6 | # Description: For `declare -A array=([test]='')`, the `${#array[@]}` is 0, because bash excludes the null value. 7 | # --- 8 | 9 | l.array_size() { 10 | # shellcheck disable=2034 11 | local _size_=0 _key_ 12 | eval "for _key_ in \"\${!${1}[@]}\" ; do _size_=\$((_size_ + 1)); done" 13 | echo "$_size_" 14 | } 15 | -------------------------------------------------------------------------------- /src/modules/ask_input.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Prompt 3 | # Since: 0.2.0 4 | # Usage: l.ask_input [='Ask Input:'] [] 5 | # Description: Print a message and read user input from stdin. 6 | # Description: If `` provided, return it when user type empty. 7 | # --- 8 | 9 | l.ask_input() { 10 | local answer prompt 11 | local default=${2:-} 12 | if (( $# < 2 )); then 13 | prompt="${1:-Ask Input:} " 14 | else 15 | prompt="${1:-Ask Input:} (Default: $default) " 16 | fi 17 | 18 | read -rp "$prompt" answer 19 | printf '%s\n' "${answer:-$default}" 20 | } 21 | -------------------------------------------------------------------------------- /src/modules/ask_with_cancel.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Prompt 3 | # Since: 0.5.0 4 | # Usage: l.ask_with_cancel [=''] 5 | # Description: Print the message to tty and wait for user typing from stdin. 6 | # Description: It will print 'YES' when user types y/Y/ye/Ye/yE/YE/yes/yES/yeS/YeS/Yes/YEs/YES. 7 | # Description: It will print 'NO' when user types n/N/no/No/nO/NO. 8 | # Description: It will print 'CANCEL' when user types c/C/cancel/CANCEL. 9 | # Description: It prints default value when get empty answer. 10 | # Description: When default=Y, it prints 'YES' by default. 11 | # Description: When default=N, it prints 'NO' by default. 12 | # Description: When default='', there is no default value. It will keep asking until user typed right answer. 13 | # Description: **Attention: "echo invalid_string | l.ask message" will fall into a infinite loop.** 14 | # Description: "echo y | l.ask message" and "echo n | l.ask message" are valid. 15 | # Dependent: ask 16 | # --- 17 | 18 | l.ask_with_cancel() { 19 | local values=(yes no cancel) 20 | _l.ask "$1" "${2:-}" 21 | } 22 | -------------------------------------------------------------------------------- /src/modules/basename.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Path 3 | # Since: 0.1.0 4 | # Usage: l.basename 5 | # Description: Alternative to basename command. It much faster because using shell parameter expansion. 6 | # --- 7 | 8 | l.basename() { 9 | local str=${1:-} 10 | str="${str%/}" 11 | printf '%s\n' "${str##*/}" 12 | } 13 | -------------------------------------------------------------------------------- /src/modules/basename.p.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Path 3 | # Since: 0.1.0 4 | # Usage: echo | l.basename.p 5 | # Description: The pipeline version of l.basename 6 | # Dependent: basename 7 | # --- 8 | 9 | l.basename.p() { 10 | local str 11 | IFS='' read -r str 12 | 13 | l.basename "$str" 14 | } 15 | -------------------------------------------------------------------------------- /src/modules/benchmark.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Util 3 | # Since: 0.1.0 4 | # Usage: l.benchmark [=10] 5 | # Description: Run command in repeats to get benchmarks. 6 | # --- 7 | 8 | _l.run_benchmark() { 9 | local c=$1 10 | local repeats=$2 11 | local i 12 | 13 | # Run the given command [repeats] times 14 | for (( i = 1; i <= "$repeats" ; i++ )); do 15 | $c > /dev/null 2>&1 16 | done; 17 | } 18 | 19 | l.benchmark() { 20 | local c=$1 21 | local repeats=${2:-10} 22 | 23 | echo "Benchmarking: Run command '$c' [$repeats] times."; 24 | echo "=============" 25 | type "$c" 26 | echo "=============" 27 | 28 | time _l.run_benchmark "$c" "$repeats" 29 | 30 | printf '\n--------------------------\n\n' 31 | } 32 | -------------------------------------------------------------------------------- /src/modules/choose.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Prompt 3 | # Since: 0.1.0 4 | # Usage: l.choose ... 5 | # Description: Prompt user to choose one item from options. The function will return the value of chosen item. 6 | # --- 7 | 8 | _l.choose_prompt() { 9 | printf ' %s\n' 'No. Item' 10 | local i 11 | for i in "${!items[@]}"; do 12 | printf -- '- %-2d %s\n' $((i + 1)) "${items[$i]}" 13 | done 14 | 15 | printf 'Please enter the number to choose: \n' 16 | } 17 | 18 | l.choose() { 19 | local items=("$@") 20 | 21 | local num prompt 22 | prompt=$(_l.choose_prompt) 23 | read -r -p "$prompt" num 24 | 25 | if ! [[ ${num} =~ ^[0-9]+$ ]]; then 26 | echo "Must enter an integer. Current: $num">&2 27 | return 3 28 | fi 29 | 30 | if [[ $num -gt ${#items[@]} ]] || [[ $num -lt 1 ]]; then 31 | printf '%s\n' "Invalid choose number: $num" >&2 32 | return 4 33 | fi 34 | 35 | printf '%s\n' "${items[$((num - 1))]}" 36 | } 37 | -------------------------------------------------------------------------------- /src/modules/compose.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Util 3 | # Since: 0.1.0 4 | # Usage: l.compose []... 5 | # Description: Function composition 6 | # --- 7 | 8 | l.compose() { 9 | local -a last=() 10 | local f 11 | 12 | for f in "$@"; do 13 | if [[ $(type -t "$f") == function ]]; then 14 | if (( ${#last[@]} > 0 )); then 15 | last=( "$($f "${last[@]}")" ) 16 | else 17 | last=( "$($f)" ) 18 | fi 19 | else 20 | last=( "$f" ) 21 | fi 22 | done 23 | 24 | printf '%s\n' "${last[@]:-}" 25 | } 26 | -------------------------------------------------------------------------------- /src/modules/count_file_lines.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: File 3 | # Since: 0.1.0 4 | # Usage: l.count_file_lines 5 | # Description: Count lines of file. Similar to `wc -l`. 6 | # --- 7 | 8 | # readarray slow than wc 9 | # l.count_file_lines() { 10 | # # readarray supported since bash 4.0 11 | # readarray -tn 0 lines < "$1" 12 | # printf '%s\n' "${#lines[@]}" 13 | # } 14 | 15 | l.count_file_lines() { 16 | wc -l < "$1" | tr -d ' ' 17 | } 18 | -------------------------------------------------------------------------------- /src/modules/count_files.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: File 3 | # Since: 0.1.0 4 | # Usage: l.count_files 5 | # Description: Count the sum of files under ``. 6 | # --- 7 | 8 | l.count_files() { 9 | local files 10 | # compgen will return 1 when no matched files 11 | readarray -t files < <(compgen -f "$1"/ || [[ $? == 1 ]]) 12 | 13 | printf '%s\n' "${#files[@]}" 14 | } 15 | -------------------------------------------------------------------------------- /src/modules/count_lines.p.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: String 3 | # Since: 0.1.0 4 | # Usage: echo | l.count_lines.p 5 | # Description: Count lints of string like `wc -l`. 6 | # Description: The Bash command substitution always trim blank line. So pass argument in pipeline. 7 | # Description: Refer to https://stackoverflow.com/a/37706905 8 | # --- 9 | 10 | l.count_lines.p() { 11 | local count=0 12 | while read -r -d $'\n' _; do 13 | ((count+=1)) 14 | done 15 | printf '%s\n' "$count" 16 | } 17 | -------------------------------------------------------------------------------- /src/modules/cur_function_name.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Variable 3 | # Since: 0.1.0 4 | # Usage: l.cur_function_name 5 | # Description: Return the name of current function where the l.cur_function_name called in. 6 | # --- 7 | 8 | l.cur_function_name() { 9 | # _LOBASH_PUBLIC_DEPTH=1 in built lobash.bash, and _LOBASH_PUBLIC_DEPTH=2 in test. 10 | printf '%s\n' "${FUNCNAME[$_LOBASH_PUBLIC_DEPTH]}" 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/cursor_col.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Terminal 3 | # Since: 0.1.0 4 | # Usage: l.cursor_col 5 | # Description: Get column number of current cursor position. 6 | # --- 7 | 8 | # Refer to https://unix.stackexchange.com/a/183121 9 | l.cursor_col() { 10 | local COL 11 | IFS=';' read -rsdR -p $'\E[6n' _ COL 12 | echo "${COL}" 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/cursor_pos.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Terminal 3 | # Since: 0.1.0 4 | # Usage: l.cursor_pos [] 5 | # Description: Get current cursor position. 6 | # Description: If `` passed, it will assign row to `${array_name[0]}`, and col to `${array_name[1]}`. 7 | # Description: Otherwise, it prints `$row\n$column\n`. 8 | # --- 9 | 10 | # l.cursor_pos() { 11 | # stty size 12 | # } 13 | 14 | # Refer to https://github.com/dylanaraps/pure-bash-bible#get-the-current-cursor-position 15 | # and https://unix.stackexchange.com/a/183121 16 | l.cursor_pos() { 17 | local _ROW_ _COL_ 18 | IFS='[;' read -p $'\e[6n' -d R -rs _ _ROW_ _COL_ _; 19 | 20 | if (( $# > 0 )); then 21 | IFS=' ' read -ra "$1" <<<"$_ROW_ $_COL_" 22 | else 23 | printf '%s\n' "$_ROW_" "$_COL_" 24 | fi 25 | } 26 | -------------------------------------------------------------------------------- /src/modules/cursor_row.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Terminal 3 | # Since: 0.1.0 4 | # Usage: l.cursor_row 5 | # Description: Get row number of current cursor position. 6 | # --- 7 | 8 | # Refer to https://unix.stackexchange.com/a/183121 9 | l.cursor_row() { 10 | local ROW 11 | IFS=';' read -rsdR -p $'\E[6n' ROW _ 12 | # Strip decoration characters [ 13 | echo "${ROW#*[}" 14 | } 15 | -------------------------------------------------------------------------------- /src/modules/date.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Time 3 | # Since: 0.1.0 4 | # Usage: l.date 5 | # Description: Get the current date using [strftime](https://man7.org/linux/man-pages/man3/strftime.3.html). 6 | # Bash: 4.2 7 | # --- 8 | 9 | # Reference: https://github.com/dylanaraps/pure-bash-bible#get-the-current-date-using-strftime 10 | l.date() { 11 | # %(datefmt)T : Causes printf to output the date-time string resulting from using datefmt as a 12 | # format string for strftime(3). The corresponding argument is an integer representing the number 13 | # of seconds since the epoch. Two special argument values may be used: -1 represents the 14 | # current time, and -2 represents the time the shell was invoked. If no argument is specified, 15 | # conversion behaves as if -1 had been given. This is an exception to the usual printf behavior. 16 | printf "%($1)T\\n" "-1" 17 | } 18 | -------------------------------------------------------------------------------- /src/modules/detect_os.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: System 3 | # Since: 0.1.0 4 | # Usage: l.detect_os 5 | # Description: Return the name of current operator system. 6 | # --- 7 | 8 | l.detect_os() { 9 | _lobash.detect_os 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/dirname.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Path 3 | # Since: 0.1.0 4 | # Usage: l.dirname 5 | # Description: Alternative to dirname command. It much faster because using shell parameter expansion. 6 | # --- 7 | 8 | l.dirname() { 9 | _lobash.dirname "${1:-}" 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/dirname.p.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Path 3 | # Since: 0.1.0 4 | # Usage: echo | l.dirname.p 5 | # Description: The pipeline version of l.dirname 6 | # --- 7 | 8 | l.dirname.p() { 9 | local str 10 | IFS='' read -r str 11 | _lobash.dirname "$str" 12 | } 13 | -------------------------------------------------------------------------------- /src/modules/each.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Array 3 | # Since: 0.6.0 4 | # Usage: l.each 5 | # Usage: l.read_array out < <(l.each ) 6 | # Description: Iterates over elements of array and invokes function for each element. 7 | # Description: The function is invoked with two arguments: (value, index|key). 8 | # Description: `` must be the array name. `` must be the function name. 9 | # Description: You can use `l.read_array` to create an array with l.each while `` printing new value. 10 | # Description: l.each can be used for map/filter/count/find. 11 | # Notice: For associative array, the order of keys is uncertain. 12 | # --- 13 | 14 | l.each() { 15 | # shellcheck disable=2034 16 | local _key_ 17 | eval "for _key_ in \"\${!$1[@]}\"; do ${2} \"\${$1[\"\$_key_\"]}\" \"\$_key_\"; done" 18 | } 19 | -------------------------------------------------------------------------------- /src/modules/each.p.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Array 3 | # Since: 0.6.0 4 | # Usage: printf '1\n2\n' | l.each.p 5 | # Description: Iterates over elements from pipe and invokes function with each element. 6 | # Description: The function is invoked with two arguments: (value, index). 7 | # Description: `` must be the function name. 8 | # --- 9 | 10 | l.each.p() { 11 | local _i_=0 _l_ 12 | local IFS=$'\n' 13 | 14 | while read -r _l_; do 15 | $1 "$_l_" "$_i_" 16 | (( ++_i_ )) 17 | done 18 | } 19 | -------------------------------------------------------------------------------- /src/modules/echo.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Console 3 | # Since: 0.1.0 4 | # Usage: l.echo ... 5 | # Description: A easy and safe way to print string. Not support any options. 6 | # Description: The builtin echo will get unexpected result while execute `b=( -n 123 ); echo "${b[@]}"`. 7 | # Description: See https://github.com/anordal/shellharden/blob/master/how_to_do_things_safely_in_bash.md#echo--printf 8 | # Description: It can be used as iteratee, `l.seq l.echo 01 100` 9 | # --- 10 | 11 | l.echo() { 12 | printf -- '%b\n' "$*" 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/echo.p.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Console 3 | # Since: 0.6.0 4 | # Usage: printf '%s\n' {01..10} | l.echo.p 5 | # Description: Just print each line from pipe. It can be used for functional programming. 6 | # --- 7 | 8 | l.echo.p() { 9 | local _l_ 10 | while read -r _l_; do 11 | printf -- '%b\n' "$_l_" 12 | done 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/echo_array.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Console 3 | # Since: 0.1.0 4 | # Usage: l.echo_array 5 | # Description: Print each values of array with newline. 6 | # --- 7 | 8 | l.echo_array() { 9 | eval "printf '%s\\n' \"\${${1}[@]:-}\"" 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/echo_screen.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Console 3 | # Since: 0.2.0 4 | # Usage: l.echo_screen ... 5 | # Description: Similar to l.echo, but always print text to screen no matter redirection. 6 | # Description: If no screen, it will print nothing. 7 | # --- 8 | 9 | l.echo_screen() { 10 | # /dev/tty may not exist when run in interactive shell 11 | (printf -- '%b\n' "$*" >/dev/tty || true) 2>/dev/null 12 | } 13 | -------------------------------------------------------------------------------- /src/modules/end_with.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.1 4 | # Usage: l.end_with 5 | # Description: Return 0 (true) or 1 (false). This function should never throw exception error. 6 | # --- 7 | 8 | l.end_with() { 9 | [[ $1 =~ "$2"$ ]] 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/end_with.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.1 4 | # Usage: l.end_with.s 5 | # Description: This function always echo `true` or `false` and exit code always be 0. 6 | # Dependent: end_with 7 | # --- 8 | 9 | l.end_with.s() { 10 | l.end_with "$@" && echo true || echo false 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/extname.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: File 3 | # Since: 0.1.0 4 | # Usage: l.extname 5 | # Description: Returns the extension of the path, from the last occurrence of the . (period) character to end of string in the last portion of the path. If there is no . in the last portion of the path, or if the first character of the basename of path (see path.basename()) is ., then an empty string is returned. 6 | # --- 7 | 8 | l.extname() { 9 | local path=${1:-} 10 | [[ $path =~ ^\. ]] && echo '' && return 11 | [[ ! $path =~ \. ]] && echo '' && return 12 | echo ".${path##*.}" 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/extname.p.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: File 3 | # Since: 0.1.0 4 | # Usage: echo | l.extname.p 5 | # Description: The pipeline of l.extname 6 | # Dependent: extname 7 | # --- 8 | 9 | l.extname.p() { 10 | local path 11 | IFS='' read -r path 12 | l.extname "$path" 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/extract.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: String 3 | # Since: 0.6.0 4 | # Usage: l.extract < "" 5 | # Usage: printf 'text\n' | l.extract 6 | # Description: Extract strings between `` and ``. 7 | # --- 8 | 9 | l.extract() { 10 | local IFS=$'\n' 11 | local extract=false 12 | 13 | while read -r line; do 14 | [[ $extract == true ]] && [[ $line != "$2" ]] && printf '%s\n' "$line" 15 | [[ $line == "$1" ]] && extract=true 16 | [[ $line == "$2" ]] && extract=false 17 | done 18 | } 19 | -------------------------------------------------------------------------------- /src/modules/first.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Array 3 | # Since: 0.1.0 4 | # Usage: l.first [=1] 5 | # Description: Return the first values of array. 6 | # --- 7 | 8 | l.first() { 9 | local _size_ 10 | _size_=$(eval "echo \${#${1}[@]}") 11 | if (( _size_ > 0 )); then 12 | eval "printf '%s\\n' \"\${${1}[@]:0:${2:-1}}\"" 13 | fi 14 | } 15 | -------------------------------------------------------------------------------- /src/modules/has.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.has 5 | # Description: Exit with 0 or 1. Check if command/function/alias/keyword/builtin or anything existed. 6 | # Description: `` Valid value: `command`, `function`, `alias`, `keyword`, `builtin`, `the` 7 | # --- 8 | 9 | # Reference: https://github.com/qzb/is.sh/blob/master/is.sh 10 | l.has() { 11 | local condition="$1" 12 | local value="$2" 13 | 14 | case "$condition" in 15 | command) 16 | [[ -x "$(command -v "$value")" ]] && return 0;; 17 | function) 18 | [[ $(type -t "$value") == function ]] && return 0;; 19 | alias) 20 | [[ $(type -t "$value") == alias ]] && return 0;; 21 | keyword) 22 | [[ $(type -t "$value") == keyword ]] && return 0;; 23 | builtin) 24 | [[ $(type -t "$value") == builtin ]] && return 0;; 25 | the) 26 | type -t "$value" 27 | return $?;; 28 | *) 29 | echo "Invalid Condition: $condition" >&2 30 | return 3;; 31 | esac > /dev/null 32 | 33 | return 1 34 | } 35 | -------------------------------------------------------------------------------- /src/modules/has.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.has.s 5 | # Description: Echo `true` or `false` to indicate that command/function/alias/keyword/builtin or anything existed. 6 | # Description: `` Valid value: `command`, `function`, `alias`, `keyword`, `builtin`, `the` 7 | # --- 8 | 9 | l.has.s() { 10 | local condition="$1" 11 | local value="$2" 12 | 13 | case "$condition" in 14 | command) 15 | [[ -x "$(command -v "$value")" ]] && echo true || echo false;; 16 | function) 17 | [[ $(type -t "$value") == function ]] && echo true || echo false;; 18 | alias) 19 | [[ $(type -t "$value") == alias ]] && echo true || echo false;; 20 | keyword) 21 | [[ $(type -t "$value") == keyword ]] && echo true || echo false;; 22 | builtin) 23 | [[ $(type -t "$value") == builtin ]] && echo true || echo false;; 24 | the) 25 | type -t "$value" >/dev/null && echo true || echo false;; 26 | *) 27 | echo "Invalid Condition: $condition" >&2 28 | return 3;; 29 | esac 30 | } 31 | -------------------------------------------------------------------------------- /src/modules/has_not.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.has_not 5 | # Description: Opposite to l.has 6 | # Dependent: has 7 | # --- 8 | 9 | l.has_not() { 10 | local e=false 11 | [[ $- =~ e ]] && e=true 12 | set +e 13 | l.has "${@}" 14 | local result=$? 15 | [[ $e == true ]] && set -e 16 | 17 | if [[ $result == 0 ]]; then 18 | return 1 19 | elif [[ $result == 1 ]]; then 20 | return 0 21 | else 22 | return $result 23 | fi 24 | } 25 | -------------------------------------------------------------------------------- /src/modules/has_not.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.has_not.s 5 | # Dependent: has.s, not.s 6 | # Description: Opposite to l.has.s 7 | # --- 8 | 9 | l.has_not.s() { 10 | local r 11 | r=$(l.has.s "$@") 12 | l.not.s "$r" 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/head.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Array 3 | # Since: 0.6.0 4 | # Usage: l.head < "file" 5 | # Description: Display first lines from pipe 6 | # --- 7 | 8 | l.head() { 9 | local lines 10 | mapfile -tn "$1" lines 11 | printf '%s\n' "${lines[@]}" 12 | } 13 | -------------------------------------------------------------------------------- /src/modules/hex_to_rgb.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Color 3 | # Since: 0.1.0 4 | # Usage: l.hex_to_rgb 5 | # Description: It prints `$r\n$g\n$b\n`. 6 | # --- 7 | 8 | # Reference: https://github.com/dylanaraps/pure-bash-bible#convert-a-hex-color-to-rgb 9 | l.hex_to_rgb() { 10 | local hex r g b 11 | hex="${1/\#}" 12 | 13 | if (( ${#hex} == 6 )); then 14 | ((r=16#${hex:0:2},g=16#${hex:2:2},b=16#${hex:4:2})) || true 15 | elif (( ${#hex} == 3 )); then 16 | ((r=16#${hex:0:1}${hex:0:1},g=16#${hex:1:1}${hex:1:1},b=16#${hex:2:1}${hex:2:1})) || true 17 | else 18 | echo 'Argument is not a hex.' >&2 19 | return 3 20 | fi 21 | 22 | printf '%s\n%s\n%s\n' "$r" "$g" "$b" 23 | } 24 | -------------------------------------------------------------------------------- /src/modules/hostname.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: System 3 | # Since: 0.1.0 4 | # Usage: l.hostname 5 | # Description: Return current hostname. 6 | # --- 7 | 8 | l.hostname() { 9 | printf '%s\n' "${HOSTNAME:-$(hostname)}" 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/if.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.1.0 4 | # Usage: l.if [] 5 | # Description: The difference from shell builtin `if` is when condition function throw exception it will ended immediately. 6 | # Description: `` can be function name, string and number. The function should return `true`/`0` or `false`/`1`. 7 | # Description: `` and `` must be function name. And `` is optional. 8 | # Description: When `` is true, `` function will be invoked. Otherwise `` will be invoked if it passed. 9 | # --- 10 | 11 | l.if() { 12 | local condition 13 | if [[ $(type -t "$1") == function ]]; then 14 | condition=$($1) 15 | else 16 | condition=$1 17 | fi 18 | 19 | if [[ $condition == true ]] || [[ $condition == 0 ]]; then 20 | $2 21 | elif [[ $condition == false ]] || [[ $condition == 1 ]]; then 22 | ${3:-} 23 | else 24 | echo "Invalid condition: $condition" 25 | return 3 26 | fi 27 | } 28 | -------------------------------------------------------------------------------- /src/modules/inc.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Arithmetic 3 | # Since: 0.1.0 4 | # Usage: l.inc [=1] 5 | # Description: Increase number with addend. 6 | # --- 7 | 8 | l.inc() { 9 | eval "((${1}+=${2:-1})) || true" 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/is_array.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_array 5 | # Description: When the variable is array or associative array, it returns 0 (true). Otherwise it returns 1 (false). This function should never throw exception error. 6 | # Notice: Only with bash 4.3, this function return 1 when the variable declared without initialization. 7 | # Notice: Because `declare -p a` shows `declare: a: not found` when `declare -a a`. It's a bug in bash 4.3. 8 | # Dependent: var_attrs 9 | # --- 10 | 11 | l.is_array() { 12 | [[ -z ${1:-} ]] && return 1 13 | 14 | local attrs 15 | attrs=$(l.var_attrs "$1") 16 | 17 | # a: array 18 | # A: associate array 19 | [[ ${attrs} =~ a|A ]] 20 | } 21 | -------------------------------------------------------------------------------- /src/modules/is_array.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_array.s 5 | # Description: When the variable is array or associative array, it prints `true`. Otherwise it prints `false`. And it always exit with code 0. 6 | # Dependent: is_array 7 | # Notice: Only with bash 4.3, this function will echo `false` when the variable declared without initialization. 8 | # Notice: Because `declare -p a` shows `declare: a: not found` when `declare -a a`. It's a bug in bash 4.3. 9 | # --- 10 | 11 | l.is_array.s() { 12 | l.is_array "$@" && echo true || echo false 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/is_associative_array.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.7.0 4 | # Usage: l.is_associative_array 5 | # Description: When the variable is associative array, it returns 0 (true). Otherwise it returns 1 (false). This function should never throw exception error. 6 | # Notice: Only with bash 4.3, this function return 1 when the variable declared without initialization. 7 | # Notice: Because `declare -p a` shows `declare: a: not found` when `declare -a a`. It's a bug in bash 4.3. 8 | # Dependent: var_attrs 9 | # --- 10 | 11 | l.is_associative_array() { 12 | [[ -z ${1:-} ]] && return 1 13 | 14 | local attrs 15 | attrs=$(l.var_attrs "$1") 16 | 17 | [[ ${attrs} =~ A ]] 18 | } 19 | -------------------------------------------------------------------------------- /src/modules/is_associative_array.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.7.0 4 | # Usage: l.is_associative_array.s 5 | # Description: When the variable is array or associative array, it prints `true`. Otherwise it prints `false`. And it always exit with code 0. 6 | # Dependent: is_associative_array 7 | # Notice: Only with bash 4.3, this function will echo `false` when the variable declared without initialization. 8 | # Notice: Because `declare -p a` shows `declare: a: not found` when `declare -a a`. It's a bug in bash 4.3. 9 | # --- 10 | 11 | l.is_associative_array.s() { 12 | l.is_associative_array "$@" && echo true || echo false 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/is_bash.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_bash 5 | # Description: Return 0 (true) or 1 (false). This function should never throw exception error. 6 | # --- 7 | 8 | l.is_bash() { 9 | _lobash.is_bash 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/is_bash.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_bash.s 5 | # Description: This function always echo `true` or `false` and exit code always be 0. 6 | # --- 7 | 8 | l.is_bash.s() { 9 | _lobash.is_bash && echo true || echo false 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/is_dir.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_dir 5 | # Description: Detect `` is whether a directory or not. 6 | # Description: Return 0 (true) or 1 (false). This function should never throw exception error. 7 | # --- 8 | 9 | l.is_dir() { 10 | [[ -d ${1:-} ]] 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/is_dir.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_dir.s 5 | # Description: Detect `` is whether a directory or not. 6 | # Description: This function always echo `true` or `false` and exit code always be 0. 7 | # --- 8 | 9 | l.is_dir.s() { 10 | [[ -d ${1:-} ]] && echo true || echo false 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/is_empty_dir.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.6.0 4 | # Usage: l.is_empty_dir 5 | # Description: Test `` is whether a empty directory or not. If directory not found, it returns false. 6 | # Description: Return 0 (true) or 1 (false). This function should never throw exception error. 7 | # --- 8 | 9 | l.is_empty_dir() { 10 | [[ -d ${1:-} ]] && [[ -z $(ls -A "${1:-}") ]] 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/is_executable.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_executable 5 | # Description: Similar to `[[ -x ]]`. 6 | # Description: Return 0 (true) or 1 (false). This function should never throw exception error. 7 | # --- 8 | 9 | l.is_executable() { 10 | [[ -x ${1:-} ]] 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/is_executable.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_executable.s 5 | # Description: Similar to `[[ -x ]]` 6 | # Description: This function always echo `true` or `false` and exit code always be 0. 7 | # --- 8 | 9 | l.is_executable.s() { 10 | [[ -x ${1:-} ]] && echo true || echo false 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/is_executable_file.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Dependent: is_dir 5 | # Usage: l.is_executable_file 6 | # Description: Similar to `l.is_executable`. But if `` is directory it will return false. 7 | # Description: Return 0 (true) or 1 (false). This function should never throw exception error. 8 | # --- 9 | 10 | l.is_executable_file() { 11 | [[ -z ${1:-} ]] && return 1 12 | 13 | if l.is_dir "$1"; then 14 | # directory is executable 15 | # https://superuser.com/a/168583 16 | return 1 17 | else 18 | [[ -x $1 ]] 19 | fi 20 | } 21 | -------------------------------------------------------------------------------- /src/modules/is_executable_file.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_executable_file.s 5 | # Description: Similar to `l.is_executable`. But if `` is directory it will return false. 6 | # Description: This function always echo `true` or `false` and exit code always be 0. 7 | # Dependent: is_executable_file 8 | # --- 9 | 10 | l.is_executable_file.s() { 11 | l.is_executable_file "${1:-}" && echo true || echo false 12 | } 13 | -------------------------------------------------------------------------------- /src/modules/is_exported.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.5.0 4 | # Bash: 4.0 5 | # Usage: l.is_exported 6 | # Description: Check whether a shell variable is exported. 7 | # Description: Return 0 (true) or 1 (false). This function should never throw exception error. 8 | # Notice: Only with bash 4.3, this function return 1 when the exported variable declared without initialization. 9 | # Notice: Because `declare -p a` shows `declare: a: not found` when `declare -a a`. It's a bug in bash 4.3. 10 | # Notice: In bash 4.0~4.3, when `export ` without initialization, is_exported will return false. Because `declare -p ` will print "not found". It's a bug in Bash. 11 | # Dependent: var_attrs 12 | # --- 13 | 14 | l.is_exported() { 15 | local attrs 16 | attrs=$(l.var_attrs "$1") 17 | [[ $attrs == *x* ]] 18 | } 19 | -------------------------------------------------------------------------------- /src/modules/is_falsy.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_falsy 5 | # Description: nonzero number and string "false" should be falsy. 6 | # Description: Return 0 (true) or 1 (false). This function should never throw exception error. 7 | # Dependent: is_integer 8 | # --- 9 | 10 | l.is_falsy() { 11 | [[ -z ${1:-} ]] && return 1 12 | 13 | if l.is_integer "$1"; then 14 | [[ $1 != 0 ]] 15 | else 16 | [[ $1 == false ]] 17 | fi 18 | } 19 | -------------------------------------------------------------------------------- /src/modules/is_falsy.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_falsy.s 5 | # Description: nonzero number and string "false" should be falsy. 6 | # Description: This function always echo `true` or `false` and exit code always be 0. 7 | # Dependent: is_falsy 8 | # --- 9 | 10 | l.is_falsy.s() { 11 | l.is_falsy "${1:-}" && echo true || echo false 12 | } 13 | -------------------------------------------------------------------------------- /src/modules/is_file.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_file 5 | # Description: Return 0 (true) or 1 (false). This function should never throw exception error. 6 | # --- 7 | 8 | l.is_file() { 9 | [[ -f ${1:-} ]] 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/is_file.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_file.s 5 | # Description: This function always echo `true` or `false` and exit code always be 0. 6 | # --- 7 | 8 | l.is_file.s() { 9 | [[ -f ${1:-} ]] && echo true || echo false 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/is_float.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_float 5 | # Description: Return 0 (true) or 1 (false). This function should never throw exception error. 6 | # --- 7 | 8 | l.is_float() { 9 | [[ ${1:-} =~ ^[-+]?[0-9]+([.][0-9]+)?$ ]] 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/is_float.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_float.s 5 | # Description: This function always echo `true` or `false` and exit code always be 0. 6 | # Dependent: is_float 7 | # --- 8 | 9 | l.is_float.s() { 10 | l.is_float "${1:-}" && echo true || echo false 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/is_function.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.5.0 4 | # Usage: l.is_function 5 | # Description: Return 0 (true) or 1 (false). This function should never throw exception error. 6 | # --- 7 | 8 | l.is_function() { 9 | [[ $(type -t "${1:-}") == function ]] 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/is_function.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.5.0 4 | # Usage: l.is_function.s 5 | # Description: Detect `` is whether a function or not. 6 | # Description: This function always echo `true` or `false` and exit code always be 0. 7 | # --- 8 | 9 | l.is_function.s() { 10 | [[ $(type -t "${1:-}") == function ]] && echo true || echo false 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/is_gnu_sed.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.5.0 4 | # Usage: l.is_gnu_sed 5 | # Description: Return 0 (true) or 1 (false). This function should never throw exception error. 6 | # --- 7 | 8 | l.is_gnu_sed() { 9 | _lobash.is_gnu_sed 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/is_integer.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_integer 5 | # Description: Return 0 (true) or 1 (false). This function should never throw exception error. 6 | # --- 7 | 8 | l.is_integer() { 9 | [[ ${1:-} =~ ^[-+]?[0-9]+$ ]] 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/is_integer.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_integer.s 5 | # Description: This function always echo `true` or `false` and exit code always be 0. 6 | # Dependent: is_integer 7 | # --- 8 | 9 | l.is_integer.s() { 10 | l.is_integer "${1:-}" && echo true || echo false 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/is_link.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_link 5 | # Description: Return 0 (true) or 1 (false). This function should never throw exception error. 6 | # --- 7 | 8 | l.is_link() { 9 | [[ -L ${1:-} ]] 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/is_link.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_link.s 5 | # Description: This function always echo `true` or `false` and exit code always be 0. 6 | # --- 7 | 8 | l.is_link.s() { 9 | [[ -L ${1:-} ]] && echo true || echo false 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/is_number.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_number 5 | # Description: Return 0 (true) or 1 (false). This function should never throw exception error. 6 | # --- 7 | 8 | l.is_number() { 9 | [[ ${1:-} =~ ^[-+]?[0-9]+(.[0-9]+)?$ ]] 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/is_number.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_number.s 5 | # Description: This function always echo `true` or `false` and exit code always be 0. 6 | # Dependent: is_number 7 | # --- 8 | 9 | l.is_number.s() { 10 | l.is_number "${1:-}" && echo true || echo false 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/is_readable.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_readable 5 | # Description: Return 0 (true) or 1 (false). This function should never throw exception error. 6 | # --- 7 | 8 | l.is_readable() { 9 | [[ -r ${1:-} ]] 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/is_readable.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_readable.s 5 | # Description: This function always echo `true` or `false` and exit code always be 0. 6 | # --- 7 | 8 | l.is_readable.s() { 9 | [[ -r ${1:-} ]] && echo true || echo false 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/is_truthy.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_truthy 5 | # Description: zero number and string "true" should be truthy. 6 | # Description: Return 0 (true) or 1 (false). This function should never throw exception error. 7 | # --- 8 | 9 | l.is_truthy() { 10 | [[ ${1:-} == true ]] || [[ ${1:-} == 0 ]] 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/is_truthy.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_truthy.s 5 | # Description: zero number and string "true" should be truthy. 6 | # Description: This function always echo `true` or `false` and exit code always be 0. 7 | # Dependent: is_truthy 8 | # --- 9 | 10 | l.is_truthy.s() { 11 | l.is_truthy "${1:-}" && echo true || echo false 12 | } 13 | -------------------------------------------------------------------------------- /src/modules/is_tty_available.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.5.0 4 | # Usage: l.is_tty_available 5 | # Description: Whether /dev/tty is available 6 | # --- 7 | 8 | l.is_tty_available() { 9 | _lobash.is_tty_available 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/is_tty_available.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.5.0 4 | # Usage: l.is_tty_available.s 5 | # Description: Whether /dev/tty is available 6 | # Description: This function always echo `true` or `false` and exit code always be 0. 7 | # --- 8 | 9 | l.is_tty_available.s() { 10 | _lobash.is_tty_available && echo true || echo false 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/is_ubuntu.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.5.0 4 | # Usage: l.is_ubuntu 5 | # Description: Detect whether current os is Ubuntu or not. 6 | # Description: Return 0 (true) or 1 (false). This function should never throw exception error. 7 | # --- 8 | 9 | l.is_ubuntu() { 10 | if [[ -f /etc/os-release ]]; then 11 | grep '^NAME="Ubuntu"' /dev/null 12 | else 13 | return 1 14 | fi 15 | } 16 | -------------------------------------------------------------------------------- /src/modules/is_undefined.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.5.0 4 | # Bash: 4.0 5 | # Usage: l.is_undefined 6 | # Description: Check whether a shell variable is undefined. 7 | # Description: Return 0 (true) or 1 (false). This function should never throw exception error. 8 | # --- 9 | 10 | l.is_undefined() { 11 | if declare -p "$1" &>/dev/null ;then 12 | return 1 13 | else 14 | return 0 15 | fi 16 | } 17 | -------------------------------------------------------------------------------- /src/modules/is_writable.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_writable 5 | # Description: Return 0 (true) or 1 (false). This function should never throw exception error. 6 | # --- 7 | 8 | l.is_writable() { 9 | [[ -w ${1:-} ]] 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/is_writable.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.is_writable.s 5 | # Description: This function always echo `true` or `false` and exit code always be 0. 6 | # --- 7 | 8 | l.is_writable.s() { 9 | [[ -w ${1:-} ]] && echo true || echo false 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/join.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: String 3 | # Since: 0.1.0 4 | # Usage: l.join [=,] 5 | # Description: Convert all elements in array into a string separated by delimiter. 6 | # --- 7 | 8 | l.join() { 9 | if [[ $# == 1 ]]; then 10 | local IFS=, 11 | else 12 | local IFS=${2} 13 | fi 14 | eval "printf '%s\\n' \"\${${1}[*]:-}\"" 15 | } 16 | -------------------------------------------------------------------------------- /src/modules/keys.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Array 3 | # Since: 0.5.0 4 | # Usage: l.keys 5 | # Description: List all keys of array or associative array. 6 | # Notice: For associative array, the order of keys is uncertain. 7 | # --- 8 | 9 | l.keys() { 10 | # shellcheck disable=2034 11 | local _key_ 12 | eval "for _key_ in \"\${!${1}[@]}\" ; do echo \"\$_key_\"; done" 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/last.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Array 3 | # Since: 0.1.0 4 | # Usage: l.last [=1] 5 | # Description: Return the last values of array. 6 | # --- 7 | 8 | l.last() { 9 | local _count_=${2:-1} 10 | local _size_ 11 | _size_=$(eval "echo \${#${1}[@]}") 12 | 13 | if (( _size_ > 0 )); then 14 | if (( _count_ < _size_ )); then 15 | eval "printf '%s\\n' \"\${${1}[@]: -${_count_}:${_count_}}\"" 16 | else 17 | eval "printf '%s\\n' \"\${${1}[@]}\"" 18 | fi 19 | fi 20 | } 21 | -------------------------------------------------------------------------------- /src/modules/lower_case.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: String 3 | # Since: 0.1.0 4 | # Usage: l.lower_case 5 | # Description: Convert all characters of string to lower case. 6 | # --- 7 | 8 | # An alternative for Bash 3.x 9 | # l.lower_case() { 10 | # tr '[:upper:]' '[:lower:]' <<< ${1:-} 11 | # } 12 | 13 | l.lower_case() { 14 | local str=${1:-} 15 | printf '%s\n' "${str,,}" 16 | } 17 | -------------------------------------------------------------------------------- /src/modules/lower_case.p.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: String 3 | # Since: 0.1.0 4 | # Usage: echo | l.lower_case.p 5 | # Description: The pipeline version of l.lower_case 6 | # Dependent: lower_case 7 | # --- 8 | 9 | l.lower_case.p() { 10 | local str 11 | IFS='' read -r str 12 | l.lower_case "$str" 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/lower_first.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: String 3 | # Since: 0.1.0 4 | # Usage: l.lower_first 5 | # Description: Convert the first character of string to lower case. 6 | # --- 7 | 8 | l.lower_first() { 9 | local str=${1:-} 10 | printf '%s\n' "${str,}" 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/lower_first.p.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: String 3 | # Since: 0.1.0 4 | # Usage: echo | l.lower_first.p 5 | # Description: The pipeline version of l.lower_first 6 | # Dependent: lower_first 7 | # --- 8 | 9 | l.lower_first.p() { 10 | local str 11 | IFS='' read -r str 12 | l.lower_first "$str" 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/match.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: String 3 | # Since: 0.1.0 4 | # Usage: l.match "string" "regex" [index=1] 5 | # Description: Return matched part of string. Return empty string if no matched. Support capturing groups. 6 | # --- 7 | 8 | l.match() { 9 | [[ ${3:-} == 0 ]] && echo "index cannot be 0" >&2 && return 3 10 | 11 | if [[ $1 =~ $2 ]]; then 12 | if (( ${#BASH_REMATCH[@]} > 1 )); then 13 | printf '%s\n' "${BASH_REMATCH[${3:-1}]}" 14 | else 15 | echo '' 16 | fi 17 | else 18 | echo '' 19 | fi 20 | } 21 | -------------------------------------------------------------------------------- /src/modules/match_list.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: String 3 | # Since: 0.4.0 4 | # Usage: l.match_list 5 | # Description: Match multi strings with Regex Capturing Groups. The matched parts will be put in ``. 6 | # --- 7 | 8 | l.match_list() { 9 | (( $# != 3 )) && echo "wrong parameters" >&2 && return 3 10 | 11 | if [[ $1 =~ $2 ]]; then 12 | local _len_=${#BASH_REMATCH[@]} 13 | local _i_ 14 | if (( _len_ > 1 )); then 15 | for (( _i_ = 1; _i_ < _len_; _i_++ )); do 16 | eval "${3}+=( \"\${BASH_REMATCH[$_i_]}\" )" 17 | done 18 | fi 19 | fi 20 | } 21 | -------------------------------------------------------------------------------- /src/modules/normalize.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Path 3 | # Since: 0.1.0 4 | # Usage: l.normalize 5 | # Description: Normalize the given path which can be an unexisted path. 6 | # Description: Trailing `/` always be removed. 7 | # Dependent: split, join 8 | # --- 9 | 10 | l.normalize() { 11 | local path=${1:-} 12 | 13 | if [[ -z ${path} ]]; then 14 | echo '.' 15 | return 0 16 | fi 17 | 18 | if [[ ${path} == '.' ]]; then 19 | echo '.' 20 | return 0 21 | fi 22 | 23 | local -a words 24 | l.split "$path" words '/' 25 | local -a list=() 26 | local -a pre_list=() 27 | local n=0 28 | local i 29 | 30 | if [[ ${path:0:1} == '/' ]]; then 31 | pre_list+=(/) 32 | else 33 | for i in "${words[@]}"; do 34 | if [[ $i =~ ^'.' ]]; then 35 | ((n+=1)) 36 | pre_list+=("$i") 37 | else 38 | break 39 | fi 40 | done 41 | fi 42 | 43 | for (( ; n < ${#words[@]}; n++ )); do 44 | i=${words[$n]} 45 | if [[ $i == '' ]] || [[ $i == '.' ]]; then 46 | true 47 | elif [[ $i == '..' ]]; then 48 | local k=$(( ${#list[@]} - 1)) || true 49 | [[ $k > -1 ]] && unset list["$k"] 50 | else 51 | list+=("$i") 52 | fi 53 | done 54 | 55 | printf '%s%s\n' "$(l.join pre_list '/')" "$(l.join list '/')" 56 | } 57 | -------------------------------------------------------------------------------- /src/modules/normalize.p.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Path 3 | # Since: 0.1.0 4 | # Usage: echo | l.normalize.p 5 | # Description: The pipeline version of l.normalize 6 | # Dependent: normalize 7 | # --- 8 | 9 | l.normalize.p() { 10 | local path 11 | IFS='' read -r path 12 | l.normalize "$path" 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/not.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: l.not.s 5 | # Description: `` must be `true` or `false`. This function returns the opposite value. 6 | # --- 7 | 8 | l.not.s() { 9 | local condition="${1:-}" 10 | 11 | if [[ $condition == true ]]; then 12 | echo false; 13 | elif [[ $condition == false ]]; then 14 | echo true 15 | else 16 | echo "Invalid condition! It must be one of 'true' and 'false'. Current value=$condition" >&2 17 | return 3 18 | fi 19 | } 20 | -------------------------------------------------------------------------------- /src/modules/not.s.p.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.0 4 | # Usage: echo | l.not.s.p 5 | # Description: The pipeline version of l.not 6 | # Dependent: not.s 7 | # --- 8 | 9 | l.not.s.p() { 10 | local condition 11 | read -r condition 12 | l.not.s "$condition" 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/now.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Time 3 | # Since: 0.1.0 4 | # Usage: l.now 5 | # Description: Print the timestamp of the number of milliseconds that have elapsed since the Unix epoch (1 January 1970 00:00:00 UTC). 6 | # Notice: When run under Bash 4.0~4.4 and Alpine/Busybox systems, perl 5+ is required. Most Unix/Linux operating systems have included Perl 5. See the [Perl Binaries](https://www.cpan.org/ports/binaries.html). 7 | # --- 8 | 9 | if (( BASH_VERSINFO[0] > 4 )); then 10 | # EPOCHREALTIME is supported since Bash 5.0 11 | l.now() { 12 | echo $(( ${EPOCHREALTIME/./} / 1000 )) 13 | } 14 | else 15 | _l.perl_now() { 16 | perl -MTime::HiRes=time -e 'printf "%d\n", time * 1000' 17 | } 18 | 19 | l.now() { 20 | local timestamp 21 | 22 | if [[ $_LOBASH_OS == 'MacOS' ]]; then 23 | # date '+%N' not supported in MacOS. 24 | _l.perl_now 25 | else 26 | # Some Linux systems may not install the Perl module "Time::HiRes". 27 | # So use date '+%3N' to get milliseconds. 28 | timestamp=$(date '+%s%3N') 29 | 30 | if [[ ${#timestamp} == 10 ]]; then 31 | # But the date '+%N' is GNU date feature which not supported in Alpine/Busybox systems. 32 | _l.perl_now 33 | else 34 | printf '%s\n' "$timestamp" 35 | fi 36 | fi 37 | } 38 | fi 39 | -------------------------------------------------------------------------------- /src/modules/now_s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Time 3 | # Since: 0.1.0 4 | # Usage: l.now_s 5 | # Description: Print the timestamp of the number of seconds that have elapsed since the Unix epoch (1 January 1970 00:00:00 UTC). 6 | # Dependent: date 7 | # --- 8 | 9 | l.now_s() { 10 | l.date '%s' 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/pwd.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Path 3 | # Since: 0.1.0 4 | # Usage: l.pwd 5 | # Description: Return the current working directory as set by the `cd` builtin command. 6 | # --- 7 | 8 | l.pwd() { 9 | printf '%s\n' "$PWD" 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/random.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Variable 3 | # Since: 0.1.0 4 | # Usage: l.random [=10] [=a-zA-Z0-9@#*=[]] 5 | # Description: Return a random string in specific length. It must be a positive integer. 6 | # Description: The `` is allowed characters in range. 7 | # Description: This function requires `dd` command available in system. 8 | # --- 9 | 10 | l.random() { 11 | local length=${1:-10} 12 | local pattern=${2:-a-zA-Z0-9@#*=[]} 13 | local result='' 14 | 15 | # This line does not work in Github Action. See https://github.com/orgs/community/discussions/39644 16 | # printf '%s\n' "$(LC_ALL=C tr -dc "$pattern" < /dev/urandom | head -c "$length" || true)" 17 | 18 | while (( ${#result} < length )); do 19 | result="$result$(dd bs=512 if=/dev/urandom count=1 2>/dev/null | LC_ALL=C tr -dc "$pattern" | head -c "$length" || true)" 20 | done 21 | 22 | echo "${result:0:$length}" 23 | } 24 | -------------------------------------------------------------------------------- /src/modules/read_array.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Array 3 | # Since: 0.6.0 4 | # Usage: l.read_array < <(printf 'a\nb\nc\n') 5 | # Description: Read array from pipeline (Each item must be split with `\n`). The results store in ``. 6 | # Notice: Do not use `printf 'a\nb\nc\n' | l.read_array `. See [this link](https://superuser.com/a/1348950) for reason. 7 | # --- 8 | 9 | if (( BASH_VERSINFO[0] == 4 )) && (( BASH_VERSINFO[1] < 4 )); then 10 | l.read_array() { 11 | IFS=$'\n' readarray -t "$1" 12 | } 13 | else 14 | l.read_array() { 15 | readarray -d $'\n' -t "$1" 16 | } 17 | fi 18 | -------------------------------------------------------------------------------- /src/modules/relative.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Path 3 | # Since: 0.5.0 4 | # Usage: l.relative 5 | # Description: Output the relative path. 6 | # Dependent: normalize 7 | # --- 8 | 9 | l.relative() { 10 | local from=${1:-.} 11 | local to=${2:-.} 12 | 13 | from=$(l.normalize "$from") 14 | to=$(l.normalize "$to") 15 | 16 | local result='' 17 | 18 | while [[ "${to#"$from"}" == "$to" ]]; do 19 | if [[ $from == '.' ]]; then 20 | break 21 | fi 22 | 23 | from=$(dirname "$from") 24 | if [[ -z $result ]]; then 25 | result="../" 26 | else 27 | result="../$result" 28 | fi 29 | done 30 | 31 | forward_part="${to#"$from"}" 32 | forward_part="${forward_part#/}" # remove head slash 33 | 34 | if [[ -n $result ]]; then 35 | result="$result$forward_part" 36 | else 37 | result="${forward_part}" 38 | fi 39 | 40 | echo "${result%/}" # remove tail slash 41 | } 42 | -------------------------------------------------------------------------------- /src/modules/repeat.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Util 3 | # Since: 0.1.0 4 | # Usage: l.repeat []... 5 | # Description: Execute command in N times. 6 | # --- 7 | 8 | l.repeat() { 9 | local -i n=$1 10 | (( n == 0 )) && return 11 | (( n < 0 )) && return 12 | 13 | shift 14 | for n in $(seq "$n"); do 15 | "$@" 16 | done 17 | } 18 | -------------------------------------------------------------------------------- /src/modules/rgb_to_hex.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Color 3 | # Since: 0.1.0 4 | # Usage: l.rgb_to_hex 5 | # Dependent: is_number 6 | # Description: ``, ``, `` must be positive integer (0~255). 7 | # Description: It prints hex string. Like '#ffffff' 8 | # --- 9 | 10 | # Reference: https://github.com/dylanaraps/pure-bash-bible#convert-an-rgb-color-to-hex 11 | l.rgb_to_hex() { 12 | if (( $# != 3 )); then 13 | echo 'The arguments size not equal 3' >&2 14 | return 3 15 | fi 16 | 17 | if ! l.is_number "$1"; then 18 | echo 'The first argument is not a number' >&2 19 | return 4 20 | fi 21 | 22 | if ! l.is_number "$2"; then 23 | echo 'The second argument is not a number' >&2 24 | return 5 25 | fi 26 | 27 | if ! l.is_number "$3"; then 28 | echo 'The third argument is not a number' >&2 29 | return 6 30 | fi 31 | 32 | printf '#%02x%02x%02x\n' "$1" "$2" "$3" 33 | } 34 | -------------------------------------------------------------------------------- /src/modules/sedi.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Util 3 | # Since: 0.6.0 4 | # Usage: l.sedi ... 5 | # Description: Wrap "sed -i". Compatible with GNU sed and BSD sed. 6 | # Description: The usage refer to `man sed`. 7 | # --- 8 | 9 | if _lobash.is_gnu_sed; then 10 | l.sedi() { sed -i'' "$@"; } 11 | else 12 | l.sedi() { sed -i '' "$@"; } 13 | fi 14 | -------------------------------------------------------------------------------- /src/modules/seq.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Array 3 | # Since: 0.6.0 4 | # Usage: l.seq [=1] 5 | # Description: Same to `for i in {....}; do "$i"; done` 6 | # Description: `` must be positive integer. And `` can be less than ``. 7 | # Description: It can print zero-padded numbers. `l.seq 01 100` 8 | # Notice: With Bash 4.0, do not use zero-padded numbers, bash has a bug. 9 | # --- 10 | 11 | l.seq() { 12 | local i 13 | for i in $(eval "echo {$2..$3..${4:-1}}"); do $1 "$i"; done 14 | } 15 | -------------------------------------------------------------------------------- /src/modules/sort.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Array 3 | # Since: 0.1.0 4 | # Usage: l.sort []... 5 | # Description: Print a new sorted array with linux "sort" command. 6 | # Description: The `` are options of "sort" command. 7 | # --- 8 | 9 | l.sort() { 10 | local _array_name_=$1 11 | shift 12 | eval "printf '%s\\n' \"\${${_array_name_}[@]:-}\" | sort \"\$@\"" 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/start_with.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.1 4 | # Usage: l.start_with 5 | # Description: Check if a string starts with given match string. 6 | # Description: Return 0 (true) or 1 (false). This function should never throw exception error. 7 | # --- 8 | 9 | l.start_with() { 10 | [[ $1 =~ ^"$2" ]] 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/start_with.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.1 4 | # Usage: l.start_with.s 5 | # Description: Check if a string starts with given match string. 6 | # Description: This function always echo `true` or `false` and exit code always be 0. 7 | # Dependent: start_with 8 | # --- 9 | 10 | l.start_with.s() { 11 | l.start_with "$@" && echo true || echo false 12 | } 13 | -------------------------------------------------------------------------------- /src/modules/str_include.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.1 4 | # Usage: l.str_include 5 | # Description: Return `true` or `false`. Check if a string includes given match string. 6 | # --- 7 | 8 | # shellcheck disable=SC2076 9 | 10 | l.str_include() { 11 | [[ ${2:-} == '' ]] && return 0 12 | [[ "${1:-}" =~ "${2:-}" ]] 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/str_include.s.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Condition 3 | # Since: 0.3.1 4 | # Usage: l.str_include.s 5 | # Description: Return `true` or `false`. Check if a string includes given match string. 6 | # Dependent: str_include 7 | # --- 8 | 9 | # shellcheck disable=SC2076 10 | 11 | l.str_include.s() { 12 | l.str_include "$@" && echo true || echo false; 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/str_len.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: String 3 | # Since: 0.1.0 4 | # Usage: l.str_len 5 | # Description: Return the byte length of string. 6 | # --- 7 | 8 | l.str_len() { 9 | [[ -z ${1:-} ]] && echo 0 && return 10 | 11 | local old_lang old_lc_all bytlen 12 | [[ -n ${LC_ALL:-} ]] && old_lc_all=$LC_ALL 13 | [[ -n ${LANG:-} ]] && old_lang=$LANG 14 | 15 | LANG=C LC_ALL=C 16 | bytlen=${#1} 17 | printf -- '%s\n' "$bytlen" 18 | 19 | [[ -n ${old_lang:-} ]] && LANG=$old_lang 20 | if [[ -n ${old_lc_all:-} ]]; then 21 | LC_ALL=$old_lc_all 22 | fi 23 | } 24 | -------------------------------------------------------------------------------- /src/modules/str_replace.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: String 3 | # Since: 0.5.0 4 | # Usage: l.str_replace 5 | # Description: The first longest match of `` is replaced with ``. 6 | # --- 7 | 8 | l.str_replace() { 9 | local pattern=${2:-} 10 | if [[ $pattern =~ ^'#' ]]; then pattern="\\$pattern" ; fi 11 | if [[ $pattern =~ ^'%' ]]; then pattern="\\$pattern" ; fi 12 | echo "${1/$pattern/${3:-}}" 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/str_replace_all.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: String 3 | # Since: 0.5.0 4 | # Usage: l.str_replace_all 5 | # Description: All matches of `` are replaced with ``. 6 | # --- 7 | 8 | l.str_replace_all() { 9 | echo "${1//${2:-}/${3:-}}" 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/str_replace_last.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: String 3 | # Since: 0.5.0 4 | # Usage: l.str_replace_last 5 | # Description: The first longest match of `` is replaced with ``. But it matches from the end of string to the head. 6 | # --- 7 | 8 | l.str_replace_last() { 9 | echo "${1/%${2:-}/${3:-}}" 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/str_size.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: String 3 | # Since: 0.1.0 4 | # Usage: l.str_size 5 | # Description: Return the sum of string letters. 6 | # --- 7 | 8 | l.str_size() { 9 | [[ -z ${1:-} ]] && echo 0 && return 10 | 11 | # It not work with double-width characters when environment LANG is not UTF-8. 12 | local OLD_LANG 13 | [[ -n ${LANG:-} ]] && OLD_LANG=$LANG 14 | 15 | LANG=C.UTF-8 16 | printf '%s\n' "${#1}" 17 | 18 | if [[ -n ${OLD_LANG:-} ]]; then 19 | LANG=$OLD_LANG 20 | fi 21 | } 22 | -------------------------------------------------------------------------------- /src/modules/sub.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Arithmetic 3 | # Since: 0.1.0 4 | # Usage: l.sub [=1] 5 | # Description: Subtract number with subtrahend. 6 | # --- 7 | 8 | l.sub() { 9 | eval "((${1}-=${2:-1})) || true" 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/term_size.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Terminal 3 | # Since: 0.6.0 4 | # Usage: l.term_size 5 | # Description: Print the terminal size. Format: `$lines\n$columns\n` 6 | # Description: You can use `l.read_array term < <(l.term_size)` to create an array. 7 | # --- 8 | 9 | # Refer to https://github.com/dylanaraps/pure-bash-bible#get-the-terminal-size-in-lines-and-columns-from-a-script 10 | l.term_size() { 11 | local status 12 | status=$(shopt -p checkwinsize) 13 | # (:;:) is a micro sleep to ensure the variables are exported immediately. 14 | shopt -s checkwinsize; (:;:) 15 | printf '%s\n' "$LINES" "$COLUMNS" 16 | $status 17 | } 18 | -------------------------------------------------------------------------------- /src/modules/trace_count.bash: -------------------------------------------------------------------------------- 1 | # --- 2 | # Category: Debug 3 | # Since: 0.1.0 4 | # Usage: l.trace_count [