如何使 ruby ShellWords.shellescape 与多字节字符一起使用?
How can I make ruby ShellWords.shellescape work with multibyte characters?
我一直在尝试使用包含来自 Windows 环境变量的多字节字符的参数来调用 exec,但尚未找到可行的解决方案。到目前为止,这是我能够调试的内容。
为简单起见,假设我有一个名为 "Seán" 的目录,我试图将其用作 exec 的参数。如果我只是打电话
exec 'script', "Se\u00E1n".encode("IBM437")
执行的脚本找不到文件,因为 arg 被调整为重音字符丢失。如果我执行以下操作,它会起作用,但这是不好的做法,因为 arg 应该在进入 shell.
之前进行转义
exec "script #{"Se\u00E1n".encode("IBM437")}"
所以我的想法是我会使用 shellescape 来保护 exec 的使用。
require 'shellwords'
exec "script #{"Se\u00E1n".encode("IBM437").shellescape}"
但问题是它对特殊字符进行了转义,因此它看起来像下面这样 - "Se\án"。我弄清楚这是哪里发生的,它来自这个 regular expression.
str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/, "\\\1")
乍一看似乎转义了不在已知良好 shell 字符集中的字符。不幸的是,这个集合不包含特殊字符,所以我 运行 遇到了问题。
我正在寻找的是一个可以进行 shell 转义且不会弄乱特殊字符的正则表达式,这样我就可以在将这些 args 传递给 exec 之前对其进行转义。
TL;DR
转义字符
代码
String.class_eval do
def escapeshell()
# Escape shell special characters
self.gsub!(/[#-&(-*;<>?\[-^`{-~\u00FF]/, '\\[=10=]')
# Escape unbalanced quotes (single and double quotes)
self.gsub!(/(["'])(?:([^"']*(?:(?!)["'][^"']*)*))?/) do
if .nil?
'\' +
else
# and escape quotes inside (e.g. "x'x" or 'y"y')
qt =
qt + .gsub(/["']/, '\\[=10=]') + qt
end
end
self
end
end
# Test it
str = "(dir *.txt & dir \"\some dir\Sè\u00E1ñ*.rb\") | sort /R >Filé.txt 2>&1"
puts 'String:'
puts str
puts "\nEscaped:"
puts str.escapeshell
输出
String:
(dir *.txt & dir "\some dir\Sèáñ*.rb") | sort /R >Filé.txt 2>&1
Escaped:
\(dir \*.txt \& dir "\some dir\Sèáñ\*.rb"\) \| sort /R \>Filé.txt 2\>\&1
描述
元字符
考虑应转义的 shell 个元字符:
# & % ; ` | * ? ~ < > ^ ( ) [ ] { } $ \ \u00FF
我们可以在 character class:
中包含每个字符
[#&%;`|*?~<>^()\[\]{}$\\u00FF]
与以下内容完全相同:
/[#-&(-*;<>?\[-^`{-~\u00FF]/
然后,我们使用 gsub!()
在 class:
中的任何字符前添加一个反斜杠
str.gsub!(/[#-&(-*;<>?\[-^`{-~\u00FF]/, '\\[=15=]')
行情
只有不平衡的引号需要转义。这对于保留命令的参数很重要。使用以下表达式,我们匹配平衡引号:
/(["'])[^"']*(?:(?!)["'][^"']*)*)/
以及不平衡,使最后一部分成为可选的
/(["'])(?:[^"']*(?:(?!)["'][^"']*)*))?/
但我们还需要在另一对中转义引号。这是双引号内的单引号,反之亦然。因此,我们将嵌套另一个 gsub()
以替换引号内匹配的文本 (</code>):</p>
<pre><code>str.gsub!(/(["'])(?:([^"']*(?:(?!)["'][^"']*)*))?/) do
if .nil?
'\' +
else
qt =
qt + .gsub(/["']/, '\\[=18=]') + qt
end
end
正则表达式 /([^A-Za-z0-9_\-.,:\/@\n])/
只处理 ASCII 字母和数字,而不是所有 Unicode 字母。 [^...]
是一个 negated character class,匹配除 class 中指定的所有字符 。因此,所有 Я
、Ц
、Ą
都与该表达式一起删除,因为它们与 [A-Za-z]
.
不匹配
你需要的是添加shorthand classes来排除所有Unicode字母和数字。为了使它更安全,我们可以添加变音符号 class 以保留变音符号:
str.gsub(/([^\p{L}\p{M}\p{N}_.,:\/@\n-])/, "\\\1")
此处,\p{L}
匹配所有 Unicode 基本字母,\p{M}
匹配所有变音符号,\p{N}
匹配任何 Unicode 数字。
请注意,当连字符位于字符 class 的 start/end 处(或在有效范围或 shorthand 字符后 class).
我一直在尝试使用包含来自 Windows 环境变量的多字节字符的参数来调用 exec,但尚未找到可行的解决方案。到目前为止,这是我能够调试的内容。
为简单起见,假设我有一个名为 "Seán" 的目录,我试图将其用作 exec 的参数。如果我只是打电话
exec 'script', "Se\u00E1n".encode("IBM437")
执行的脚本找不到文件,因为 arg 被调整为重音字符丢失。如果我执行以下操作,它会起作用,但这是不好的做法,因为 arg 应该在进入 shell.
之前进行转义exec "script #{"Se\u00E1n".encode("IBM437")}"
所以我的想法是我会使用 shellescape 来保护 exec 的使用。
require 'shellwords'
exec "script #{"Se\u00E1n".encode("IBM437").shellescape}"
但问题是它对特殊字符进行了转义,因此它看起来像下面这样 - "Se\án"。我弄清楚这是哪里发生的,它来自这个 regular expression.
str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/, "\\\1")
乍一看似乎转义了不在已知良好 shell 字符集中的字符。不幸的是,这个集合不包含特殊字符,所以我 运行 遇到了问题。
我正在寻找的是一个可以进行 shell 转义且不会弄乱特殊字符的正则表达式,这样我就可以在将这些 args 传递给 exec 之前对其进行转义。
TL;DR
转义字符
代码
String.class_eval do
def escapeshell()
# Escape shell special characters
self.gsub!(/[#-&(-*;<>?\[-^`{-~\u00FF]/, '\\[=10=]')
# Escape unbalanced quotes (single and double quotes)
self.gsub!(/(["'])(?:([^"']*(?:(?!)["'][^"']*)*))?/) do
if .nil?
'\' +
else
# and escape quotes inside (e.g. "x'x" or 'y"y')
qt =
qt + .gsub(/["']/, '\\[=10=]') + qt
end
end
self
end
end
# Test it
str = "(dir *.txt & dir \"\some dir\Sè\u00E1ñ*.rb\") | sort /R >Filé.txt 2>&1"
puts 'String:'
puts str
puts "\nEscaped:"
puts str.escapeshell
输出
String:
(dir *.txt & dir "\some dir\Sèáñ*.rb") | sort /R >Filé.txt 2>&1
Escaped:
\(dir \*.txt \& dir "\some dir\Sèáñ\*.rb"\) \| sort /R \>Filé.txt 2\>\&1
描述
元字符
考虑应转义的 shell 个元字符:
# & % ; ` | * ? ~ < > ^ ( ) [ ] { } $ \ \u00FF
我们可以在 character class:
中包含每个字符[#&%;`|*?~<>^()\[\]{}$\\u00FF]
与以下内容完全相同:
/[#-&(-*;<>?\[-^`{-~\u00FF]/
然后,我们使用 gsub!()
在 class:
str.gsub!(/[#-&(-*;<>?\[-^`{-~\u00FF]/, '\\[=15=]')
行情
只有不平衡的引号需要转义。这对于保留命令的参数很重要。使用以下表达式,我们匹配平衡引号:
/(["'])[^"']*(?:(?!)["'][^"']*)*)/
以及不平衡,使最后一部分成为可选的
/(["'])(?:[^"']*(?:(?!)["'][^"']*)*))?/
但我们还需要在另一对中转义引号。这是双引号内的单引号,反之亦然。因此,我们将嵌套另一个 gsub()
以替换引号内匹配的文本 (</code>):</p>
<pre><code>str.gsub!(/(["'])(?:([^"']*(?:(?!)["'][^"']*)*))?/) do
if .nil?
'\' +
else
qt =
qt + .gsub(/["']/, '\\[=18=]') + qt
end
end
正则表达式 /([^A-Za-z0-9_\-.,:\/@\n])/
只处理 ASCII 字母和数字,而不是所有 Unicode 字母。 [^...]
是一个 negated character class,匹配除 class 中指定的所有字符 。因此,所有 Я
、Ц
、Ą
都与该表达式一起删除,因为它们与 [A-Za-z]
.
你需要的是添加shorthand classes来排除所有Unicode字母和数字。为了使它更安全,我们可以添加变音符号 class 以保留变音符号:
str.gsub(/([^\p{L}\p{M}\p{N}_.,:\/@\n-])/, "\\\1")
此处,\p{L}
匹配所有 Unicode 基本字母,\p{M}
匹配所有变音符号,\p{N}
匹配任何 Unicode 数字。
请注意,当连字符位于字符 class 的 start/end 处(或在有效范围或 shorthand 字符后 class).