跳转到内容

Windows 编程/编程 CMD

来自 Wikibooks,开放世界中的开放书籍

在 Windows NT(XP、Vista、7、8、10 等)中,可以使用命令提示符 (cmd.exe) 编写批处理文件。它们可用于自动化文件系统任务,如备份或基本安装,也可与其他命令行实用程序结合使用。批处理文件可以看作是一种简单的脚本语言,包含逻辑和跳转。使用批处理文件的优势在于编写简单,无需编译即可编辑文件,跨 Windows NT 操作系统兼容,并且由于其基于 MS-DOS,因此具有操作文件系统的能力。批处理文件脚本不区分大小写,但字符串和数据区分大小写。该文件由一系列命令组成,这些命令按行分隔并像在命令提示符下运行的普通命令一样运行,尽管某些功能有所不同。批处理文件可以从 Windows 资源管理器运行,但用于显示它们的控制台在批处理文件结束时会自动关闭,因此需要在最后添加一个命令来防止立即退出,以便在控制台窗口关闭之前读取任何剩余的输出。尽管批处理文件能够操作其环境,例如颜色设置和环境变量,但所有更改都是临时的,就像在标准命令提示符会话中一样。但是,颜色设置在 Windows NT 的后续版本中会被保留。为了尝试学习有关批处理文件的信息,了解命令提示符命令非常有用。请参阅:Windows 命令指南

批处理文件

[编辑 | 编辑源代码]

脚本保存在一个批处理文件中,扩展名为 .bat 或 .cmd。虽然 .bat 更为人所知,因为它曾用于命令提示符之前的 MS-DOS 环境,但命令提示符对批处理文件的解释方式与解释 DOS 批处理文件的方式大不相同,而且 .cmd 文件只能由命令提示符解释,因此使用 .cmd 扩展名可以防止在较旧的环境中被误用。

执行从文件顶部开始,到文件末尾结束。当文件结束时,如果文件是从命令提示符中调用的,则退出到命令提示符;如果文件是从 Windows 资源管理器或 START 命令调用的,则控制台窗口关闭。

ECHO 命令

[编辑 | 编辑源代码]

通常,批处理文件以 'echo off' 命令开头,这会停止在执行期间显示输入和提示符,因此只会显示命令输出。'@' 符号会阻止命令显示输入和提示符。它用在 'echo off' 命令上,以防止第一个命令显示输入和提示符

@ECHO OFF

为了打印行,再次使用 ECHO 命令,但这次使用除 'off' 之外的文本参数

ECHO.Hello World!

句点('.')用于防止与尝试输出 ON 或 OFF 混淆,而不是打开和关闭输入和提示符显示。代码中的最后一行应该是

ECHO ON

'Echo on' 会重新打开输入/提示符显示,以防程序退出到命令提示符,如果没有 'echo on' 命令,将不会留下可见的提示符供使用。

提示:从 Windows XP 开始,ECHO ON 命令是可选的。命令解释器在 BAT 文件终止后会自动启用它。

Hello World 示例

[编辑 | 编辑源代码]

使用上面的代码,我们可以创建一个 Hello World 程序,如下所示

@ECHO OFF
ECHO Hello World!
ECHO ON

在批处理文件中,有两种方法可以编写注释。首先是这种形式

REM Comment here.

这种形式被包含在内,因为它存在于 MS-DOS 批处理文件脚本中。另一种形式是这种

::Comment here.

这种形式通常更受青睐,因为它执行和编写速度更快,并且易于与普通命令区分开来。对于这种类型的注释,只需要两个双冒号('::'),注释在行尾结束。批处理文件没有多行注释类型。

您也可以在命令末尾添加注释

command &::Comment here.

在批处理脚本中,所有变量都是存储在系统内存中的字符串。 使用 SET 命令分配变量。以这种方式分配的变量在系统环境中与预定义的全局系统变量一起存在,直到定义它们的環境终止,无论是通过使用 ENDLOCAL 命令还是关闭命令提示符会话。应注意避免覆盖 'PATH' 'TIME' 'DATE' 等全局环境变量,因为其他程序可能依赖于它们具有 '正确' 的值。

  • 在使用 'SETLOCAL' 命令创建了子环境的环境中使用 'GOTO :EOF' 命令意味着并等效于 'ENDLOCAL' 命令。
