如何在Windows命令行或批处理文件中搜索带有斜杠或冒号的字符串?

3
我正在尝试编写一个函数,它接受一个参数并将其分类为文件夹名称(FOLDER)、相对路径名(RELATIVE_PATHNAME)或绝对路径名(ABSOLUTE_PATHNAME)。
目前我已经有了以下代码:
@echo off
call :classifyInput C:\Users\MikeW
echo C:\Users\MikeW is of type %_inputType%
call :classifyInput documentation
echo documentation is of type %_inputType%
goto :EOF

:classifyInput
SETLOCAL
set _inputType=
rem // a string is a "name of folder" iff it has no '/', '\\' or ':' in it, and a pathname otherwise
echo %1%|findstr /i [\/:] >nul
if %errorlevel% == 1 set _inputType=FOLDER
echo %errorlevel%
rem // a string is a "relative pathname" iff it has no ':' as its second character, and an absolute pathname otherwise
if _inputType == [] & "%1:~2,1% == :" (
    _inputType=ABSOLUTE_PATHNAME
    echo You gave me absolute pathname.
)

ENDLOCAL & set _inputType=%_inputType%

目前为止,我的函数在两种情况下的表现是相同的。

此外,搜索如何查找字符/\:的谷歌结果没有任何有用的信息。


我非常清楚我没有处理RELATIVE_PATHNAME。这是有意的,因为我还没来得及处理,我正在努力弄清楚到目前为止出了什么问题。 - Mike Warren
我现在想编写一个程序,将我的CMD变量发送到该程序,并返回一些内容给CMD进程。问题是我不知道如何做到这一点。 - Mike Warren
相对路径也可以包含 : -- 比如说 C:folder... - aschipfl
@aschipfl 正确的是,C:folder 是一个相对路径,指定了驱动器 C: 上当前目录中的子文件夹 folder。但可以假设 C:folder 大约有99.999999999% 的可能是一个错误输入的绝对路径,而不是故意使用的相对路径。在使用 Windows 计算机超过20年的时间里,我从未见过有人故意使用这样的路径作为相对路径。然而,我已经修改了我的答案中的代码,以检查绝对路径。 - Mofi
@Mofi,自MS-DOS时代以来,每个驱动器都有自己的当前路径。当时使用相对路径比较方便,例如D:file.ext,特别是在有多个磁盘驱动器的情况下。这种“遗留问题”仍然存在于Windows的cmd中。不管怎样,我只是想指出,在处理路径时,如果使用字符串操作函数而不是与路径相关的函数,可能会出现特殊情况被错误地处理的情况... - aschipfl
3个回答

4
为了回答你的问题,使用 findstr 查找字符 /, \, :,你可以使用以下正则表达式:
findstr "[\\/:]"

编辑: 或者如果你想在字符串中查找像评论中所描述的\\, /:,你可以使用以下方法:

findstr /r "\\\\ / :"

由于在正则表达式中,\是转义字符,因此您需要使用\\来获取真正的\findstr将其正则表达式中的空格视为OR运算符。
在让您继续之前,我想指出代码中的一些小错误并帮助您改进它。
  • the comparison _inputType == [] will compare the literal string "_inputType" with the string "[]". If you want to check if a variable is empty or not, use the if defined var that will turn true only if %var% is not empty (use if not for the opposite behaviour) or use if "%var%" == "". But be sure you use the same delimiters because the == operator will compare each single character: if abc == "abc" will never turn be taken but if "abc" == "abc" will.

  • substring extraction will only work on environment variables that has been defined with set. It won't work on arguments variables (nor on loop variables). So %1:~2,1% won't get what you want. It will just replace %1 with the value of the argument in there and give something like C:\Users\MikeW:~2,1% for example. If you want to extract from an argument you'll have to set a variable with the value of the argument and extract from that variable:

    set input=%~1
    rem now you can use %input:~2,1%
    

    In your code I read that you want the 2nd character of the input: %input:~2,1% will give you the 3rd, not the 2nd. In case of substring, read %var:~n,k% as k characters after the n-th character. So what you need is %input:~1,1%

  • I presume you tried to perform an AND operation on this line:

    if _inputType == [] & "%1:~2,1% == :"
    

    Unfortunately, the AND and the OR operators don't exist in batch, so you'll have to "mock" the AND yourself, by using consecutive if-statements (after having applied the modifications proposed above):

    set input=%~1
    if not defined _inputType if "%input:~1,1%" == ":"
    

最终我得到了这个:

@echo off
call :classifyInput C:\Users\MikeW
echo C:\Users\MikeW is of type %_inputType%
call :classifyInput documentation
echo documentation is of type %_inputType%
call :classifyInput Documents\Folder\inside
echo Documents\Folder\inside is of type %_inputType%
goto :EOF

