Preserve escaped dollar literals

This commit is contained in:
Sami Samhuri 2026-02-02 20:43:43 -08:00
parent 52ef7cb7f5
commit 5172c60910
No known key found for this signature in database

View file

@ -3,6 +3,7 @@ require "shellwords"
module Shell module Shell
class WordExpander class WordExpander
ENV_VAR_REGEX = /\$(?:\{([^}]+)\}|(\w+)\b)/ ENV_VAR_REGEX = /\$(?:\{([^}]+)\}|(\w+)\b)/
ESCAPED_DOLLAR = "\u0001"
# Splits the given line into multiple words, performing the following transformations: # Splits the given line into multiple words, performing the following transformations:
# #
@ -11,13 +12,15 @@ module Shell
# - Tilde expansion, which means that ~ is expanded to $HOME # - Tilde expansion, which means that ~ is expanded to $HOME
# - Glob expansion on files and directories # - Glob expansion on files and directories
def expand(line) def expand(line)
shellsplit(line) protected_line = protect_escaped_dollars(line)
shellsplit(protected_line)
.map do |word| .map do |word|
word word
.gsub(ENV_VAR_REGEX) do .gsub(ENV_VAR_REGEX) do
name = Regexp.last_match(2) || Regexp.last_match(1) name = Regexp.last_match(2) || Regexp.last_match(1)
ENV.fetch(name) ENV.fetch(name)
end end
.tr(ESCAPED_DOLLAR, "$")
# TODO: expand globs # TODO: expand globs
end end
end end
@ -94,5 +97,29 @@ module Shell
def expand_globs(word) def expand_globs(word)
Dir.glob(word) Dir.glob(word)
end end
def protect_escaped_dollars(line)
output = +""
i = 0
while i < line.length
if line.getbyte(i) == "\\".ord
j = i + 1
j += 1 while j < line.length && line.getbyte(j) == "\\".ord
count = j - i
if j < line.length && line.getbyte(j) == "$".ord && count.odd?
output << ("\\" * (count - 1))
output << ESCAPED_DOLLAR
i = j + 1
else
output << ("\\" * count)
i = j
end
else
output << line[i]
i += 1
end
end
output
end
end end
end end