SET name=John Smith

此命令创建一个名为 name 的环境变量,并将它的值设置为字符串 "John Smith"。第一个 '=' 符号两侧的空格包含在变量引用名称(在本例中为 'name')和变量值的分配中 [变量名可以包含空格!]。变量值的尾随空格也会被分配,如果未检测到,这会导致某些脚本意外失败。因此,建议将变量的分配用双引号括起来

SET "name=John Smith"

通常使用 '%' 字符标记变量引用字符串的开头和结尾,在其他命令中使用变量。

ECHO %name%

在批处理脚本中,使用 '%' 扩展展开的变量在执行该行或代码块之前由解释器 [解析]。因此,这意味着命令将使用变量在该代码段开始之前保存的值执行。这在使用代码块时尤其重要,因为变量的值在该代码块内被重新分配。批处理脚本中的代码块是指使用 '&' 将多个命令链接到同一行,以及在任何括号代码中,例如 'IF' 语句和 'FOR' 循环。

下面是一个演示此问题存在的示例

@Echo off
Set "count=0"
For /L %%n in (1 1 3)Do (
 Set /A count+=1
 Echo %count%
)
Echo %count%

输出

0
0
0
3

有两种方法可以解决这个问题。第一个也是最常用的方法是启用延迟扩展,这允许使用 '!' (延迟) 扩展展开变量

@Echo off
Set "count=0"
SETLOCAL EnableExtensions EnableDelayedExpansion
For /L %%n in (1 1 3)Do (
 Set /A count+=1
 Echo !count!
)
Echo %count%

输出

1
2
3
3

使用延迟扩展会导致变量在执行期间展开,而不是在解释器最初解析命令时展开。

在代码块中扩展变量当前值的第二种方法是使用 'CALL' 命令,在执行命令期间有效地重置解析器,以便读取当前值。

@Echo off
Set "count=0"
For /L %%n in (1 1 5)Do (
 Set /A count+=1
 Call Echo %%count%%
)
Echo %count%

调用方法速度明显较慢,但对于处理可能包含 '!' 字符的字符串很有用,解释器在启用延迟扩展时会尝试将这些字符解析为变量。它不适合包含插入符号 '^' 的字符串,因为调用命令会将调用后命令字符串中出现的插入符号加倍。

使用延迟扩展允许使用具有唯一索引的公共引用名称定义 '关联' 变量,以模拟数组。这是可能的,因为在启用延迟扩展的情况下,变量的扩展分为两个步骤。首先,展开 '%' 变量,然后展开 '!' 变量。例如

@Echo off
 Set "str[1]=one,three,five"
 Set "str[2]=two,four,six"
 Setlocal EnableExtensions EnableDelayedExpansion
rem for each in 1 2
 For %%i in (1 2)Do (
rem reset sub index variable
  Set "{i}=0"
rem for each in variable str[index]
  For %%G in (!str[%%i]!)Do (
rem increment sub index count
   Set /A {i}+=1
rem define element '{i}' from str[index] to str[index][subindex]
   Set "str[%%i][!{i}!]=%%G"
  )
 )
Set Str
Goto :Eof

输出

str[1]=one,three,five
str[1][1]=one
str[1][2]=three
str[1][3]=five
str[2]=two,four,six
str[2][1]=two
str[2][2]=four
str[2][3]=six

set 命令也可以用于输入

SET /P var=Enter a value for var:

此命令显示 "Enter a value for var:",当用户输入数据时,var 将被赋予该值。

请注意,如果用户按下 Enter 键但不输入任何内容,则 var 中的值将保持不变,因此为了提示,最好给出默认值,或者如果变量之前被使用过,则先清除变量的值

SET var=
SET /P var=Enter a value for var:

下面是一个示例

@ECHO OFF
SET /P answer= Enter name of file to delete: 
DEL /P %answer%
ECHO ON

此批处理文件获取要删除的文件的名称,然后使用带有提示参数 '/P' 的 DEL 命令询问用户是否确定要删除该文件。

流程控制

[编辑 | 编辑源代码]

条件语句

[edit | edit source]

