【发布时间】:2022-01-16 09:22:43
【问题描述】:
这是一个看似复杂的问题,但我会尽力解释这个问题。
我有一个简单的包装脚本,名为VSYSCopyPathToClipboard.ps1:
param (
[Parameter(Mandatory,Position = 0)]
[String[]]
$Path,
[Parameter(Mandatory=$false)]
[Switch]
$FilenamesOnly,
[Parameter(Mandatory=$false)]
[Switch]
$Quotes
)
if($FilenamesOnly){
Copy-PathToClipboard -Path $Path -FilenamesOnly
}else{
Copy-PathToClipboard -Path $Path
}
Copy-PathToClipboard 只是我可以使用的一个函数,它可以将路径/文件名复制到剪贴板。这与问题无关,只是假设它按照它所说的去做。
调用包装器的方式是通过 Windows 右键单击上下文菜单。这涉及在此处创建密钥:HKEY_CLASSES_ROOT\AllFilesystemObjects\shell\。
命令如下:
"C:\Tools\scripts\BIN\SingleInstanceAccumulator.exe" -q:' "-c:pwsh -noprofile -windowstyle hidden -Command "C:\Tools\scripts\VSYSCopyPathToClipboard.ps1" -Path $files" "%1"
同样对于“复制为文件名”:
"C:\Tools\scripts\BIN\SingleInstanceAccumulator.exe" -q:' "-c:pwsh -noprofile -windowstyle hidden -Command "C:\Tools\scripts\VSYSCopyPathToClipboard.ps1" -FilenamesOnly -Path $files" "%1"
我在这里使用了一个名为SingleInstanceAccumulator 的工具。这允许我将多个选定的文件传递给 PowerShell 的 single 实例。如果我不使用该程序并在选择多个文件的情况下运行我的命令,它将为每个选定的文件启动多个 PowerShell 实例。创建自己的 shell 扩展和实现 IPC 等是下一个最好的事情。
直到今天我遇到一个文件名中包含单引号的文件 (I.E.testing'video.mov) 并且整个脚本都失败时,这一直很好用。它失败了,因为我与 SingleInstanceAccumulator 一起使用的分隔符也是单引号,而 PowerShell 看不到匹配的引号......因此出错。
如果我的变量是静态的,我可以通过将有问题的单引号加倍来解决这个问题,但由于我的参数是文件,除了重命名文件本身之外,我没有机会转义单引号......这是一个非解决方案.
所以现在我不知道如何处理。
我第一次尝试解决这个问题是这样的:
- 创建一个批处理文件并将我的注册表命令重定向到它。
- 将 SingleInstanceAccumulator 分隔符更改为“/”(所有文件将用正斜杠分隔。)
- 将有问题的单引号替换为两个单引号。
- 用单引号替换“/”分隔符。
- 最后将整个参数列表传回 Powershell。
这张图片展示了上述过程的样子:
这是批处理文件的代码:
@echo off
setlocal EnableDelayedExpansion
:: This script is needed to escape filenames that have
:: a single quote ('). It's replaced with double single
:: quotes so the filenames don't choke powershell
:: echo %cmdcmdline%
set "fullpath=%*"
echo Before
echo !fullpath!
echo ""
echo After
set fullpath=%fullpath:'=''%
set fullpath=%fullpath:/='%
echo !fullpath!
:: pwsh.exe -noprofile -windowstyle hidden -command "%~dpn0.ps1 -Path !fullpath!
pause
连接好之后,我开始庆祝……直到我找到一个带有 & 或感叹号 (!) 的文件。一切再次崩溃。关于转义 & 和 !字符,但没有任何建议对我有用。
如果我将'C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\testing&video.mov' 传递到我的批处理文件中,我会返回'C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\testing。
它在 & 符号的确切位置截断字符串。
我觉得必须有办法解决这个问题,而我错过了一些愚蠢的东西。如果我回显%cmdcmdline%,它会显示完整的命令行with &,因此它可以通过该变量以某种方式使用。
结论:我很抱歉这篇文章的小说。我试图完成的工作有很多细微差别,需要解释一下。我的问题如下:
- 我可以仅使用 Powershell 并以某种方式预先转义单引号来完成此操作吗?
- 我可以使用批处理文件完成此操作,并以某种方式预先转义
&和!(以及任何其他会导致失败的特殊字符)吗?
任何帮助都将不胜感激。
编辑1:
因此,我设法以最可怕和最骇人听闻的方式解决了我的问题。但由于它太可怕了,而且我觉得这样做很糟糕,所以我仍在寻找合适的解决方案。
基本上,回顾一下,当我执行以下任一变量赋值时:
set "args=%*"
set "args=!%*!"
echo !args!
& 和 ! 字符仍然会破坏,而且我没有得到我的文件的完整枚举。带有& 的文件会被截断等。
但是当我这样做时我注意到了:
set "args=!cmdcmdline!"
echo !args!
我得到了保留所有特殊字符的完整命令行调用:
C:\WINDOWS\system32\cmd.exe /c ""C:\Tools\scripts\VSYSCopyPathToClipboardTest.bat" /C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\KylieCan't.mov/,/C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\The !Rodinians - Future Forest !Fantasy - FFF Trailer.mov/,/C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\Yelle - Je Veu&x Te Voir.mov/,/C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\Erik&Truffaz.mov/,/C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\my_file'name.mov/,/C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\testing&video.mov/"
所以我所做的只是去掉了字符串的初始 C:\WINDOWS\system32\cmd.exe /c ""C:\Tools\scripts\VSYSCopyPathToClipboardTest.bat" 部分:
@echo off
setlocal enableDelayedExpansion
set "args=!cmdcmdline!"
set args=!args:C:\WINDOWS\system32\cmd.exe=!
set args=!args: /c ""C:\Tools\scripts\VSYSCopyPathToClipboard.bat" =!
set args=!args:'=''!
set args=!args:/='!
set args=!args:~0,-1!
echo !args!
pwsh.exe -noprofile -noexit -command "%~dpn0.ps1 -Path !args!
而且...它完美无缺。它可以处理我扔给它的任何疯狂角色,而无需逃避任何事情。我知道这完全是解决这个问题的最堕落的垃圾方式,但是在任何地方都找不到解决方案导致我采取绝望的措施。 :)
我可能会让字符串删除更通用一点,因为如果我更改文件名,它实际上会中断。
如果有人知道以更优雅的方式完成同一件事的方法,我仍然非常愿意接受其他解决方案。
【问题讨论】:
-
我完全不确定您如何期望某些东西可以按照预期的方式正确使用
"C:\Tools\scripts\BIN\SingleInstanceAccumulator.exe" -q:' "-c:pwsh -noprofile -windowstyle hidden -Command "C:\Tools\scripts\VSYSCopyPathToClipboard.ps1" -Path $files" "%1"和"C:\Tools\scripts\BIN\SingleInstanceAccumulator.exe" -q:' "-c:pwsh -noprofile -windowstyle hidden -Command "C:\Tools\scripts\VSYSCopyPathToClipboard.ps1" -FilenamesOnly -Path $files" "%1"中的双引号。您有嵌套的双引号,我认为这需要转义才能理解字符串位置。 -
他们不需要转义。
"C:\Tools\scripts\BIN\SingleInstanceAccumulator.exe" -q:' "-c:pwsh -noprofile -windowstyle hidden -Command "C:\Tools\scripts\VSYSCopyPathToClipboard.ps1" -Path $files" "%1"工作得很好,除了带有单引号的文件名或目录。这是一个快速的模型,将调用分解为多个块:i.imgur.com/Wi2L2Ly.png ` -
如果你说它有效,那很好,我只是说我看到了一个明确定义的单双引号元素
"-c:pwsh -noprofile -windowstyle hidden -Command "。 -
Unicode 标准名称
"是引号,'是撇号。我们应该弃用“双引号”和“单引号”术语。只是说“引用它”是模棱两可的。 -
@lit 公平点。我会用我的术语不那么模棱两可。
标签: powershell batch-file cmd command-line escaping