Board logo

标题: [原创] 批处理如何从用户传入的参数中去掉引号? [打印本页]

作者: namejm    时间: 2008-11-2 15:40     标题: 批处理如何从用户传入的参数中去掉引号?

原文地址:http://www.cn-dos.net/forum/viewthread.php?tid=20838
作者:bagpipe、willsort、3742668、无奈何
发表日期:2006.5.26


作者: wingofsea     时间: 2006-5-26 04:03 PM    标题: [讨论]如何从用户传入的参数中去掉引号?
批处理获取到用户输入的参数,如
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替代变量转命令行参数: calleQuote %%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 05:01 PM
@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 12:00 AM
用 CMD 变量的扩展特性不行吗?
set arg=%~1
这样可以去掉首尾的双引号。


作者: bagpipe     时间: 2006-5-27 02:57 PM
为什么我老是找不到最佳方法呢?伤心.........受教中.............


作者: willsort     时间: 2006-5-27 06:17 PM

Re 无奈何:
      实际上,这个问题还要比你我想象的要复杂一些。
      首先,bagpipe兄的代码更适合脱去环境变量的引号,而兄的代码则更适合脱去命令行参数的引号,二者互有所长。
      其次,无论是哪种方法,都存在无法切实约束串内特殊字符的缺点。
      比如,有变量test1="C:\Program files",而如果使用兄的方法[1],则files将丢失;又比如,有变量test2="echo
test>sample.txt",无论使用何种方法,都会导致程序意外产生的垃圾文件。
      这我在namejm兄的主题曾略有提及,只是语焉不详,现在在这里提出,作为一个课题讨论一下,欢迎各位版主达人不吝赐教。
  1.   
  2.   call:DeQuote "%test1%"
  3.   :DeQuote
  4.   set "return=%~1"
  5.   goto:eof
复制代码
[ Last edited by willsort on 2006-5-27 at 21:26 ]


作者: 3742668     时间: 2006-5-27 07:08 PM
俺也来抛个砖:
  1.   :Main
  2.       set tmpVar=%1
  3.       %tmpVar:*"=set "ret=%
  4.       goto :Eof
复制代码
对于字符串内有多个引号的问题无法处理,不过可以先对字符串内除了开头和结尾的引号进行转换,完了再转换回来就行了。只是抛个砖,希望能引出willsort和无奈何两位的玉出来。。


作者: 无奈何     时间: 2006-5-27 07:28 PM

TO willsort
兄的这段代码调用有些问题。变量值中含有引号,并作为参数传送时,参数不要用引号扩起来。这样潜在的问题是不容易确定变量值中是否含有引号,所以我习惯的做法是用 %~n 脱去引号,然后再加上引号。

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

              :DeQuote1
              set return=%~1
              echo %return%
              goto:eof

              :DeQuote2
              set return="star %~1 end"
              echo %return%
              goto:eof



作者: willsort     时间: 2006-5-27 10:06 PM

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 10:51 PM
Well,我就按willsort兄的要求向不大了解set命令中*号作用的朋友做个简介吧:
    %PATH:str1=str2%
    "str1" 可以以星号打头;在这种情况下,"str1" 会从扩展结果的开始到 str1 剩余部分第一次出现的地方,都一直保持相配。
    所以,在本例中会匹配第一个引号,如果不加*号的话,那么将会匹配所有的引号,那么得到的结果将是错误的。
  1.   set var=www.cn-dos.net
  2.   echo %var:.=#%                   rem 输出结果应该是:www#cn-dos#net
  3.   echo %var:*.=#%                 rem 输出结果应该是:www#cn-dos.net
  4.   pause>nul
复制代码
作者: 无奈何     时间: 2006-5-27 11:21 PM

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 11:33 PM

Re 3742668:
      受教了!也已从set /?中找到了相关信息,看来自己还需要复习数遍命令行帮助文档了。
      
  也就是说星号在此的作用,仍然与文件名中的通配作用相似,它可以通配0到多个任意字符。只是类似的单字符通配符?尚不被支持,而且星号的位置只限于串首,不能在串中或串后出现。


作者: 3742668     时间: 2006-5-27 11:37 PM
嗯,谢谢无奈何指出毛病。关于*的解释嘛,不能怪我,要怪microsoft的 帮助与支持 写得不太好理解。心浮气躁,离大成始终差那么一步,呵呵。


作者: Climbing     时间: 2006-5-27 11:42 PM
TMD,微软的命令说明简直就象绕口令(估计他们自己也没有搞明白到底该如何说),还是3742668兄的例子来得简明易懂一些。说白了,str1前加*号,那么就会只替换环境变量中第一个出现的str1,其它的就不替换了,是这意思吧?


作者: willsort     时间: 2006-5-28 12:49 AM

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 AM

Re willsort
总结的很详细,大众的智慧是无穷的^_^。
关于 call :DeQuote "%test1%"
只是想说明当串值中含有特殊字符时,在传递和调用的过程中必须加引号以使其失去特殊性。不然会解释为多个参数或多个命令。


作者: 3742668     时间: 2006-5-28 01:49 AM
唔,果然最后的总结工作是由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 AM
三位都是大家,我是灌水的。努力学习了!


作者: willsort     时间: 2006-5-28 11:42 PM

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 01:24 PM
好长一篇。在我的习惯中,一般能不用到if就会尽量不用if语句,而尽可能地用echo,dir等等命令来操作变量并根据错误返回值来进行相应的操作。即便是使用 if "%~1"==的格式,我想,如果%1中包含有"=="等不可预料的,类似if语句结构的字符串(牛角尖?)时,也难免出错(没有试验过,只不过是很早的时候的一种想法,从而导致一直以来的习惯)。对于if语句来说,或许能不用就不用就是最好的方法吧。当然,这只是我个人菲薄的认识而已,毕竟当初浅尝辄止并未深究。已见willsort官方理论,期待 无奈何 之高见,呵呵。
    ps:此贴已被加精华贴,各位看官赶快留个爪印吧,到cn-dos来还是第一次混进传说中的精华贴.


作者: tclgb     时间: 2007-7-8 05:24 PM
反复看了三遍,好


作者: rockdong     时间: 2007-8-23 02:28 PM
很受用,谢谢啦!


作者: upsco     时间: 2007-11-19 08:46 PM
 2-4 、可以使用set "var=%var%脱去环境变量var 串尾的最后一个引号,如果串尾
不存在引号则环境变量被清空;
是不是因为版本的原因呀,我在XP中如果var中没引号,set "var=%var%值不变


作者: upsco     时间: 2007-11-19 09:16 PM
原来set "var=%var%,如果var
1、不含引号,数值不变
2、如果有引号,且不在首位,保留倒数第一个引号前的值
3、如果有引号都在串首,清空变量


作者: laihaibin08     时间: 2008-10-19 05:53 PM
有点深了……
作者: 随风    时间: 2008-11-2 19:10

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

应该是
脱去环境变量test串最外侧的一对引号及两边的所有字符
如果环境变量test串不含引号则出现语法错误
如果环境变量test串只有一个引号则脱去此引号及它左边的所有内容

cls
:
  1. @echo off  
  2. set test=abc1"2345efg
  3. %test:*"=set "test=%
  4. echo %test%
  5. set test="abc1"2345efg"
  6. %test:*"=set "test=%
  7. echo %test%
  8. pause
复制代码

作者: doupip    时间: 2010-11-1 16:45

特殊情况特殊对待吧,看来高手们境界还是不一样




欢迎光临 批处理之家 (http://bathome.net./) Powered by Discuz! 7.2