IF 命令可以在批处理文件中创建程序逻辑。IF 命令允许三种基本检查:ERRORLEVEL、两个字符串的相等性和文件或文件夹的存在性。对 ERRORLEVEL 的第一个检查将查看它是否大于或等于某个特定数字

IF ERRORLEVEL 5 ECHO.The ERRORLEVEL is at least 5.

对于这种样式,第一个参数始终是 ERRORLEVEL,第二个参数是它检查的值。在本例中,如果 ERRORLEVEL 至少为 5,那么该行末尾的命令将被执行,输出消息“ERRORLEVEL 至少为 5”。第二种形式是在两个字符串之间进行检查

IF "%str1%"=="Hello." ECHO.The strings are equal.

这里,第一个参数是两个字符串,它们位于双等号 (==) 的两侧,表示检查它们是否相等。如果变量 str1 恰好等于“Hello.”,这是一个区分大小写的检查,那么将输出“字符串相等”。如果你希望进行不区分大小写的检查,你可以按如下方式重写它

IF /I "%str1%"=="Hello." ECHO.The strings are equal.

现在,例如,str1 可以包含“HELLO.”,但检查仍然会导致该行末尾的命令被执行,因为检查现在不区分大小写。最后一种基本 IF 类型是存在性检查,用于查看文件或文件夹是否存在。

IF EXIST myfile.txt TYPE myfile.txt

这里,如果文件“myfile.txt”存在于当前文件夹中,那么命令 TYPE myfile.txt 将被执行,该命令将在控制台窗口中显示“myfile.txt”的内容。

所有前面的示例都有一个可选的 NOT 参数,可以在 IF 后面写入,如果条件为真,则将在该行末尾执行该命令。例如

IF NOT EXIST myfile.txt ECHO.File missing.

如果文件“myfile.txt”不存在于当前文件夹中,则将输出“文件丢失”。还有一些其他的 IF 类型带有命令扩展,你可以通过在命令提示符下使用 IF /? 命令查看。

ELSE 运算符可以与括号组合使用,以提供多行逻辑语句,如果条件不为真,则提供一组备用命令。

IF condition (
   commands to be executed if the condition is true 
) ELSE (
   commands to be executed if the condition is false
)

与一些语言不同,在批处理文件中,脚本要求 IF condition () ELSE () 行按这种非常特定的方式编写。但是,可以将其重写为在同一行上使用单行结果

IF condition (command if true) ELSE command if false

以下是一个 ELSE 运算符使用示例

@ECHO OFF
::Prompt for input.
SET /P answer=Enter filename to delete: 
IF EXIST %answer% (
 DEL /P %answer%
) ELSE (
 ECHO.ERROR: %answer% can not be found in this folder!
)
ECHO ON

这个批处理文件将删除一个文件,除非它不存在,在这种情况下它会用消息“ERROR: %answer% 在此文件夹中找不到!”告诉你。

与大多数计算机语言不同,多行 IF...ELSE 样式语句不能嵌套在批处理文件中。

跳转

[edit | edit source]

你可以使用 GOTO 语句来控制程序流程。批处理文件没有结构化编程脚本的所有元素,但是可以模拟一些结构化编程元素,比如函数。但是,控制程序流程的最简单方法是 GOTO 语句,它跳转到指定的标签。

GOTO labelnam

此代码将程序流程引导到标签 labelnam,该标签位于此行的第一次出现处

:labelnam

重要的是要记住,标签只存储 8 个字符,所以如果一个标签超过 8 个字符,只有前 8 个字符会被看到。这意味着标签 labelname1 和 labelname2 无法区分,因为它们的区别只出现在前 8 个字符之后。虽然并不严格错误,但最好避免使用超过 8 个字符的标签名称,以避免这些容易区分的问题。

以下是之前示例的重新设计版本,它会循环直到被要求停止

@ECHO OFF

:prompt
::Clear the value of answer ready for use.
SET answer=
SET /P answer=Enter filename to delete (q to quit): 

IF EXIST %answer% (
 DEL /P %answer%
 GOTO prompt
)
IF /I "%answer%"=="q" GOTO :EOF

::By this point an error must have occurred as all
::the correct entries have already been dealt with.
ECHO.ERROR: Incorrect entry!
GOTO prompt

