Board logo

标题: [讨论]如何从用户传入的参数中去掉引号? [打印本页]

作者: wingofsea     时间: 2006-5-26 16:03    标题: [讨论]如何从用户传入的参数中去掉引号?

批处理获取到用户输入的参数,如
utility "C:\program files\utility",

如何去掉"C:\program files\utility" 的引号?
要做一些字符串拼接的操作,如

@echo off
@set arg=%1
@set file_path=%arg%\readme.txt
@for /f "usebackq delims=" %%a in (%file_path%) do set a=%%a

运行上诉代码提示:
The system cannot find the file C:\Program Files\utility"\version.txt

请问如何解决?


───────────────── 版主提示 ─────────────────
本主题讨论小结如下:

  很多情况下,我们需要脱除一个字符串中可能会存在的引号,然后在加上自己的引
号使其中的特殊字符(命令连接符& 、| 、&&、||,命令行参数界定符Space 、tab 、
; 、= ,字符化转义符^ 、" ,变量化转义符% 等)字符化,失去特定的作用,而作为
普通的字符成为字符串的一个组成部分。

  一、将字符串中的引号脱去的简单办法有三种,它们的功能相近,只是各自的使用
场合不同,可以处理大多数的情况。

  1-1 、如果字符串存在于命令行参数%1中,可以使用%~1 脱去第一对外侧引号,如
果没有外侧引号则字符串不变;

  1-2 、如果字符串存在于for 替代变量%%i 中,可以使用%%~i脱去第一对外侧引号,
如果没有外侧引号则字符串不变;

  1-3 、如果字符串存在于环境变量%temp%中,可以使用%temp:"=% 脱去其中所有的
引号,如果没有引号则字符串不变;

  1-4 、以上三种方案在某种程度上可以互相通用,因为它们作为变量的一种类型,
可以通过类似以下的代码或代码片断相互转移:

      1-4-1、for替代变量转命令行参数: call:DeQuote %%i
      1-4-2、环境变量转命令行参数:call:DeQuote %temp%
      1-4-3、命令行参数转for替代变量:for %%i in (%1) do ...
      1-4-4、环境变量转for替代变量:for %%i in (%temp%) do ...
      1-4-5、命令行参数转环境变量:set temp=%1
      1-4-6、for替代变量转环境变量:for ... set temp=%%i

  二、如果字符串的引号分布情况很复杂,或者我们对被脱去引号的位置有特殊要求,
或者字符串中可能出现某些控制字符,则可以将字符串首先通过1-4 中的对应方法转存
至环境变量中,在使用以下方案或其组合进行处理:

  2-1 、可以使用set var=%var:~1%脱去环境变量var 串首的第一个引号,如果串首
不存在引号则第一个字符被脱去;

  2-2 、可以使用set %var:*"=% 脱去环境变量var 串首的第一个引号,如果串首不
存在引号则变量值不变;

  2-3 、可以使用set var=%var:~0,-1% 脱去环境变量var 串尾的最后一个引号,如
果串尾不存在引号则最后一个被脱去;

  2-4 、可以使用set "var=%var%脱去环境变量var 串尾的最后一个引号,如果串尾
不存在引号则环境变量被清空;

  2-5 、可以使用set var=%var:~1,-1% 脱去环境变量var 串最外侧的一对引号,如
果串外侧不存在引号则外侧一对字符被脱去;

  2-6 、可以使用%var:*"=set "var=%脱去环境变量var 串最外侧的一对引号,如果
串外侧不存在引号则出现语法错误;

  2-7 、可以使用set "var=%var:"=%"脱去环境变量var 串中可能出现的所有引号,
如果串外侧不出现引号则变量值不变;与1-3 不同的是,它容许字符串的匹配引号对内
出现特殊控制字符;

───────────────── 版主提示 ─────────────────


[ Last edited by willsort on 2006-5-28 at 22:52 ]
作者: bagpipe     时间: 2006-5-26 17:01
@echo off
set arg=%1
echo %arg:"=%
set file=%arg:"=%\1.txt
echo %file%
for /f "usebackq" %%a in ("%file%") do echo %%a

其实很简单,就是没有想到罢了.............
作者: 无奈何     时间: 2006-5-27 00:00
用 CMD 变量的扩展特性不行吗?

set arg=%~1

这样可以去掉首尾的双引号。
作者: bagpipe     时间: 2006-5-27 14:57
为什么我老是找不到最佳方法呢?伤心.........受教中.............
作者: willsort     时间: 2006-5-27 18:17
Re 无奈何:

      实际上,这个问题还要比你我想象的要复杂一些。

      首先,bagpipe兄的代码更适合脱去环境变量的引号,而兄的代码则更适合脱去命令行参数的引号,二者互有所长。

      其次,无论是哪种方法,都存在无法切实约束串内特殊字符的缺点。

      比如,有变量test1="C:\Program files",而如果使用兄的方法[1],则files将丢失;又比如,有变量test2="echo test>sample.txt",无论使用何种方法,都会导致程序意外产生的垃圾文件。

      这我在namejm兄的主题曾略有提及,只是语焉不详,现在在这里提出,作为一个课题讨论一下,欢迎各位版主达人不吝赐教。
call:DeQuote "%test1%"

:DeQuote
set "return=%~1"
goto:eof
[ Last edited by willsort on 2006-5-27 at 21:26 ]
作者: 3742668     时间: 2006-5-27 19:08
俺也来抛个砖:
:Main
    set tmpVar=%1
    %tmpVar:*"=set "ret=%
    goto :Eof
对于字符串内有多个引号的问题无法处理,不过可以先对字符串内除了开头和结尾的引号进行转换,完了再转换回来就行了。只是抛个砖,希望能引出willsort和无奈何两位的玉出来。。
作者: 无奈何     时间: 2006-5-27 19:28
TO willsort
        兄的这段代码调用有些问题。变量值中含有引号,并作为参数传送时,参数不要用引号扩起来。这样潜在的问题是不容易确定变量值中是否含有引号,所以我习惯的做法是用 %~n 脱去引号,然后再加上引号。

  Quote:

  1. @echo off
  2. set test1="C:\Program files"
  3. set test2="echo test>sample.txt"
  4. call :DeQuote1 %test1%
  5. call :DeQuote2 %test2%
  6. goto:eof

  7. :DeQuote1
  8. set return=%~1
  9. echo %return%
  10. goto:eof

  11. :DeQuote2
  12. set return="star %~1 end"
  13. echo %return%
  14. goto:eof
        -=代码着色  BY:无奈何=-


作者: willsort     时间: 2006-5-27 22:06
Re 无奈何:

      我的意思是,当我们无法预知test1/test2的串值中是否含有引号时,就无法使用特定的方法对其中的特殊字符进行约束。比如namejm兄代码中source变量,是通过set /p获取的,我们无法确知执行代码的用户有没有使用引号,或者会不会有意无意给我们的代码制造麻烦。

      也就是说,当串值(不仅仅是环境变量)含有引号时,我们(在各种命令行下)引用它就不能再使用引号,而当串值不含引号时,我们又需要使用引号进行约束,而这还没有考虑到两对或更多对引号甚至奇数个引号出现的情形。而因为我们无法预先获知串值的内容,所以需要预先检测,而目前所知的任何检测都需要引用串值。引用前需要检测,检测时必须引用,这就形成了一个佯谬。

      感觉上,这个问题实际上是来源于CMD匹配引号采用就近匹配,而不是就远匹配的原则。我想知道是否存在一个比较另类的办法可以解决这个问题。

Re 3742668:

      你的 %tmpVar:*"=set "ret=% 一句确实妙绝,对星号在此处的用途尚不十分清楚,不知可否解释一二?

      当然它也需要预先确定串值外含有引号,否则语法错误。

Re All:

      目前的测试中,set "ret=%test%会将含有后引号的串值脱去一个后引号,这是可预期的结果;而没有引号的串值将整个脱去,就在意料之外了,我本猜想它会将ret=%test%作为环境变量名的一部分,因变量匹配无效而不对%ret%做任何修改的,而结果是%ret%被清除。
作者: 3742668     时间: 2006-5-27 22:51
Well,我就按willsort兄的要求向不大了解set命令中*号作用的朋友做个简介吧:
    %PATH:str1=str2%
    "str1" 可以以星号打头;在这种情况下,"str1" 会从扩展结果的开始到 str1 剩余部分第一次出现的地方,都一直保持相配。
    所以,在本例中会匹配第一个引号,如果不加*号的话,那么将会匹配所有的引号,那么得到的结果将是错误的。
set var=www.cn-dos.net
echo %var:.=#%                   rem 输出结果应该是:www#cn-dos#net
echo %var:*.=#%                 rem 输出结果应该是:www#cn-dos.net
pause>nul

作者: 无奈何     时间: 2006-5-27 23:21
Re willsort
理解兄的意思了,能够办到替换串值中的所有引号,相应的问题是在传输时必须加引号扩起来。

set test="C:\Pro&gram" files"
set "test=%test:"=%"
call :DeQuote "%test1%"

Re 3742668
兄对 * 的解释不太好理解,可以分开来表述。
1、%PATH:str1=str2%
2、%PATH:*str1=str2%
其中 1 替换 PATH 中所有 str1 为 str2 ;
     2 替换 PATH 中开始到 str1 部分为 str2 。
再者 echo %var:*.=#%    输出结果应该是:#cn-dos.net
作者: willsort     时间: 2006-5-27 23:33
Re 3742668:

      受教了!也已从set /?中找到了相关信息,看来自己还需要复习数遍命令行帮助文档了。

      也就是说星号在此的作用,仍然与文件名中的通配作用相似,它可以通配0到多个任意字符。只是类似的单字符通配符?尚不被支持,而且星号的位置只限于串首,不能在串中或串后出现。
作者: 3742668     时间: 2006-5-27 23:37
嗯,谢谢无奈何指出毛病。关于*的解释嘛,不能怪我,要怪microsoft的 帮助与支持 写得不太好理解。心浮气躁,离大成始终差那么一步,呵呵。
作者: Climbing     时间: 2006-5-27 23:42
TMD,微软的命令说明简直就象绕口令(估计他们自己也没有搞明白到底该如何说),还是3742668兄的例子来得简明易懂一些。说白了,str1前加*号,那么就会只替换环境变量中第一个出现的str1,其它的就不替换了,是这意思吧?
作者: willsort     时间: 2006-5-28 00:49
Re Ups:

      有意思的一个发现,由于密集回复导致从10楼到13楼都变成了隔层回复,不知是由于论坛同步延迟的原因,还是用户刷新延迟的问题。

      另外,还不是很明白无奈何兄set "test=%test:"=%"之后再去call :DeQuote "%test1%"的缘由。

      最后,就此主题略作一个小结,请大家补正:

      很多情况下,我们需要脱除一个字符串中可能会存在的引号,然后在加上自己的引号使其中的特殊字符(命令连接符&、|、&&、||,命令行参数界定符Space、tab、;、=,字符化转义符^、",变量化转义符%等)进行字符化,使其失去特定的作用,而作为普通的字符成为字符串的一个组成部分。

      一、将字符串中的引号脱去的简单办法由三种,它们的功能相近,只是各自的使用场合不同,可以处理大多数的情况。

      1-1、如果字符串存在于命令行参数%1中,可以使用%~1脱去第一对外侧引号,如果没有外侧引号则字符串不变;

      1-2、如果字符串存在于for替代变量%%i中,可以使用%%~i脱去第一对外侧引号,如果没有外侧引号则字符串不变;

      1-3、如果字符串存在于环境变量%temp%中,可以使用%temp:"=%脱去其中所有的引号,如果没有引号则字符串不变;

      1-4、以上三种方案在某种程度上可以互相通用,因为它们作为变量的一种类型,可以通过类似以下的代码或代码片断相互转移:
      1-4-1、for替代变量转命令行参数: call:DeQuote %%i
      1-4-2、环境变量转命令行参数:call:DeQuote %temp%
      1-4-3、命令行参数转for替代变量:for %%i in (%1) do ...
      1-4-4、环境变量转for替代变量:for %%i in (%temp%) do ...
      1-4-5、命令行参数转环境变量:set temp=%1
      1-4-6、for替代变量转环境变量:for ... set temp=%%i

      二、如果字符串的引号分布情况很复杂,或者我们对被脱去引号的位置有特殊要求,或者字符串中可能出现某些控制字符,则可以使用以下方案:

      2-1、可以使用%test:*"=%脱去环境变量test串首的第一个引号,如果串首不存在引号则变量值不变;

      2-2、可以使用set "test=%test%脱去环境变量test串尾的最后一个引号,如果串尾不存在引号则变量值被清空;

      2-3、可以使用%test:*"=set "test=%脱去环境变量test串最外侧的一对引号,如果串外侧不存在引号则出现语法错误;

      2-4、可以使用set "test=%test:"=%"脱去环境变量test串中可能出现的所有引号,如果串外侧不出现引号则变量值不变;与1-3不同的是,它可以容许字符串的匹配引号对内出现特殊控制字符。

[ Last edited by willsort on 2006-5-28 at 01:22 ]
作者: 无奈何     时间: 2006-5-28 01:05
Re willsort
总结的很详细,大众的智慧是无穷的^_^。
关于 call :DeQuote "%test1%" 只是想说明当串值中含有特殊字符时,在传递和调用的过程中必须加引号以使其失去特殊性。不然会解释为多个参数或多个命令。
作者: 3742668     时间: 2006-5-28 01:49
唔,果然最后的总结工作是由willsort来完成,只有willsort与无奈何两位严谨的态度才适合发这种理论性的贴。But,俺还是班门弄弄斧,关二爷面前耍耍大刀:
    2.5.可以用%var:~1%来去掉前引号。
    2.6.可以用%var:~1,-1%来去掉前后引号
    2.7.可以用%var:~0,-1%来去掉后引号。

[ Last edited by 3742668 on 2006-5-28 at 02:02 ]
作者: Climbing     时间: 2006-5-28 02:50
三位都是大家,我是灌水的。努力学习了!
作者: willsort     时间: 2006-5-28 23:42
Re 3742668:

      新的小结被编辑到顶楼贴中方便浏览,并将你的建议分别并入到2-1、2-3、2-5中。

      正如无奈何兄所言,集体的智慧远大于个体智慧的总和。现在想到另外一道与引号相关的课题,也提请大家讨论。

      命令行参数的防御错误

      我过去的代码中,在if语句中比较命令命令行参数时,总喜欢使用类似if "%1"=="string1" ...的格式,现在想来,这个习惯是很有些问题的。

      因为使用引号的目的在于防御串中可能出现的转义字符,而在命令行参数中,串值在经过CMD的命令行解析后,所有的可能被转义的转义字符应该都已发挥作用而被脱去,剩下的转义字符应该都是在命令行中就被预先防御过。所以,%1中不可能含有空格或其它转义字符而没有引号防御,也因此再在%1外使用引号就成了多余。

      再进一步讲,如果%1是经过引号防御转义的字符串,比如"C:\Program files",此时再使用"%1"引用则变成了""C:\Program files"",引号被重新匹配,因而转义字符被暴露出来,导致程序出错,因此此时使用引号就成了错误。

      那么,我们是否不需要再if语句中对%1进行防御呢?答案是否定的,因为if中防御字符的出现,最初是为了防御%1为空,就是类似if "%1"=="" ...的格式,所以我一度曾称呼防御字符为“防空字符”。如果不对%1进行防御,则再无参数调用批处理执行到此句会出现语法错误。
      
      至此,我们最好的选择就是将%1脱引号后再使用引号进行防御。比如 if "%~1"=="C:\Program file" ... 或 if exist "%~1" .... 。这确实是很好的办法。

      但是,脱引号的实现只有在CMD中才比较简单,而我的批处理很多还在9x和DOS下运行,所以,我需要退而求其次想其它的办法,比如选取其它字符作为命令行参数串的防御字符。在串值比较的if语句中,可选的防御字符有很多,几乎大多非转义的字符都可用来防御。比如我早期常用的 if [%1]==[] ... 。

      然而,还需要考虑的是,防御字符还会出现在其它语句中,比如文件存在判断语句if exist中。如果我们使用if exist [%1] ... 的范式,则程序的流程显然将脱出我们预期之外。尽管在我通常的惯例是 if [%1]==[] ... 之后再 if exist %1 ... 。

      故而,为了保持代码可阅读性,我需要使用风格一致的的防御字符,所以我们剩下的选择就很少了。或许,我们可以使用后缀的句点。比如 if %1.==. ... 或 if exist %1. ...

      它可以解决串为文件名时的部分防御问题,但当串为路径. 或者..时,if exist %1. 显然又会出错。

      那么还存在更好的解决方案吗?
作者: 3742668     时间: 2006-5-29 13:24
好长一篇。在我的习惯中,一般能不用到if就会尽量不用if语句,而尽可能地用echo,dir等等命令来操作变量并根据错误返回值来进行相应的操作。即便是使用 if "%~1"==的格式,我想,如果%1中包含有"=="等不可预料的,类似if语句结构的字符串(牛角尖?)时,也难免出错(没有试验过,只不过是很早的时候的一种想法,从而导致一直以来的习惯)。对于if语句来说,或许能不用就不用就是最好的方法吧。当然,这只是我个人菲薄的认识而已,毕竟当初浅尝辄止并未深究。已见willsort官方理论,期待 无奈何 之高见,呵呵。
    ps:此贴已被加精华贴,各位看官赶快留个爪印吧,到cn-dos来还是第一次混进传说中的精华贴.
作者: tclgb     时间: 2007-7-8 17:24
反复看了三遍,好
作者: rockdong     时间: 2007-8-23 14:28
很受用,谢谢啦!
作者: upsco     时间: 2007-11-19 20:46
 2-4 、可以使用set "var=%var%脱去环境变量var 串尾的最后一个引号,如果串尾
不存在引号则环境变量被清空;

是不是因为版本的原因呀,我在XP中如果var中没引号,set "var=%var%值不变
作者: upsco     时间: 2007-11-19 21:16
原来set "var=%var%,如果var
1、不含引号,数值不变
2、如果有引号,且不在首位,保留倒数第一个引号前的值
3、如果有引号都在串首,清空变量
作者: laihaibin08     时间: 2008-10-19 17:53
有点深了……