summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/spack/spack/test/util/environment.py18
-rw-r--r--lib/spack/spack/util/environment.py27
2 files changed, 42 insertions, 3 deletions
diff --git a/lib/spack/spack/test/util/environment.py b/lib/spack/spack/test/util/environment.py
index 417dd2e337..eff07cbee9 100644
--- a/lib/spack/spack/test/util/environment.py
+++ b/lib/spack/spack/test/util/environment.py
@@ -151,3 +151,21 @@ def test_reverse_environment_modifications(working_env):
start_env.pop("UNSET")
assert os.environ == start_env
+
+
+def test_escape_double_quotes_in_shell_modifications():
+ to_validate = envutil.EnvironmentModifications()
+
+ to_validate.set("VAR", "$PATH")
+ to_validate.append_path("VAR", "$ANOTHER_PATH")
+
+ to_validate.set("QUOTED_VAR", '"MY_VAL"')
+
+ cmds = to_validate.shell_modifications()
+
+ if sys.platform != "win32":
+ assert 'export VAR="$PATH:$ANOTHER_PATH"' in cmds
+ assert r'export QUOTED_VAR="\"MY_VAL\""' in cmds
+ else:
+ assert "export VAR=$PATH;$ANOTHER_PATH" in cmds
+ assert r'export QUOTED_VAR="MY_VAL"' in cmds
diff --git a/lib/spack/spack/util/environment.py b/lib/spack/spack/util/environment.py
index 6be232bf42..64082ff313 100644
--- a/lib/spack/spack/util/environment.py
+++ b/lib/spack/spack/util/environment.py
@@ -12,7 +12,6 @@ import os.path
import pickle
import platform
import re
-import shlex
import socket
import sys
from typing import Any, Callable, Dict, List, MutableMapping, Optional, Tuple, Union
@@ -64,6 +63,26 @@ Path = str
ModificationList = List[Union["NameModifier", "NameValueModifier"]]
+_find_unsafe = re.compile(r"[^\w@%+=:,./-]", re.ASCII).search
+
+
+def double_quote_escape(s):
+ """Return a shell-escaped version of the string *s*.
+
+ This is similar to how shlex.quote works, but it escapes with double quotes
+ instead of single quotes, to allow environment variable expansion within
+ quoted strings.
+ """
+ if not s:
+ return '""'
+ if _find_unsafe(s) is None:
+ return s
+
+ # use double quotes, and escape double quotes in the string
+ # the string $"b is then quoted as "$\"b"
+ return '"' + s.replace('"', r"\"") + '"'
+
+
def is_system_path(path: Path) -> bool:
"""Returns True if the argument is a system path, False otherwise."""
return bool(path) and (os.path.normpath(path) in SYSTEM_DIRS)
@@ -135,7 +154,7 @@ def _env_var_to_source_line(var: str, val: str) -> str:
fname=BASH_FUNCTION_FINDER.sub(r"\1", var), decl=val
)
else:
- source_line = f"{var}={shlex.quote(val)}; export {var}"
+ source_line = f"{var}={double_quote_escape(val)}; export {var}"
return source_line
@@ -649,7 +668,9 @@ class EnvironmentModifications:
cmds += _SHELL_UNSET_STRINGS[shell].format(name)
else:
if sys.platform != "win32":
- cmd = _SHELL_SET_STRINGS[shell].format(name, shlex.quote(new_env[name]))
+ cmd = _SHELL_SET_STRINGS[shell].format(
+ name, double_quote_escape(new_env[name])
+ )
else:
cmd = _SHELL_SET_STRINGS[shell].format(name, new_env[name])
cmds += cmd