ECHO ON

请注意 GOTO :EOF 命令。此命令将脚本带到文件末尾并结束当前批处理脚本。

FOR 循环

[edit | edit source]

对一组文件中的每个文件运行指定命令。

   FOR %variable IN (set) DO command [command-parameters]
 %variable  Specifies a single letter replaceable parameter.
 (set)      Specifies a set of one or more files.  Wildcards may be used.
 command    Specifies the command to carry out for each file.
 command-parameters
            Specifies parameters or switches for the specified command.

要在批处理程序中使用 FOR 命令,请指定 %%variable 而不是 %variable。变量名称区分大小写,因此 %i 与 %I 不同。

批处理文件示例

 for %%F IN (*.txt) DO @echo %%F

此命令将列出当前目录中所有以 .txt 结尾的文件。

如果启用了命令扩展,则支持以下 FOR 命令的额外形式

   FOR /D %variable IN (set) DO command [command-parameters]

如果 set 包含通配符,则指定与目录名称而不是文件名匹配。

   FOR /R [[drive:]path] %variable IN (set) DO command [command-parameters]

遍历以 [drive:]path 为根的目录树,在树中的每个目录中执行 FOR 语句。如果在 /R 后没有指定目录规范,则假定为当前目录。如果 set 只是一个句点 (.) 字符,那么它只会枚举目录树。

   FOR /L %variable IN (start,step,end) DO command [command-parameters]

set 是从 start 到 end 的一系列数字,步长为 step amount。因此 (1,1,5) 将生成序列 1 2 3 4 5,而 (5,-1,1) 将生成序列 (5 4 3 2 1)

   FOR /F ["options"] %variable IN (file-set) DO command [command-parameters]
   FOR /F ["options"] %variable IN ("string") DO command [command-parameters]
   FOR /F ["options"] %variable IN ('command') DO command [command-parameters]

或者,如果存在 usebackq(或 useback)选项

   FOR /F ["options"] %variable IN ("file-set") DO command [command-parameters]
   FOR /F ["options"] %variable IN ('string') DO command [command-parameters]
   FOR /F ["options"] %variable IN (`command`) DO command [command-parameters]

(usebackq 的目的是使用包含空格的文件集的完整名称。)

filenameset 是一个或多个文件名。每个文件都会被打开、读取和处理,然后继续处理 filenameset 中的下一个文件。处理包括读取文件,将其分解成单独的文本行,然后将每行解析成零个或多个标记。然后调用 for 循环的主体,并将变量值设置为找到的标记字符串。默认情况下,/F 传递每个文件每行中第一个空格分隔的标记。空行将被跳过。你可以通过指定可选的“options”参数来覆盖默认的解析行为。这是一个包含一个或多个关键字以指定不同解析选项的引号字符串。关键字是

   eol=c           - specifies an end of line comment character
                     (just one)
   skip=n          - specifies the number of lines to skip at the
                     beginning of the file.
   delims=xxx      - specifies a delimiter set.  This replaces the
                     default delimiter set of space and tab.
   tokens=x,y,m-n  - specifies which tokens from each line are to
                     be passed to the for body for each iteration.
                     This will cause additional variable names to
                     be allocated.  The m-n form is a range,
                     specifying the mth through the nth tokens.  If
                     the last character in the tokens= string is an
                     asterisk, then an additional variable is
                     allocated and receives the remaining text on
                     the line after the last token parsed.
   usebackq        - specifies that the new semantics are in force,
                     where a back quoted string is executed as a
                     command and a single quoted string is a
                     literal string command and allows the use of
                     double quotes to quote file names in
                     filenameset.

一些示例可能会有所帮助

   FOR /F "eol=; tokens=2,3* delims=, " %i in (myfile.txt) do @echo %i %j %k

将解析 myfile.txt 中的每一行,忽略以分号开头的行,将每一行中的第 2 个和第 3 个标记传递给 for 主体,标记以逗号和/或空格分隔。请注意,for 主体语句引用 %i 以获取第 2 个标记,%j 以获取第 3 个标记,%k 以获取第 3 个标记后的所有剩余标记。对于包含空格的文件名,你需要用双引号引用文件名。为了以这种方式使用双引号,你还需要使用 usebackq 选项,否则双引号将被解释为定义要解析的文字字符串。

