From 6850fcf6cd3e6412e32b19bda0b7beba5b71cf8a Mon Sep 17 00:00:00 2001 From: Alexey Gladkov Date: Mon, 25 Aug 2025 14:56:05 +0200 Subject: [PATCH 1/8] tests: Change list of shells MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove yash. Shell seems to behave completely differently than POSIX-compatible shells. - Add lksh. The mksh variant that uses POSIX-compliant arithmetics with the host “long” data type and is automatically in POSIX mode when called as /bin/sh. Signed-off-by: Alexey Gladkov --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 94c3501..be9949a 100644 --- a/Makefile +++ b/Makefile @@ -83,7 +83,7 @@ release: $(PROJECT)-$(VERSION).tar.sign check: @cd tests; \ - for sh in $${CHECK_SHELL:-/bin/sh /bin/dash /bin/bash /bin/bash3 /bin/bash4 /bin/mksh /bin/yash /bin/pdksh}; do \ + for sh in $${CHECK_SHELL:-/bin/sh /bin/dash /bin/bash /bin/bash3 /bin/bash4 /bin/mksh /bin/lksh /bin/pdksh}; do \ [ -x "$$sh" ] || continue; \ export TEST_SHELL="$$sh"; \ echo "Running tests with $$sh"; \ From 3064fd741ec376054e04f4b402018de76b4a8b52 Mon Sep 17 00:00:00 2001 From: Alexey Gladkov Date: Mon, 25 Aug 2025 15:05:04 +0200 Subject: [PATCH 2/8] tests: Do not stop on first failed test Signed-off-by: Alexey Gladkov --- Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index be9949a..08871dd 100644 --- a/Makefile +++ b/Makefile @@ -83,6 +83,7 @@ release: $(PROJECT)-$(VERSION).tar.sign check: @cd tests; \ + rc=0; \ for sh in $${CHECK_SHELL:-/bin/sh /bin/dash /bin/bash /bin/bash3 /bin/bash4 /bin/mksh /bin/lksh /bin/pdksh}; do \ [ -x "$$sh" ] || continue; \ export TEST_SHELL="$$sh"; \ @@ -90,9 +91,9 @@ check: if ! "$$sh" -efu ./runtests; then \ echo "Tests failed with $$sh"; \ echo; \ - exit 1; \ + rc=1; \ fi; \ - done + done; exit $$rc; @sed -n -e 's/^## \([^[:space:]]\+\)$$/\1/p' ${mddocs_TARGETS} |sort -uo "$(CURDIR)/.shell-funcs-documented" @sed -n -e 's/^\([A-Za-z][A-Za-z0-9_]\+\)().*/\1/p' ${bin_TARGETS} |sort -uo "$(CURDIR)/.shell-funcs" @comm -13 \ From e999c16aa5bd41016dd54d172347858df4559abb Mon Sep 17 00:00:00 2001 From: Alexey Gladkov Date: Mon, 25 Aug 2025 17:07:00 +0200 Subject: [PATCH 3/8] github: Rewrite workflow file Signed-off-by: Alexey Gladkov --- .github/workflows/lint.yml | 64 +++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 36 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e32801a..c9b1a52 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,24 +7,6 @@ on: branches: [ master, for-master ] jobs: - lint-shell: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: "install tools" - run: sudo apt-get -y -qq install shellcheck - - name: "shellcheck" - run: make verify - - check-docs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: "install tools" - run: sudo apt-get -y -qq install scdoc - - name: "make docs" - run: make man - check-commits: runs-on: ubuntu-latest steps: @@ -52,32 +34,42 @@ jobs: exit 1 fi - check-dash: + lint-shell: runs-on: ubuntu-latest - needs: [ lint-shell ] steps: - uses: actions/checkout@v4 - - name: "install tools" - run: sudo apt-get -y -qq install dash bash mksh - - name: "unittests" - run: make check CHECK_SHELL=/bin/dash V=1 + - run: sudo apt-get -y -qq install shellcheck + - run: make verify - check-bash: + check-docs: runs-on: ubuntu-latest - needs: [ lint-shell ] steps: - uses: actions/checkout@v4 - - name: "install tools" - run: sudo apt-get -y -qq install dash bash mksh - - name: "unittests" - run: make check CHECK_SHELL=/bin/bash V=1 + - run: sudo apt-get -y -qq install scdoc + - run: make man - check-mksh: + check-shells: runs-on: ubuntu-latest - needs: [ lint-shell ] + needs: + - lint-shell + strategy: + fail-fast: false + matrix: + include: + - name: dash + pkgs: dash + + - name: bash + pkgs: bash + + - name: mksh + pkgs: mksh + + - name: lksh + pkgs: mksh + + name: "check with ${{ matrix.name }}" steps: - uses: actions/checkout@v4 - - name: "install tools" - run: sudo apt-get -y -qq install dash bash mksh - - name: "unittests" - run: make check CHECK_SHELL=/bin/mksh V=1 + - run: sudo apt-get -y -qq install ${{ matrix.pkgs }} + - run: make check V=1 CHECK_SHELL=/bin/${{ matrix.name }} From 6efc05e5cd3e2dc1a4c4bbbb3db59b41c1758fc8 Mon Sep 17 00:00:00 2001 From: Alexey Gladkov Date: Mon, 25 Aug 2025 18:40:42 +0200 Subject: [PATCH 4/8] tests: Split check into tests and documentation check Signed-off-by: Alexey Gladkov --- Makefile | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 08871dd..e1007ee 100644 --- a/Makefile +++ b/Makefile @@ -81,19 +81,25 @@ $(PROJECT)-$(VERSION).tar.sign: $(PROJECT)-$(VERSION).tar.xz tar: $(PROJECT)-$(VERSION).tar.xz release: $(PROJECT)-$(VERSION).tar.sign -check: - @cd tests; \ - rc=0; \ - for sh in $${CHECK_SHELL:-/bin/sh /bin/dash /bin/bash /bin/bash3 /bin/bash4 /bin/mksh /bin/lksh /bin/pdksh}; do \ - [ -x "$$sh" ] || continue; \ - export TEST_SHELL="$$sh"; \ - echo "Running tests with $$sh"; \ - if ! "$$sh" -efu ./runtests; then \ - echo "Tests failed with $$sh"; \ +CHECK_SHELL = +CHECK_SHELL += /bin/sh # POSIX Shell +CHECK_SHELL += /bin/dash # Debian Almquist Shell (https://git.kernel.org/pub/scm/utils/dash/dash.git) +CHECK_SHELL += /bin/bash /bin/bash3 /bin/bash4 # GNU Bourne-Again Shell (https://git.savannah.gnu.org/cgit/bash.git) +CHECK_SHELL += /bin/mksh /bin/lksh # MirBSD Korn Shell (https://mbsd.evolvis.org/mksh.htm) + +check-tests: + @cd tests; rc=0; \ + for TEST_SHELL in $(wildcard $(CHECK_SHELL)); do export TEST_SHELL; \ + echo "Running tests with $$TEST_SHELL"; \ + if ! $$TEST_SHELL -efu ./runtests; then \ + echo >&2 "ERROR: tests failed."; \ echo; \ rc=1; \ fi; \ - done; exit $$rc; + done; \ + exit $$rc; + +check-documented: @sed -n -e 's/^## \([^[:space:]]\+\)$$/\1/p' ${mddocs_TARGETS} |sort -uo "$(CURDIR)/.shell-funcs-documented" @sed -n -e 's/^\([A-Za-z][A-Za-z0-9_]\+\)().*/\1/p' ${bin_TARGETS} |sort -uo "$(CURDIR)/.shell-funcs" @comm -13 \ @@ -104,18 +110,19 @@ check: if [ "$$(wc -l < "$(CURDIR)/.shell-funcs-not-documented")" != "0" ]; then \ echo >&2 "ERROR: some functions are not documented:"; \ cat "$(CURDIR)/.shell-funcs-not-documented"; \ - echo; \ rc=1; \ else \ echo "All functions are documented."; \ - echo; \ fi; \ + echo; \ rm -f -- \ "$(CURDIR)/.shell-funcs-documented" \ "$(CURDIR)/.shell-funcs-not-documented" \ "$(CURDIR)/.shell-funcs"; \ exit $$rc; +check: check-tests check-documented + NULL = SPACE = $(NULL) $(NULL) COMMA = , From 32ccfa5db06407268fb370bb58f5f78777272690 Mon Sep 17 00:00:00 2001 From: Alexey Gladkov Date: Tue, 26 Aug 2025 11:16:21 +0200 Subject: [PATCH 5/8] Rename docs/*.md -> mans/*.scd Signed-off-by: Alexey Gladkov --- Makefile | 12 ++++++------ README.md | 2 +- docs/.template.md => mans/.template.scd | 0 docs/README.md => mans/README.scd | 0 docs/libshell.md => mans/libshell.scd | 0 docs/shell-args.md => mans/shell-args.scd | 0 docs/shell-cmdline.md => mans/shell-cmdline.scd | 0 docs/shell-config.md => mans/shell-config.scd | 0 docs/shell-error.md => mans/shell-error.scd | 0 docs/shell-getopt.md => mans/shell-getopt.scd | 0 .../shell-git-config.md => mans/shell-git-config.scd | 0 .../shell-ini-config.md => mans/shell-ini-config.scd | 0 .../shell-ip-address.md => mans/shell-ip-address.scd | 0 docs/shell-json.md => mans/shell-json.scd | 0 docs/shell-locks.md => mans/shell-locks.scd | 0 .../shell-mail-address.scd | 0 docs/shell-process.md => mans/shell-process.scd | 0 docs/shell-quote.md => mans/shell-quote.scd | 0 docs/shell-run.md => mans/shell-run.scd | 0 docs/shell-signal.md => mans/shell-signal.scd | 0 docs/shell-source.md => mans/shell-source.scd | 0 docs/shell-string.md => mans/shell-string.scd | 0 docs/shell-temp.md => mans/shell-temp.scd | 0 docs/shell-terminfo.md => mans/shell-terminfo.scd | 0 docs/shell-unittest.md => mans/shell-unittest.scd | 0 docs/shell-var.md => mans/shell-var.scd | 0 26 files changed, 7 insertions(+), 7 deletions(-) rename docs/.template.md => mans/.template.scd (100%) rename docs/README.md => mans/README.scd (100%) rename docs/libshell.md => mans/libshell.scd (100%) rename docs/shell-args.md => mans/shell-args.scd (100%) rename docs/shell-cmdline.md => mans/shell-cmdline.scd (100%) rename docs/shell-config.md => mans/shell-config.scd (100%) rename docs/shell-error.md => mans/shell-error.scd (100%) rename docs/shell-getopt.md => mans/shell-getopt.scd (100%) rename docs/shell-git-config.md => mans/shell-git-config.scd (100%) rename docs/shell-ini-config.md => mans/shell-ini-config.scd (100%) rename docs/shell-ip-address.md => mans/shell-ip-address.scd (100%) rename docs/shell-json.md => mans/shell-json.scd (100%) rename docs/shell-locks.md => mans/shell-locks.scd (100%) rename docs/shell-mail-address.md => mans/shell-mail-address.scd (100%) rename docs/shell-process.md => mans/shell-process.scd (100%) rename docs/shell-quote.md => mans/shell-quote.scd (100%) rename docs/shell-run.md => mans/shell-run.scd (100%) rename docs/shell-signal.md => mans/shell-signal.scd (100%) rename docs/shell-source.md => mans/shell-source.scd (100%) rename docs/shell-string.md => mans/shell-string.scd (100%) rename docs/shell-temp.md => mans/shell-temp.scd (100%) rename docs/shell-terminfo.md => mans/shell-terminfo.scd (100%) rename docs/shell-unittest.md => mans/shell-unittest.scd (100%) rename docs/shell-var.md => mans/shell-var.scd (100%) diff --git a/Makefile b/Makefile index e1007ee..ac3d1e9 100644 --- a/Makefile +++ b/Makefile @@ -20,9 +20,9 @@ capability_TARGETS = shell-regexp bin_TARGETS = $(filter-out shell-lib,$(wildcard shell-*)) data_TARGETS = COPYING -mddocs_TARGETS = $(wildcard docs/shell-*.md) -docs_TARGETS = docs/libshell.md $(mddocs_TARGETS) -man_TARGETS = $(docs_TARGETS:.md=.3) +mandocs_TARGETS = $(wildcard mans/shell-*.scd) +docs_TARGETS = mans/libshell.scd $(mandocs_TARGETS) +man_TARGETS = $(docs_TARGETS:.scd=.3) .PHONY: $(SUBDIRS) @@ -40,7 +40,7 @@ shell-lib: ${bin_TARGETS} shell-regexp: shell-quote ln -s -- $^ $@ -%.3: %.md +%.3: %.scd @[ -z "$(SCDOC)" ] || $(SCDOC) < $< > $@ man: ${man_TARGETS} @@ -100,8 +100,8 @@ check-tests: exit $$rc; check-documented: - @sed -n -e 's/^## \([^[:space:]]\+\)$$/\1/p' ${mddocs_TARGETS} |sort -uo "$(CURDIR)/.shell-funcs-documented" - @sed -n -e 's/^\([A-Za-z][A-Za-z0-9_]\+\)().*/\1/p' ${bin_TARGETS} |sort -uo "$(CURDIR)/.shell-funcs" + @sed -n -e 's/^## \([^[:space:]]\+\)$$/\1/p' ${mandocs_TARGETS} |sort -uo "$(CURDIR)/.shell-funcs-documented" + @sed -n -e 's/^\([A-Za-z][A-Za-z0-9_]\+\)().*/\1/p' ${bin_TARGETS} |sort -uo "$(CURDIR)/.shell-funcs" @comm -13 \ "$(CURDIR)/.shell-funcs-documented" \ "$(CURDIR)/.shell-funcs" \ diff --git a/README.md b/README.md index 3dae6fc..8fb4ef0 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Install dependencies: ## Documentation -See [docs](docs/libshell.md) +See [docs](mans/libshell.scd) ## LICENSE diff --git a/docs/.template.md b/mans/.template.scd similarity index 100% rename from docs/.template.md rename to mans/.template.scd diff --git a/docs/README.md b/mans/README.scd similarity index 100% rename from docs/README.md rename to mans/README.scd diff --git a/docs/libshell.md b/mans/libshell.scd similarity index 100% rename from docs/libshell.md rename to mans/libshell.scd diff --git a/docs/shell-args.md b/mans/shell-args.scd similarity index 100% rename from docs/shell-args.md rename to mans/shell-args.scd diff --git a/docs/shell-cmdline.md b/mans/shell-cmdline.scd similarity index 100% rename from docs/shell-cmdline.md rename to mans/shell-cmdline.scd diff --git a/docs/shell-config.md b/mans/shell-config.scd similarity index 100% rename from docs/shell-config.md rename to mans/shell-config.scd diff --git a/docs/shell-error.md b/mans/shell-error.scd similarity index 100% rename from docs/shell-error.md rename to mans/shell-error.scd diff --git a/docs/shell-getopt.md b/mans/shell-getopt.scd similarity index 100% rename from docs/shell-getopt.md rename to mans/shell-getopt.scd diff --git a/docs/shell-git-config.md b/mans/shell-git-config.scd similarity index 100% rename from docs/shell-git-config.md rename to mans/shell-git-config.scd diff --git a/docs/shell-ini-config.md b/mans/shell-ini-config.scd similarity index 100% rename from docs/shell-ini-config.md rename to mans/shell-ini-config.scd diff --git a/docs/shell-ip-address.md b/mans/shell-ip-address.scd similarity index 100% rename from docs/shell-ip-address.md rename to mans/shell-ip-address.scd diff --git a/docs/shell-json.md b/mans/shell-json.scd similarity index 100% rename from docs/shell-json.md rename to mans/shell-json.scd diff --git a/docs/shell-locks.md b/mans/shell-locks.scd similarity index 100% rename from docs/shell-locks.md rename to mans/shell-locks.scd diff --git a/docs/shell-mail-address.md b/mans/shell-mail-address.scd similarity index 100% rename from docs/shell-mail-address.md rename to mans/shell-mail-address.scd diff --git a/docs/shell-process.md b/mans/shell-process.scd similarity index 100% rename from docs/shell-process.md rename to mans/shell-process.scd diff --git a/docs/shell-quote.md b/mans/shell-quote.scd similarity index 100% rename from docs/shell-quote.md rename to mans/shell-quote.scd diff --git a/docs/shell-run.md b/mans/shell-run.scd similarity index 100% rename from docs/shell-run.md rename to mans/shell-run.scd diff --git a/docs/shell-signal.md b/mans/shell-signal.scd similarity index 100% rename from docs/shell-signal.md rename to mans/shell-signal.scd diff --git a/docs/shell-source.md b/mans/shell-source.scd similarity index 100% rename from docs/shell-source.md rename to mans/shell-source.scd diff --git a/docs/shell-string.md b/mans/shell-string.scd similarity index 100% rename from docs/shell-string.md rename to mans/shell-string.scd diff --git a/docs/shell-temp.md b/mans/shell-temp.scd similarity index 100% rename from docs/shell-temp.md rename to mans/shell-temp.scd diff --git a/docs/shell-terminfo.md b/mans/shell-terminfo.scd similarity index 100% rename from docs/shell-terminfo.md rename to mans/shell-terminfo.scd diff --git a/docs/shell-unittest.md b/mans/shell-unittest.scd similarity index 100% rename from docs/shell-unittest.md rename to mans/shell-unittest.scd diff --git a/docs/shell-var.md b/mans/shell-var.scd similarity index 100% rename from docs/shell-var.md rename to mans/shell-var.scd From ae028335b4f08415393a6336b93d6dacabc5dab2 Mon Sep 17 00:00:00 2001 From: Alexey Gladkov Date: Tue, 26 Aug 2025 12:03:22 +0200 Subject: [PATCH 6/8] Add doc about max int64_t values The use of maximum values varies across different shell implementations. I want to document the current behavior so that there are fewer questions about the implementation of functions. Signed-off-by: Alexey Gladkov --- Documentation/int64_max.md | 184 +++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 Documentation/int64_max.md diff --git a/Documentation/int64_max.md b/Documentation/int64_max.md new file mode 100644 index 0000000..3f1aff5 --- /dev/null +++ b/Documentation/int64_max.md @@ -0,0 +1,184 @@ +# Maximum Int64 Number + +Many shell implementations support 64-bit numbers, but there is a problem with +parsing their maximum values. Some shell implementations handle this +differently. + +It is notable that the behavior differs between dash and ash from busybox. + +## Agenda + +* zsh - zsh (5.9) - UNIX Shell similar to the Korn shell (https://www.zsh.org/) +* dash - dash (0.5.12) - Debian Almquist Shell (https://git.kernel.org/pub/scm/utils/dash/dash.git) +* bash - bash (5.3_p3) - GNU Bourne-Again Shell (https://git.savannah.gnu.org/cgit/bash.git) +* ksh - loksh (7.7) - Linux port of OpenBSD's ksh (https://github.com/dimkr/loksh/) +* lksh - mksh (59c) - MirBSD Korn Shell (https://mbsd.evolvis.org/mksh.htm) +* ash - busybox (1.36.1) - Tiny utilities for small and embedded systems (https://busybox.net/) + +## Value Calculation + +```sh +$ for sh in zsh dash bash ksh lksh "busybox ash"; do $sh -c 'printf "OUT: %4s: %d\n" "$0" $(( 1 << 62 ))'; done +OUT: zsh: 4611686018427387904 +OUT: dash: 4611686018427387904 +OUT: bash: 4611686018427387904 +OUT: ksh: 4611686018427387904 +OUT: lksh: 4611686018427387904 +OUT: ash: 4611686018427387904 + +$ for sh in zsh dash bash ksh lksh "busybox ash"; do $sh -c 'printf "OUT: %4s: %s\n" "$0" $(( 1 << 63 ))'; done +OUT: zsh: -9223372036854775808 +OUT: dash: -9223372036854775808 +OUT: bash: -9223372036854775808 +OUT: ksh: -9223372036854775808 +OUT: lksh: -9223372036854775808 +OUT: ash: -9223372036854775808 + +$ for sh in zsh dash bash ksh lksh "busybox ash"; do $sh -c 'printf "OUT: %4s: %x\n" "$0" $(( 1 << 63 ))'; done +zsh:1: number truncated after 18 digits: 9223372036854775808 +OUT: zsh: f333333333333334 +OUT: dash: 8000000000000000 +OUT: bash: 8000000000000000 +OUT: ksh: 8000000000000000 +OUT: lksh: 8000000000000000 +OUT: ash: 8000000000000000 +``` + +```sh +$ for sh in zsh dash bash ksh lksh "busybox ash"; do $sh -c 'printf "OUT: %4s: %s\n" "$0" $(( 0x7fffffffffffffff + 1 ))'; done +OUT: zsh: -9223372036854775808 +OUT: dash: -9223372036854775808 +OUT: bash: -9223372036854775808 +OUT: ksh: -9223372036854775808 +OUT: lksh: -9223372036854775808 +OUT: ash: -9223372036854775808 + +$ for sh in zsh dash bash ksh lksh "busybox ash"; do $sh -c 'printf "OUT: %4s: %x\n" "$0" $(( 0x7fffffffffffffff + 1 ))'; done +zsh:1: number truncated after 18 digits: 9223372036854775808 +OUT: zsh: f333333333333334 +OUT: dash: 8000000000000000 +OUT: bash: 8000000000000000 +OUT: ksh: 8000000000000000 +OUT: lksh: 8000000000000000 +OUT: ash: 8000000000000000 + +$ for sh in zsh dash bash ksh lksh "busybox ash"; do $sh -c 'printf "OUT: %4s: %x\n" "$0" $(( 0x7fffffffffffffff + 2 ))'; done +OUT: zsh: 8000000000000001 +OUT: dash: 8000000000000001 +OUT: bash: 8000000000000001 +OUT: ksh: 8000000000000001 +OUT: lksh: 8000000000000001 +OUT: ash: 8000000000000001 + +$ for sh in zsh dash bash ksh lksh "busybox ash"; do $sh -c 'printf "OUT: %4s: %x\n" "$0" $(( 0x7fffffffffffffff + 3 ))'; done +OUT: zsh: 8000000000000002 +OUT: dash: 8000000000000002 +OUT: bash: 8000000000000002 +OUT: ksh: 8000000000000002 +OUT: lksh: 8000000000000002 +OUT: ash: 8000000000000002 + +$ for sh in zsh dash bash ksh lksh "busybox ash"; do $sh -c 'printf "OUT: %4s: %x\n" "$0" $(( 0x7fffffffffffffff + 0x7fffffffffffffff + 1 ))'; done +OUT: zsh: ffffffffffffffff +OUT: dash: ffffffffffffffff +OUT: bash: ffffffffffffffff +OUT: ksh: ffffffffffffffff +OUT: lksh: ffffffffffffffff +OUT: ash: ffffffffffffffff +``` + +There are anomalies in parsing large numbers: + +```sh +$ for sh in zsh dash bash ksh lksh "busybox ash"; do $sh -c 'printf "OUT: %4s: %x\n" "$0" $(( 0xffffffffffffffff ))'; done +zsh:1: number truncated after 15 digits: ffffffffffffffff +OUT: zsh: fffffffffffffff +OUT: dash: 7fffffffffffffff +OUT: bash: ffffffffffffffff +OUT: ksh: ffffffffffffffff +OUT: lksh: ffffffffffffffff +OUT: ash: ffffffffffffffff + +$ for sh in zsh dash bash ksh lksh "busybox ash"; do $sh -c 'printf "OUT: %4s: %s\n" "$0" $(( 0x8000000000000000 ))'; done +zsh:1: number truncated after 15 digits: 8000000000000000 +OUT: zsh: 576460752303423488 +OUT: dash: 9223372036854775807 +OUT: bash: -9223372036854775808 +OUT: ksh: -9223372036854775808 +OUT: lksh: -9223372036854775808 +OUT: ash: -9223372036854775808 + +$ for sh in zsh dash bash ksh lksh "busybox ash"; do $sh -c 'printf "OUT: %4s: %d\n" "$0" $(( -9223372036854775808 ))'; done +zsh:1: number truncated after 18 digits: 9223372036854775808 +OUT: zsh: -922337203685477580 +OUT: dash: -9223372036854775807 +OUT: bash: -9223372036854775808 +OUT: ksh: -9223372036854775808 +OUT: lksh: -9223372036854775808 +OUT: ash: -9223372036854775808 +``` + +## Variables + +And one more thing. If you put some value in the variable, then in further +calculations, parsing will happen again, which might have an extra effect. + +```sh +$ for sh in zsh dash bash ksh lksh "busybox ash"; do $sh -c 'v=$(( 1 << 62 )); printf "OUT: %4s: %x\n" $0 $(( $v ))'; done +OUT: zsh: 4000000000000000 +OUT: dash: 4000000000000000 +OUT: bash: 4000000000000000 +OUT: ksh: 4000000000000000 +OUT: lksh: 4000000000000000 +OUT: ash: 4000000000000000 + +$ for sh in zsh dash bash ksh lksh "busybox ash"; do $sh -c 'v=0x7fffffffffffffff; printf "OUT: %4s: %x\n" $0 $(( $v ))'; done +OUT: zsh: 7fffffffffffffff +OUT: dash: 7fffffffffffffff +OUT: bash: 7fffffffffffffff +OUT: ksh: 7fffffffffffffff +OUT: lksh: 7fffffffffffffff +OUT: ash: 7fffffffffffffff + +$ for sh in zsh dash bash ksh lksh "busybox ash"; do $sh -c 'v=$(( 1 << 63 )); printf "OUT: %4s: %x\n" $0 $(( $v ))'; done +zsh:1: number truncated after 18 digits: 9223372036854775808 +OUT: zsh: f333333333333334 +OUT: dash: 8000000000000001 +OUT: bash: 8000000000000000 +OUT: ksh: 8000000000000000 +OUT: lksh: 8000000000000000 +OUT: ash: 8000000000000000 + +$ for sh in zsh dash bash ksh lksh "busybox ash"; do $sh -c 'v=0x8000000000000000; printf "OUT: %4s: %x\n" $0 $(( $v ))'; done +zsh:1: number truncated after 15 digits: 8000000000000000 +OUT: zsh: 800000000000000 +OUT: dash: 7fffffffffffffff +OUT: bash: 8000000000000000 +OUT: ksh: 8000000000000000 +OUT: lksh: 8000000000000000 +OUT: ash: 8000000000000000 + +$ for sh in zsh dash bash ksh lksh "busybox ash"; do $sh -c 'v=$(( 0x7fffffffffffffff + 1 )); printf "OUT: %4s: %x\n" $0 $(( $v ))'; done +zsh:1: number truncated after 18 digits: 9223372036854775808 +OUT: zsh: f333333333333334 +OUT: dash: 8000000000000001 +OUT: bash: 8000000000000000 +OUT: ksh: 8000000000000000 +OUT: lksh: 8000000000000000 +OUT: ash: 8000000000000000 + +for sh in zsh dash bash ksh lksh "busybox ash"; do $sh -c 'v="(0x7fffffffffffffff + 1)"; printf "OUT: %4s: %x\n" $0 $(( $v ))'; done +zsh:1: number truncated after 18 digits: 9223372036854775808 +OUT: zsh: f333333333333334 +OUT: dash: 8000000000000000 +OUT: bash: 8000000000000000 +OUT: ksh: 8000000000000000 +OUT: lksh: 8000000000000000 +OUT: ash: 8000000000000000 +``` + +It seems that if one wants to have the value `1 << 63` in a variable, it is more +correct to keep it as "(0x7fffffffffffffff + 1)" string. + +-- +Rgrds, legion From dfdf6c0a915130ea113273765ac12bd786a9151d Mon Sep 17 00:00:00 2001 From: Gleb Fotengauer-Malinovskiy Date: Wed, 5 Nov 2025 05:00:00 +0000 Subject: [PATCH 7/8] shell-quote (quote_sed_regexp_variable): fix names of variables Signed-off-by: Gleb Fotengauer-Malinovskiy --- shell-quote | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shell-quote b/shell-quote index 4384056..c756e49 100644 --- a/shell-quote +++ b/shell-quote @@ -17,15 +17,15 @@ __included_shell_quote=1 ### sed "s/$var_pattern/$var_replace/" quote_sed_regexp_variable() { - local __quote_set_regexp_variable_var __quote_set_regexp_variable_out - __quote_set_regexp_variable_var="$1"; shift - __quote_set_regexp_variable_out="$*" - if [ -z "${__quote_set_regexp_variable_out##*[\[\].*&^\$\\\\/]*}" ]; then - __quote_set_regexp_variable_out="$(printf %s "$__quote_set_regexp_variable_out" | + local __quote_sed_regexp_variable_var __quote_sed_regexp_variable_out + __quote_sed_regexp_variable_var="$1"; shift + __quote_sed_regexp_variable_out="$*" + if [ -z "${__quote_sed_regexp_variable_out##*[\[\].*&^\$\\\\/]*}" ]; then + __quote_sed_regexp_variable_out="$(printf %s "$__quote_sed_regexp_variable_out" | sed -e 's/[].*&^$[\/]/\\&/g')" || return 1 fi - eval "$__quote_set_regexp_variable_var=\"\$__quote_set_regexp_variable_out\"" + eval "$__quote_sed_regexp_variable_var=\"\$__quote_sed_regexp_variable_out\"" } ### Quote given arguments for sed basic regular expression. From 4f21d4cbdc9c1b54eb5bce43bc7a68612e5925b4 Mon Sep 17 00:00:00 2001 From: Gleb Fotengauer-Malinovskiy Date: Wed, 5 Nov 2025 05:00:00 +0000 Subject: [PATCH 8/8] shell-quote: add quote_grep_regexp and quote_grep_regexp_variable functions For years quote_sed_* functions worked fine with grep, but GNU grep since it's 3.11 release started issuing warnings like this: grep: warning: stray \ before / or grep: warning: stray \ before & Signed-off-by: Gleb Fotengauer-Malinovskiy --- mans/shell-quote.scd | 20 +++++++++++++- shell-quote | 46 ++++++++++++++++++++++++++------ tests/quote_grep_regexp | 33 +++++++++++++++++++++++ tests/quote_grep_regexp_variable | 37 +++++++++++++++++++++++++ tests/runtests | 4 +-- 5 files changed, 129 insertions(+), 11 deletions(-) create mode 100644 tests/quote_grep_regexp create mode 100644 tests/quote_grep_regexp_variable diff --git a/mans/shell-quote.scd b/mans/shell-quote.scd index fc9acd4..be2c949 100644 --- a/mans/shell-quote.scd +++ b/mans/shell-quote.scd @@ -11,10 +11,12 @@ quote_sed_regexp, quote_sed_regexp_variable, quote_shell, quote_shell_args, quot - quote_shell - quote_shell_args - quote_shell_variable +- quote_grep_regexp_variable +- quote_grep_regexp # DESCRIPTION Collection of functions to quote variables. The goal of functions to provide quoted values for -_sed_, _sh_ or for safe _eval_ inside script. +_sed_, _grep_, _sh_ or for safe _eval_ inside script. ## quote_sed_regexp Function quotes given arguments for sed basic regular expression. @@ -32,6 +34,22 @@ quote_sed_regexp_variable var_replace "$replace" sed "s/$var_pattern/$var_replace/" ``` +## quote_grep_regexp +Function quotes given arguments for grep basic regular expression. +Usage example: +``` +grep "s/$(quote_grep_regexp "$var_pattern")/$(quote_grep_regexp "$var_replacement")/" +``` + +## quote_grep_regexp_variable +Function quotes argument for grep basic regular expression and stores result into variable. +Usage example: +``` +quote_grep_regexp_variable var_pattern "$pattern" +quote_grep_regexp_variable var_replace "$replace" +grep "s/$var_pattern/$var_replace/" +``` + ## quote_shell Function quotes argument for shell. Usage example: diff --git a/shell-quote b/shell-quote index c756e49..8c75a73 100644 --- a/shell-quote +++ b/shell-quote @@ -10,6 +10,22 @@ __included_shell_quote=1 . shell-error . shell-string +__quote_basic_regexp_variable() +{ + local __quote_basic_regexp_variable_var __quote_basic_regexp_variable_out + local __quote_basic_regexp_variable_pattren __quote_basic_regexp_variable_sed_script + __quote_basic_regexp_variable_var="$1"; shift + __quote_basic_regexp_variable_pattern="$1"; shift + __quote_basic_regexp_variable_sed_script="$1"; shift + __quote_basic_regexp_variable_out="$*" + if [ -z "${__quote_basic_regexp_variable_out##*$__quote_basic_regexp_variable_pattern*}" ]; then + __quote_basic_regexp_variable_out="$(printf %s "$__quote_basic_regexp_variable_out" | + sed -e "$__quote_basic_regexp_variable_sed_script")" || + return 1 + fi + eval "$__quote_basic_regexp_variable_var=\"\$__quote_basic_regexp_variable_out\"" +} + ### Quote argument for sed basic regular expression and store result into variable. ### Usage example: ### quote_sed_regexp_variable var_pattern "$pattern" @@ -17,15 +33,9 @@ __included_shell_quote=1 ### sed "s/$var_pattern/$var_replace/" quote_sed_regexp_variable() { - local __quote_sed_regexp_variable_var __quote_sed_regexp_variable_out + local __quote_sed_regexp_variable_var __quote_sed_regexp_variable_var="$1"; shift - __quote_sed_regexp_variable_out="$*" - if [ -z "${__quote_sed_regexp_variable_out##*[\[\].*&^\$\\\\/]*}" ]; then - __quote_sed_regexp_variable_out="$(printf %s "$__quote_sed_regexp_variable_out" | - sed -e 's/[].*&^$[\/]/\\&/g')" || - return 1 - fi - eval "$__quote_sed_regexp_variable_var=\"\$__quote_sed_regexp_variable_out\"" + __quote_basic_regexp_variable "$__quote_sed_regexp_variable_var" '[\[\].*&^\$\\\\/]' 's/[].*&^$[\/]/\\&/g' "$*" } ### Quote given arguments for sed basic regular expression. @@ -37,6 +47,26 @@ quote_sed_regexp() printf %s "$result" } +### Quote argument for grep basic regular expression and store result into variable. +### Usage example: +### quote_grep_regexp_variable var_pattern "$pattern" +### grep "$var_pattern" +quote_grep_regexp_variable() +{ + local __quote_grep_regexp_variable_var + __quote_grep_regexp_variable_var="$1"; shift + __quote_basic_regexp_variable "$__quote_grep_regexp_variable_var" '[\[\].*^\$\\]' 's/[].*^$[\]/\\&/g' "$*" +} + +### Quote given arguments for grep basic regular expression. +### Usage example: grep "$(quote_grep_regexp "$var_pattern")" +quote_grep_regexp() +{ + local result + quote_grep_regexp_variable result "$@" + printf %s "$result" +} + ### Quote argument for shell and store result into variable. ### Usage example: ### quote_shell_variable var_name "$var_value" diff --git a/tests/quote_grep_regexp b/tests/quote_grep_regexp new file mode 100644 index 0000000..385b57a --- /dev/null +++ b/tests/quote_grep_regexp @@ -0,0 +1,33 @@ +#!/bin/ash -efu + +quote_grep_regexp_test1() { # UnitTest + . ../shell-quote + local string='.*' + local regexp="$(quote_grep_regexp "$string")" + local result="$(printf '%s\n' "$string passed" |grep "$regexp")" + assertEquals "$string" "$string passed" "$result" +} + +quote_grep_regexp_test2() { # UnitTest + . ../shell-quote + local string='[[:space:]]' + local regexp="$(quote_grep_regexp "$string")" + local result="$(printf '%s\n' "test $string" |grep "$regexp")" + assertEquals "$string" "test $string" "$result" +} + +quote_grep_regexp_test3() { # UnitTest + . ../shell-quote + local string='t{1,3}' + local regexp="$(quote_grep_regexp "$string")" + local result="$(printf '%s\n' "test$string passed" |grep "$regexp")" + assertEquals "$string" "test$string passed" "$result" +} + +quote_grep_regexp_test4() { # UnitTest + . ../shell-quote + local string='&\1' + local regexp="$(quote_grep_regexp "$string")" + local result="$(printf '%s\n' "test &\1 passed" |grep "$regexp")" #" + assertEquals "$result" 'test &\1 passed' "$result" +} diff --git a/tests/quote_grep_regexp_variable b/tests/quote_grep_regexp_variable new file mode 100644 index 0000000..a9a2d71 --- /dev/null +++ b/tests/quote_grep_regexp_variable @@ -0,0 +1,37 @@ +#!/bin/ash -efu + +quote_grep_regexp_variable_test1() { # UnitTest + . ../shell-quote + local string='.*' + local regexp + quote_grep_regexp_variable regexp "$string" + local result="$(printf '%s\n' "$string passed" |sed -e "s/^$regexp/test/")" + assertEquals "$string" "$result" 'test passed' +} + +quote_grep_regexp_variable_test2() { # UnitTest + . ../shell-quote + local string='[[:space:]]' + local regexp + quote_grep_regexp_variable regexp "$string" + local result="$(printf '%s\n' "test $string" |sed -e "s/$regexp/passed/")" + assertEquals "$string" "$result" 'test passed' +} + +quote_grep_regexp_variable_test3() { # UnitTest + . ../shell-quote + local string='t{1,3}' + local regexp + quote_grep_regexp_variable regexp "$string" + local result="$(printf '%s\n' "test$string passed" |sed -e "s/$regexp//")" + assertEquals "$string" "$result" 'test passed' +} + +quote_grep_regexp_variable_test4() { # UnitTest + . ../shell-quote + local string='&\1' + local regexp + quote_grep_regexp_variable regexp "$string" + local result="$(printf '%s\n' "test &\\1 passed" |grep "$regexp")" + assertEquals "$string" "$result" 'test &\1 passed' +} diff --git a/tests/runtests b/tests/runtests index 9b8fc52..ed7058f 100755 --- a/tests/runtests +++ b/tests/runtests @@ -26,9 +26,9 @@ tearDownTests() { for s in \ check-string-performance \ - quote_sed_regexp quote_shell string_quote_remove \ + quote_sed_regexp quote_shell quote_grep_regexp string_quote_remove \ opt_check_dir opt_check_number opt_check_read \ - quote_sed_regexp_variable quote_shell_variable \ + quote_sed_regexp_variable quote_shell_variable quote_grep_regexp_variable \ getopt getsubopt getopts signal \ fatal message verbose \ quote_shell_args \