:classifyInput
SETLOCAL
set _inputType=
set input=%~1

rem // a string is a "name of folder" iff it has no '/', '\\' or ':' in it, and a pathname otherwise
echo %input%|findstr /r "\\\\ / :" >nul
if %errorlevel% == 1 set _inputType=FOLDER

echo %errorlevel%

rem // a string is a "relative pathname" iff it has no ':' as its second character, and an absolute pathname otherwise
if not defined _inputType if "%input:~1,1%" == ":" (
    set _inputType=ABSOLUTE_PATHNAME
    echo You gave me absolute pathname.
) else (
    set _inputType=RELATIVE_PATHNAME
)

ENDLOCAL & set _inputType=%_inputType%

上面的脚本将会产生以下结果:
0
You gave me absolute pathname.
C:\Users\MikeW is of type ABSOLUTE_PATHNAME
1
documentation is of type FOLDER
0
Documents\Folder\inside is of type RELATIVE_PATHNAME

PS:如果您可以去除 echo %errorlevel%,您可以将 :classifyInput 函数简化为以下形式:

:classifyInput
SETLOCAL
set _inputType=
set input=%~1

rem // a string is a "name of folder" iff it has no '/', '\\' or ':' in it, and a pathname otherwise
echo %input%|findstr /r "\\\\ / :" >nul
if %errorlevel% == 1 (
    set _inputType=FOLDER
) else (
     if "%input:~1,1%" == ":" (
        set _inputType=ABSOLUTE_PATHNAME
        echo You gave me absolute pathname.
    ) else (
        set _inputType=RELATIVE_PATHNAME
    )
)

ENDLOCAL & set _inputType=%_inputType%

0

前言:本文不涉及如何搜索字符串中的/\:,而是集中于正确分类路径!


首先,我强烈建议避免使用纯字符串操作函数进行路径分类,因为这样很容易出现错误;而是应该使用与路径相关的功能!
然后,您需要明确以下情况下这个程序应该输出什么:
输入类似于 name:您称其为FOLDER,但这也可能指向文件而不是文件夹,或者根本不存在,因此我们不知道它是文件还是文件夹;我将这样的项分类为PURENAME;请注意,这只是特定相对路径;
输入类似于.\name:我将其分类为RELATIVE,与您类似(RELATIVE_PATHNAME);
输入类似于D:\name:我将其分类为ABSOLUTE,与您类似(ABSOLUTE_PATHNAME);
输入类似于D:name:有一个冒号:,因此您会将其称为ABSOLUTE_PATHNAME,尽管这构成了相对于驱动器D:的当前目录的路径,因此我将其分类为RELATIVE
输入类似于\name:没有指定驱动器,因此我将其分类为RELATIVE,因为它指向当前驱动器的根目录;
输入类似于\\Host\Share\name(UNC路径):需要正确处理(某些命令不能处理此类路径);我将它们分类为ABSOLUTE
输入包含正斜杠/:许多命令不将它们视为路径分隔符,因此在分类之前我将所有/替换为\
输入包含引号":它们不是用于文件和文件夹名称的有效字符,因此我删除所有引号,因为许多命令在路径中忽略它们;
输入包含额外的冒号:除了驱动器字母后面的冒号(非法路径),或者它包含禁止使用的字符,例如<>| :未检查输入的有效性;
输入没有检查是否存在。

这里有一个脚本,可以对您在提示框中输入的路径进行分类(空输入将终止脚本):

@echo off
setlocal EnableExtensions DisableDelayedExpansion

:LOOP
rem // Derive user input string here:
set "INPUT=" & set /P INPUT="Enter a path: "
rem // Quit script in case of empty input:
if not defined INPUT exit /B
rem // Remove quotation marks `"`:
set "INPUT=%INPUT:"=%
rem // Replace all `/` characters by `\`:
if defined INPUT set "INPUT=%INPUT:/=\%"
rem // Call classification sub-routine:
call :CLASSIFY ITYPE "%INPUT%"
rem // Return resulting class:
echo "%INPUT%" is of type %ITYPE%.
rem // Loop back to user input:
goto :LOOP

endlocal
exit /B