%i 在 for 语句中被明确声明,%j 和 %k 通过 tokens= 选项被隐式声明。你可以通过 tokens= 行指定最多 26 个标记,前提是它不会导致尝试声明一个高于字母 'z' 或 'Z' 的变量。请记住,FOR 变量是单字母的、区分大小写的、全局的,并且在任何时候你都最多只能有 52 个活动的 FOR 变量。

你还可以使用 FOR /F 解析逻辑在立即字符串上,通过将括号之间的 filenameset 设为一个用单引号括起来的字符串。它将被视为来自文件的单行输入并进行解析。

最后,你可以使用 FOR /F 命令来解析命令的输出。你可以通过将括号之间的 filenameset 设为一个反引号字符串来做到这一点。它将被视为一个命令行,传递给一个子 CMD.EXE,并且输出会被捕获到内存中并进行解析,就好像它是一个文件一样。因此,以下示例

   FOR /F "usebackq delims==" %i IN (`set`) DO @echo %i

将枚举当前环境中的环境变量名称。

此外,FOR 变量引用的替换也得到了增强。你现在可以使用以下可选语法

   %~I         - expands %I removing any surrounding quotes (")
   %~fI        - expands %I to a fully qualified path name
   %~dI        - expands %I to a drive letter only
   %~pI        - expands %I to a path only
   %~nI        - expands %I to a file name only
   %~xI        - expands %I to a file extension only
   %~sI        - expanded path contains short names only
   %~aI        - expands %I to file attributes of file
   %~tI        - expands %I to date/time of file
   %~zI        - expands %I to size of file
   %~$PATH:I   - searches the directories listed in the PATH
                 environment variable and expands %I to the
                 fully qualified name of the first one found.
                 If the environment variable name is not
                 defined or the file is not found by the
                 search, then this modifier expands to the
                 empty string

可以将修饰符组合起来以获得复合结果

   %~dpI       - expands %I to a drive letter and path only
   %~nxI       - expands %I to a file name and extension only
   %~fsI       - expands %I to a full path name with short names only
   %~dp$PATH:I - searches the directories listed in the PATH
                 environment variable for %I and expands to the
                 drive letter and path of the first one found.
   %~ftzaI     - expands %I to a DIR like output line

在上面的示例中,%I 和 PATH 可以用其他有效值替换。%~ 语法以有效的 FOR 变量名称结尾。选择大写变量名称,比如 %I,可以使它更易读,并避免与修饰符(不区分大小写)混淆。

管道

[edit | edit source]

这主要用于将一个程序的输出重定向到另一个程序

a | b

表示执行“a”,并将“a”提供给控制台的所有输出作为“b”的输入

dir | find ".htm"

将提供一个包含“.htm”的文件列表

命令的输出以及可能发生的错误也可以重定向到文件(注意 >> 前面的 2)

ACommand >>TheOutputOfTheCommandLogFile.log 2>>TheErrorOutputOfTheCommandFile.log

函数

[edit | edit source]

可以使用标签来控制执行流程,并使用环境变量来返回结果返回值,从而模拟函数。 标签可以在脚本中的任何位置定义,不需要引用。 当遇到标签后的代码时,将执行该代码。 由标签标识的代码块通常最方便地放置在主脚本退出后(文件末尾),这样它们就不会意外运行,而只会在 GOTO 或 CALL 语句的目标时才会到达。

子程序结构之所以有效,是因为以下原因

  • 可以使用内置命令 GOTO 跳转到由标签标识的代码
  • 使用带有标签的内置命令 CALL 会为同一个脚本创建一个新的命令处理器调用,并隐式 GOTO 该标签
  • 使用表达式 GOTO :EOF 会关闭当前命令处理器调用(与 EXIT /B 相同,只是后者允许指定 ERRORLEVEL)

子程序调用的结构如下所示

CALL :subroutine1 param1 param2 ...
ECHO %result% was returned from subroutine1

CALL :subroutine2 param1 param2 ...
ECHO %result% was returned from subroutine2

GOTO :EOF
REM The above line ends the main invocation of the command processor and so exits the script

:subroutine1 
  SETLOCAL
  commands using parameters %1, %2, .... and setting %retval%
  ENDLOCAL & SET result=%retval% 
  GOTO:EOF
  REM The above line ends the child invocation of the command processor and so returns to just after CALL subroutine1 in the main script

:subroutine2 
  SETLOCAL
  commands using parameters %1, %2, .... and setting %retval%
  ENDLOCAL & SET result=%retval% 
  GOTO:EOF
  REM The above line ends the child invocation of the command processor and so returns to just after CALL subroutine2 in the main script


具有以下内容的 Bat 文件将输出“42”作为执行结果

:: describes and calls function for multiplication with 2 arguments
@ECHO OFF
CALL :multiply 21 2 
ECHO %result% 

:multiply
SETLOCAL
set retval=0
set left=%1
set right=%2
:: use '/A' for arithmetic
set /A "retval=left*right" 
ENDLOCAL & SET result=%retval% 
GOTO :EOF

注意

  • 使用带有标签的 CALL 或表达式 CALL :EOF 或 EXIT /B 需要启用命令扩展。
  • 标签是一行,由一个有效名称(不包含任何分隔符,例如空格或分号)开头,并以冒号开头。

命令行界面

[edit | edit source]

假设我们要从命令提示符调用程序“MyProgram”。 我们在提示符中键入以下内容(.exe 文件扩展名不必要)

C:\>myprogram.exe

这将运行 myprogram 可执行文件。 现在,假设我们要向该程序传递一些参数

C:\>myprogram arg1 arg2 arg3

现在,如果我们进入标准 main 函数,我们将拥有 argc 和 argv 值

int main(int argc, char *argv[])

其中

argc = 4
argv[0] = "myprogram" (the name of the program - deduct 1 from argc to get the number of arguments)
argv[1] = "arg1"
argv[2] = "arg2"
argv[3] = "arg3"

对于熟悉标准 C 编程的人来说,这并不奇怪。 但是,如果我们将此转换为 WinMain 函数,我们将得到不同的值

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR CmdLine, int iCmdShow)

