From 9449392734f01a55718d8ca4c56e8da7cbc48155 Mon Sep 17 00:00:00 2001 From: Andrew Leng Date: Fri, 5 Dec 2025 00:18:23 -0500 Subject: [PATCH 1/3] fix: enable nullglob to avoid glob expansion errors when no node versions installed Fixes #3727 When no node versions are installed, nvm ls produces errors like: `find: /path/.nvm/versions/node/*: No such file or directory` This happens because bash expands `*` literally when nullglob is not set and the directory is empty. Solution: - For bash: temporarily enable nullglob before the find command - For other shells (sh, dash, zsh): suppress stderr (2>/dev/null) - Restore nullglob to its previous state after the command completes - Uses BASH_VERSION check to detect bash vs other shells --- nvm.sh | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/nvm.sh b/nvm.sh index 01013b822f..2456669dc8 100755 --- a/nvm.sh +++ b/nvm.sh @@ -1532,7 +1532,20 @@ nvm_ls() { SEARCH_PATTERN="$(nvm_echo "${PATTERN}" | command sed 's#\.#\\\.#g;')" fi if [ -n "${NVM_DIRS_TO_SEARCH1}${NVM_DIRS_TO_SEARCH2}${NVM_DIRS_TO_SEARCH3}" ]; then - VERSIONS="$(command find "${NVM_DIRS_TO_SEARCH1}"/* "${NVM_DIRS_TO_SEARCH2}"/* "${NVM_DIRS_TO_SEARCH3}"/* -name . -o -type d -prune -o -path "${PATTERN}*" \ + # Enable nullglob (bash-only) to avoid errors when directories are empty + # For other shells, stderr is suppressed to handle the case gracefully + # See: https://github.com/nvm-sh/nvm/issues/3727 + if [ -n "${BASH_VERSION-}" ]; then + # shellcheck disable=SC3044 + if shopt -q nullglob 2>/dev/null; then + NVM_NULLGLOB_WAS_SET=true + else + NVM_NULLGLOB_WAS_SET=false + # shellcheck disable=SC3044 + shopt -s nullglob 2>/dev/null + fi + fi + VERSIONS="$(command find "${NVM_DIRS_TO_SEARCH1}"/* "${NVM_DIRS_TO_SEARCH2}"/* "${NVM_DIRS_TO_SEARCH3}"/* -name . -o -type d -prune -o -path "${PATTERN}*" 2>/dev/null \ | command sed -e " s#${NVM_VERSION_DIR_IOJS}/#versions/${NVM_IOJS_PREFIX}/#; s#^${NVM_DIR}/##; @@ -1547,6 +1560,11 @@ nvm_ls() { | command sed -e 's#\(.*\)\.\([^\.]\{1,\}\)$#\2-\1#;' \ -e "s#^${NVM_NODE_PREFIX}-##;" \ )" + # Restore nullglob to its previous state (bash-only) + if [ -n "${BASH_VERSION-}" ] && [ "${NVM_NULLGLOB_WAS_SET-}" = 'false' ]; then + # shellcheck disable=SC3044 + shopt -u nullglob 2>/dev/null + fi fi fi From fc0546813d1c58ba042596b723e8f2f310922596 Mon Sep 17 00:00:00 2001 From: Andrew Leng Date: Fri, 5 Dec 2025 15:03:39 -0500 Subject: [PATCH 2/3] refactor: use subshell to isolate nullglob changes Move nullglob enabling into the command substitution subshell so that the shell option change cannot persist to the parent shell, even if something exits early. This is a safer approach than save/restore. The subshell automatically cleans up when it exits, making it impossible for nullglob to leak into the user's shell environment. --- nvm.sh | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/nvm.sh b/nvm.sh index 2456669dc8..0cfc16bb26 100755 --- a/nvm.sh +++ b/nvm.sh @@ -1532,20 +1532,12 @@ nvm_ls() { SEARCH_PATTERN="$(nvm_echo "${PATTERN}" | command sed 's#\.#\\\.#g;')" fi if [ -n "${NVM_DIRS_TO_SEARCH1}${NVM_DIRS_TO_SEARCH2}${NVM_DIRS_TO_SEARCH3}" ]; then - # Enable nullglob (bash-only) to avoid errors when directories are empty - # For other shells, stderr is suppressed to handle the case gracefully + # Use subshell to isolate nullglob changes (prevents them from persisting) # See: https://github.com/nvm-sh/nvm/issues/3727 - if [ -n "${BASH_VERSION-}" ]; then + VERSIONS="$( # shellcheck disable=SC3044 - if shopt -q nullglob 2>/dev/null; then - NVM_NULLGLOB_WAS_SET=true - else - NVM_NULLGLOB_WAS_SET=false - # shellcheck disable=SC3044 - shopt -s nullglob 2>/dev/null - fi - fi - VERSIONS="$(command find "${NVM_DIRS_TO_SEARCH1}"/* "${NVM_DIRS_TO_SEARCH2}"/* "${NVM_DIRS_TO_SEARCH3}"/* -name . -o -type d -prune -o -path "${PATTERN}*" 2>/dev/null \ + [ -n "${BASH_VERSION-}" ] && shopt -s nullglob + command find "${NVM_DIRS_TO_SEARCH1}"/* "${NVM_DIRS_TO_SEARCH2}"/* "${NVM_DIRS_TO_SEARCH3}"/* -name . -o -type d -prune -o -path "${PATTERN}*" 2>/dev/null \ | command sed -e " s#${NVM_VERSION_DIR_IOJS}/#versions/${NVM_IOJS_PREFIX}/#; s#^${NVM_DIR}/##; @@ -1560,11 +1552,6 @@ nvm_ls() { | command sed -e 's#\(.*\)\.\([^\.]\{1,\}\)$#\2-\1#;' \ -e "s#^${NVM_NODE_PREFIX}-##;" \ )" - # Restore nullglob to its previous state (bash-only) - if [ -n "${BASH_VERSION-}" ] && [ "${NVM_NULLGLOB_WAS_SET-}" = 'false' ]; then - # shellcheck disable=SC3044 - shopt -u nullglob 2>/dev/null - fi fi fi From dc76607235a0a1b882534e709b4333e93739e607 Mon Sep 17 00:00:00 2001 From: Andrew Leng Date: Fri, 5 Dec 2025 15:27:07 -0500 Subject: [PATCH 3/3] test: add regression test for nullglob issue #3727 Adds a test that explicitly disables nullglob and runs nvm ls with an empty versions directory to ensure the fix prevents regressions. The test verifies: - nvm ls does not produce 'No such file or directory' errors - nvm ls exits with code 0 when no versions are installed --- ...th empty versions directory does not error | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 test/fast/Listing versions/Running 'nvm ls' with empty versions directory does not error diff --git a/test/fast/Listing versions/Running 'nvm ls' with empty versions directory does not error b/test/fast/Listing versions/Running 'nvm ls' with empty versions directory does not error new file mode 100644 index 0000000000..638e1aded0 --- /dev/null +++ b/test/fast/Listing versions/Running 'nvm ls' with empty versions directory does not error @@ -0,0 +1,35 @@ +#!/bin/sh +# Regression test for issue #3727 +# This test ensures nvm ls works correctly when: +# 1. nullglob is NOT set (default bash behavior) +# 2. No node versions are installed (empty versions directory) +# Previously, this would cause "find: ... No such file or directory" errors + +die () { echo "$@" ; exit 1; } + +\. ../../../nvm.sh +\. ../../common.sh + +# Ensure nullglob is OFF (to test the fix works without it) +# This simulates a user who hasn't set nullglob in their shell +if [ -n "${BASH_VERSION-}" ]; then + shopt -u nullglob 2>/dev/null || true +fi + +# Make sure no fake versions exist - empty dir triggers the bug +rm -rf "${NVM_DIR}/versions/node/"* 2>/dev/null +mkdir -p "${NVM_DIR}/versions/node" + +# Run nvm ls and capture stderr +OUTPUT="$(nvm ls 2>&1)" +EXIT_CODE=$? + +# Should not contain "No such file or directory" error +if echo "$OUTPUT" | grep -q "No such file or directory"; then + die "FAIL: nvm ls produced 'No such file or directory' error with empty versions directory" +fi + +# Should succeed (exit 0) +[ "$EXIT_CODE" = "0" ] || die "FAIL: nvm ls exited with code $EXIT_CODE, expected 0" + +echo "PASS: nvm ls works with empty versions directory and nullglob disabled"