:CLASSIFY  rtn_class  val_path
    setlocal DisableDelayedExpansion
    set "CLA="
    rem // Check whether argument is empty:
    if "%~2"=="" set "CLA=EMPTY" & goto :RETURN
    rem /* Check whether argument contains wild-cards (like `?` and `*`);
    rem    the `for` loop iterates exactly once in case no wild-cards are present;
    rem    if there are wild-cards, the loop iterates wither zero times (no matching
    rem    items found), or as many times as there are matching items, hence the
    rem    wild-cards becime resolved (replaced), so the `if` condition fails: */
    set "CLA=WILDCARD"
    for %%I in ("%~2") do if /I "%~2"=="%%~I" set "CLA="
    if defined CLA goto :RETURN
    rem // Check whether argument is a pure name:
    if /I "%~2"=="%~nx2" set "CLA=PURENAME" & goto :RETURN
    rem // Check whether argument is a simple relative path:
    if /I "%~d2"=="" set "CLA=RELATIVE" & goto :RETURN
    rem // Check whether argument is a simple absolute path:
    if /I "%~2"=="%~f2" set "CLA=ABSOLUTE" & goto :RETURN
    if /I "%~2"=="%~s2" set "CLA=ABSOLUTE" & goto :RETURN
    rem /* Check whether argument is a complex relative or absolute path; for this,
    rem    the drive is split off from the argument to cover `D:path` items too: */
    set "DRV=%~d2" & set "ARG=%~2"
    if defined DRV call set "STR=%%ARG:*%DRV%=%%"
    if defined DRV if defined STR set "STR=%STR:^^=^%"
    if /I not "%DRV%%STR%"=="%ARG%" set "STR=%ARG%"
    if not defined STR set "STR=."
    rem // The `for` loop iterates once only and is needed to resolve `~` modifiers:
    for %%I in ("%STR%") do (
        rem /* Now check whether argument is an absolute or relative path; for this,
        rem    the argument is converted to an absolute path, related to the current
        rem    working directory first; then the current directory is substituted by
        rem    a temporary drive named `#:`, whose root becomes the current working
        rem    directory temporarily; the argument is again converted to an absolute
        rem    path, related to the temporary working directory; if the result is
        rem    the same as the one from the previous path conversion, the argument
        rem    holds an absolute path, otherwise it holds a relative one: */
        set "LNG=%%~fI"
        set "SHT=%%~sI"
        pushd .
        subst #: . & #:
        set "CLA=ABSOLUTE"
        if /I not "%%~fI"=="%LNG%" if /I not "%%~sI"=="%SHT%" set "CLA=RELATIVE"
        popd & subst #: /D
    )
    :RETURN
    rem // Return resulting class:
    endlocal & set "%~1=%CLA%"
    exit /B

脚本返回以下类:
  • PURENAME:(相对)路径仅由纯名称组成(类似于您的类FOLDER);
  • RELATIVE:路径是相对的(没有给出驱动器,路径不从根目录开始);
  • ABSOLUTE:路径是绝对的(给出了驱动器,路径从根目录开始,或者路径是UNC路径);
  • WILDCARD:路径包含全局通配符,如?和/或*;
  • EMPTY:路径为空(输入了类似于""的内容);

0

命令SETIF可用于检查分配给环境变量的字符串是否包含特定字符或字符串。

例如:

@echo off
setlocal EnableExtensions
for %%I in ("C:\Users\MikeW" "Folder\FileName" "Folder or file name") do call :ColonBackslash %%I
endlocal
goto :EOF

:ColonBackslash
set "Argument=%~1"

rem Substitute in argument string all colons by nothing and compare this
rem string with unmodified argument string. The argument string contains
rem a colon if the modified and the unmodified strings are not equal.

if not "%Argument::=%" == "%Argument%" (
    echo Colon in argument:        %Argument%
    goto :EOF
)

rem Substitute in argument string all backslashes by nothing and compare
rem this string with unmodified argument string. The argument string
rem contains a backslash if the modified and the unmodified strings
rem are not equal.

if not "%Argument:\=%" == "%Argument%" (
    echo Backslash in argument:    %Argument%
    goto :EOF
)
echo No colon or backslash in: %Argument%
goto :EOF

这个批处理文件输出:

Colon in argument:        C:\Users\MikeW
Backslash in argument:    Folder\FileName
No colon or backslash in: Folder or file name

比较带有替换字符/字符串的字符串与未修改的字符串可以用来确定输入字符串的类型,如下面的批处理代码所示:
@echo off
setlocal EnableExtensions
for %%I in ("C:\Users\MikeW" "C:\Users\Mi:keW" "documentation" "\\server\share\folder" "\\server\share\folder1\..\folder2" "\\server\share\fo:lder" "\\server\" "\\server\share\folder\..\" "%SystemRoot%\..\" ".\Folder" "..\..\Folder" "\Windows" ">:Invalid") do call :classifyInput %%I
endlocal
goto :EOF

:classifyInput
rem Subroutine called with no parameter or with just "" as parameter.
if "%~1" == "" goto :EOF
set "Argument=%~1"
rem Replace all forward slashes by backslashes in argument string.
set "Argument=%Argument:/=\%"

rem Is the second character a colon and third character a backlash?
if "%Argument:~1,2%" == ":\" goto AbsolutePath

rem Is there a colon anywhere else than at second position in argument?
rem C:Folder is interpreted as invalid argument and not as relative
rem path to subfolder "Folder" of current directory on drive C:.
if not "%Argument::=%" == "%Argument%" (
    echo Invalid argument:  %1
    goto :EOF
)

rem Starts the argument with two backslashes?
if "%Argument:~0,2%" == "\\" goto PathUNC

rem Starts the argument with \ for a path relative to current drive?
if "%Argument:~0,1%" == "\" (
    echo Relative path:     %1
    goto :EOF
)