我们只有一个值来接受命令行

CmdLine = "myprogram arg1 arg2 arg3".

我们还可以使用函数 **GetCommandLine** 从应用程序中的任何位置检索此字符串。 如果我们想将其解析为标准的 argv/argc 对,我们可以使用函数 **CommandLineToArgvW** 来执行转换。 重要的是要注意,CommandLineToArgvW 仅适用于 unicode 字符串。

当我们从 C 程序返回一个值时,该值将传递给 CMD shell,并存储在一个名为“ERRORLEVEL”的变量中。 ERRORLEVEL 是唯一一个不是字符串的全局变量,它可以包含一个从 0 到 255 的数字。 按照惯例,值为零表示“成功”,而其他值则表示错误。

假设我们要编写一个 C 程序来返回传递给它的参数数量。 这在 C 中听起来像是一个简单的任务,但在批处理脚本中很难实现

int main(int argc, char *argv[])
{
   return (argc - 1);
}

我们将这个程序命名为“CountArgs.exe”。 现在,我们可以将其放入批处理脚本中,传递一些参数,并打印出传递的数量

countargs.exe %*
ECHO %ERRORLEVEL%

我们可以将此脚本命名为“count.bat”,并从命令提示符运行它

C:\>count.bat arg1 arg2 arg3

运行它将返回答案:3。

注意:实际上,这可以通过批处理文件实现,而无需使用 C 程序,只需使用 CMD 延迟变量扩展通过“/V:ON”参数即可

/V:ON   Enable delayed environment variable expansion using ! as the 
delimiter. For example, /V:ON would allow !var! to expand the variable 
var at execution time.  The var syntax expands variables at input time, 
which is quite a different thing when inside of a FOR loop.

然后使用以下简单的批处理文件来计算参数

set COUNT=0
for %%x in (%*) do ( set /A COUNT=!COUNT!+1 )
echo %COUNT%

或者一个更简单的方法,无需启用和使用“延迟环境扩展”,只需执行以下操作

set COUNT=0
for %%x in (%*) do set /A COUNT+=1
echo COUNT = %COUNT%


这将返回与 C 程序示例相同的答案。

控制台 控制处理程序

[edit | edit source]
华夏公益教科书