rem Starts the argument with .\ for a path relative to current directory?
if "%Argument:~0,2%" == ".\" (
    echo Relative path:     %1
    goto :EOF
)

rem Starts the argument with ..\ for a path relative to parent directory?
if "%Argument:~0,3%" == "..\" (
    echo Relative path:     %1
    goto :EOF
)

rem Contains the argument a backslash for a path relative to current directory?
if not "%Argument:\=%" == "%Argument%" (
    echo Relative path:     %1
    goto :EOF
)

echo Name without path: %1
goto :EOF

:AbsolutePath
set "Remaining=%Argument:~2%"
rem Is there a colon anywhere else after second position in argument?
if not "%Remaining::=%" == "%Remaining%" (
    echo Invalid argument:  %1
    goto :EOF
)

rem Is the first character a drive letter if second character is a colon?
set "FirstIsLetter="
for /F "delims=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" %%# in ("%Argument:~0,1%") do set "FirstIsLetter=%%#"
if not "%FirstIsLetter%" == "" (
    echo Invalid argument:  %1
    goto :EOF
)

rem Contains the absolute path also .\ or ..\ in path?
if "%Argument:.\=%" == "%Argument%" (
    echo Absolute path:     %1
) else (
    echo Abs. + rel. path:  %1
)
goto :EOF

:PathUNC
rem Does the UNC path contain also a colon?
if not "%Argument::=%" == "%Argument%" (
    echo Invalid argument:  %1
    goto :EOF
)

set "ServerName="
set "ShareName="
set "Remaining="
for /F "tokens=1,2* delims=\" %%A in ("%Argument%") do (
    set "ServerName=%%A"
    set "ShareName=%%B
    set "Remaining=%%C"
)

rem Is there no share name specified after server name in UNC path?
if "%ShareName%" == "" (
    echo Invalid argument:  %1
    goto :EOF
)

rem Is there an invalid share name specified after server name in UNC path?
if "%ShareName%" == "." (
    echo Invalid argument:  %1
    goto :EOF
)

rem Is there an invalid share name specified after server name in UNC path?
if "%ShareName%" == ".." (
    echo Invalid argument:  %1
    goto :EOF
)

rem Contains the UNC path also .\ or ..\ in remaining path?
if "%Remaining:.\=%" == "%Remaining%" (
    echo UNC path:          %1
) else (
    echo UNC  + rel. path:  %1
)
goto :EOF

输出结果为:

Absolute path:     "C:\Users\MikeW"
Invalid argument:  "C:\Users\Mi:keW"
Name without path: "documentation"
UNC path:          "\\server\share\folder"
UNC  + rel. path:  "\\server\share\folder1\..\folder2"
Invalid argument:  "\\server\share\fo:lder"
Invalid argument:  "\\server\"
UNC  + rel. path:  "\\server\share\folder\..\"
Abs. + rel. path:  "C:\WINDOWS\..\"
Relative path:     ".\Folder"
Relative path:     "..\..\Folder"
Relative path:     "\Windows"
Invalid argument:  ">:Invalid"

但是,这个批处理代码不完整,无法检查参数字符串中的无效路径、文件夹或文件名。仍然存在很多可能由该批处理代码未检测到的错误。

为了理解所使用的命令及其工作原理,请打开命令提示符窗口,在其中执行以下命令,并非常认真地阅读每个命令显示的所有帮助页面。

  • call /?
  • echo /?
  • endlocal /?
  • for /?
  • goto /?
  • if /?
  • rem /?
  • set /?
  • setlocal /?

最好让Windows内核检查参数是否指定了现有文件夹或文件,而不管参数字符串是否带有任何类型的路径并且是否有效。请参阅以下主题:

如果要测试不存在的文件夹/文件的有效性,只需创建文件夹/文件,使用2>nul抑制错误消息(请参阅Microsoft文章使用命令重定向运算符),并使用if errorlevel 1if exist“%~1”检查文件夹/文件是否可以成功创建,或者此操作失败是因为无效的文件夹/文件名字符串、拒绝访问、缺少权限等原因。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接