if 命令在所有编程语言中都很常见,它只会在条件为真时执行一行或多行代码,而只有在条件为假时才会执行另一段代码。

其基本原理很简单,但是在批处理中,条件子句为true或false的实体与其他语言中的类似子句有很大不同。大多数比较操作符都是批处理所独有的,在本文中,我们将学习确定路径或文件是否存在以及是否填充变量的语法。理解计算返回代码的不同技术也很重要。

此外,我们还将了解如何有效地管理需要评估多个条件的实例,以及需要避免的一些常见问题。编写一个 if 命令非常容易,它在大多数情况下都能正常工作,但在某些条件下要么中止,要么无法按预期执行。

if 命令的基础

在其最基本的形式中,如果条件为真,if 命令执行一行或多行代码。我将演示如何使用该命令,以便在相同条件为假的情况下执行不同的代码,不过首先让我们从它的基本结构开始。

几乎每个批处理命令实现都以命令名本身开头,后面通常跟着参数和/或选项。例如,set命令总是以这三个字母开头。通常,它后面跟一个由变量名、等号和值组成的参数,但在前面的文章中,我们了解到它在没有参数或选项的情况下也可以工作。(这样的命令会输出一个可用变量的列表。)

if 命令是特殊的。它也是以它的命令名开始,但相似之处仅限于此;它可以跨行,有两个主要组成部分。下面是其语法结构的一般形式:

if 条件 (
	条件为true时执行的代码块
)
  • 1.
  • 2.
  • 3.

条件是一个计算结果为true(真)false(假)的表达式。如果它为true,解释器执行true代码块中的一条命令或多条命令,如果它为false,则不执行该代码。关于代码块的内容我们后续再深入的讨论,但是现在,我们只需了解代码块是括号之间的一行或多行代码。

使用这种语法,左括号不仅必须跟在条件后面,同时它们也必须在同一行上。其他语言允许您将左括号放在下一行,并将其与右括号对齐,但这在批处理中是禁止的。但是,良好的格式要求右括号应与 if 命令的开头对齐,中间的命令应缩进。我的习惯是要求缩进三个空格,但其实任何数量的空格都可以。这里是一个例子:

if "%today%" equ "2024-09-01" (
   set event=sestercentennial
)
  • 1.
  • 2.
  • 3.

条件子句"%today%" equ "2024-09-01"正在查找已解析变量和一些硬编码文本之间的相等性。这个条件子句相当简单,但我们很快就会使用不同的比较操作符、关键字甚至一个选项来演示更令人印象深刻的条件子句。许多编程语言将条件子句放在括号内;无论好坏,该子句在批处理中独立存在,括号用于括住即将执行的代码块。

如果条件子句为真,则执行代码块中的命令(在本例中为 set 命令),从而将 event 设置为 sestercentennial。

以下使用更紧凑的单行格式,在功能上等同于前面的示例:

if "%today%" equ "2024-09-01"  (set event=sestercentennial)
  • 1.

当使用单行时,可以不再需要括号。下面的代码在功能上等同于前面的两个例子:

if "%today%" equ "2024-09-01"  set event=sestercentennial
  • 1.

从技术上讲,由于缺少括号,set 命令不再位于代码块中。现在,它不再是一个简洁的术语,而是当条件子句为真时执行的命令。

在前面的两个例子中,我在条件子句后面留了两个空格,而且我经常留两个以上的空格。从语法上讲,这是不需要的,但是因为没有什么东西能够清楚地描述条件子句和它后面的东西,所以稍微分开一点可以增强可读性。

你甚至可以用&命令分隔符在一行上执行多个命令,如前面文章所述:

if "%today%" equ "2024-09-01"  set event=sestercentennial& set code=ugly
  • 1.

如果条件子句为真,则执行一个附加的set命令。我偶尔看到使用这种技术,通常是在设置错误代码和错误消息时,但是在这种情况下,使用单行只会使代码难以理解。

如果在执行的逻辑中有任何有意思的内容,请使用多行代码:

if "%today%" equ "2024-09-01" (
   set event=sestercentennial
   set code=elegant
)
  • 1.
  • 2.
  • 3.
  • 4.

在这里说这段代码很好倒不一定,但我推荐您使用该方式使代码更具可读性。读者只需看一眼就知道,如果条件为真,则设置了两个变量。

条件子句

前一节中的示例都使用了简单的条件子句,但该子句可以更加动态,采用许多其他形式并使用不同的操作符和关键字。

比较运算符

比较操作符比较两个操作数是否相等或其中一个大于另一个。您可能已经猜到前面示例中的equ操作符表示相等。下面是批处理比较操作符的完整列表:

  • equ 或者 == 相等
  • neq 不相等
  • lss 小于
  • leq 小于或等于
  • gtr 大于
  • geq 大于或等于

对于相等运算符,您可以在两个功能等效的备选方案之间进行选择。为了区别于用于赋值的单等号,例如在set命令中,批处理使用双等号作为比较操作符。我更喜欢的语法是equ操作符,因为它看起来与其他操作符相似,但有些编码人员出于相反的原因更喜欢==操作符。

如果比较的两个操作数不相等,则neq操作符将条件子句计算值为true。最后四个操作符确定哪个操作数大于或小于另一个操作数。例如,假设age被设置为一个数值,只有当变量被设置为大于12的值时,下面的一行代码块才会执行:

if %age% gtr 12 (
   rem 这里执行年龄大于12的代码块
   > con echo You are greater than 12 years old.
)
  • 1.
  • 2.
  • 3.
  • 4.

你可能想尝试%age% > 12作为条件子句,但是大于号在批处理中已经有了明确的用途;实际上,它在这个代码块中用于向控制台写入一条消息。因此,必须使用gtr作为操作符。类似地,本节中列出的操作符用于大于等于、小于和小于等于比较。

不那么直观的是,这些运算符也适用于字母数字值。所有的数字都小于所有的字母;a小于A;A小于b;b小于B;等等......。这种规则会存在各种讨论,但至少在批处理的世界中,Picard 比 Kirk 更大。

条件子句关键字

您将在 if 命令的帮助中找到以下不可或缺的关键字,但请不要搞错;这些关键词是特定于条件子句的:

exist 关键字检查是否存在路径或文件,如果找到则返回true。您可以硬编码路径或文件,或者为了灵活性,您可以使用包含潜在路径或文件的变量:

if exist D:\Batch\myFile.txt  set do=something
if exist %pathAndFile%        set do=something
  • 1.
  • 2.

您还可以将多个变量串在一起以构建路径或文件名。

defined 下面的条件子句使用defined关键字检查变量是否已定义—也就是说,它是否解析为任何内容,甚至是一个空格?一个常见的错误是在变量周围使用百分号,但以下是使用该关键字的正确语法:

if defined varThatMayBeEmpty  set do=something
  • 1.

这在功能上相当于下面用百分号解析变量:

if "%varThatMayBeEmpty%" neq ""  set do=something
  • 1.

这个关键字通常用于验证预期的输入变量。如果没有一个或多个 defined,则可以采取适当的操作,可能会启动中止。(即判断一个或多个变量是否存在,若不存在则做一些操作或者直接中止程序的启动)

not 关键字在条件子句的开头使用时,否定任何条件子句。这在为尚未被其他人或其他事物设置的变量设置默认值时非常有用。例如,下面的代码确保将skyColor设置为其通常的颜色:

if not defined skyColor  set skyColor=BLUE
  • 1.

可以将not关键字与exist关键字相结合,以确定特定文件是否存在。有了这些知识,您就可以创建一个文件,启动或中止,或者做任何对您的应用程序有意义的事情。一些编码人员将not关键字与equ操作符一起使用,但我发现这其实是有问题的,我更喜欢单独使用neq操作符。逻辑上没有区别,但无论你的偏好是什么,都要保持一致。

警告:

经过几年的批处理编码,我仍然经常尝试在exist关键字后面添加s,尽管我不愿意承认。notepad++每次都会忠实地提醒我,因为它增加了关键字;这个冒牌字符会将整个单词的粗体去掉,使其更加突出。

不区分大小写的选项

if 命令只有一个选项,就像我们到目前为止看到的关键字一样,它适用于条件子句。/i 选项使条件子句中的相等(和不相等)运算符不区分大小写。

为了演示,下面 if 命令中不带选项的条件子句只有在 myMood 解析为 happy时才计算为true:

if "%myMood%" equ "happy"  set do=something
  • 1.

下面是添加了/i选项的相同代码:

if /i "%myMood%" equ "happy"  set do=something
  • 1.

如果变量解析为HAPPY、Happy、happy或其他可能排列中的任何一种,那么条件子句现在的计算结果为true。

注意:

/i 选项可能看起来与我已经提到的和即将出现的其他选项有些不同。如前所述,我对批处理命令使用小写,尽管大小写对解释器无关紧要。在任何情况下,选项都可以工作。尽管如此,由于选项通常只是一个正斜杠后面跟着一个字符,我通常将它们大写以示强调。但是根据字体的不同,大写的I通常看起来像小写的L,所以我对/I选项的使用违背了我的个人习惯,它通常与 if 命令一起使用。是的,具有讽刺意味的是,这是与大小写不敏感有关的选项。

errorlevel 变量

在调用可执行文件或执行许多批处理命令之后,返回代码存储在errorlevel伪环境变量中。更多关于伪环境变量的信息我们将在后面的文章中进行介绍,但是现在,将errorlevel视为一个包含返回代码的变量,您不应该使用set命令设置它。(如果这样做,就会破坏errorlevel变量。)errorlevel变量可以像批处理中的其他变量一样作为 if 命令的一部分进行计算。例如,以下代码将返回码大于等于1视为失败:

if %errorlevel% geq 1  set msg=FAILURE
  • 1.

批处理还支持一种比较旧的语法,这种语法只适用于这个唯一的变量,其中删除了百分号和相等运算符。下面的代码在功能上等同于前面的例子:

if errorlevel 1  set msg=FAILURE
  • 1.

乍一看,这似乎很简单,很吸引人,因为内容被删除了,没有添加任何内容,但语法掩盖了一个令人惊讶的问题。许多批处理编码器错误地将此条件子句解释为寻找返回码与1之间的相等性。毕竟,用返回码0正确地测试它将返回false,用返回码1正确地测试它将返回true。但是条件子句errorlevel 1等价于%errorlevel% geq 1和%errorlevel% gtr 0。对于所有正整数,它的计算结果为真。

将此语法与not关键字结合使用,以实现真正不透明的内容:

if not errorlevel 0  set msg=The Return Code is NEGATIVE
  • 1.

这看起来不像一个条件子句,当返回码不等于0时,它的值为真吗?它实际上是返回码大于等于0的负数。%errorlevel% lss 0条件子句在功能上是等效的,而且可读性强得多。缺少比较操作符的语法的另一个问题是,通常情况下,0表示良好的返回代码,而所有其他代码,包括负值,表示存在某种问题。

neq 操作符使得该条件子句对所有非零值都为真:

if %errorlevel% neq 0  set msg=FAILURE
  • 1.

您可能会遇到晦涩难懂的语法,因此理解它的工作原理很重要,但更重要的是不要扩展它。总是使用百分号(或感叹号)和比较运算符来计算errorlevel伪环境变量。

if...else 结构

编码语言的不成文规则之一是if命令必须带有else关键字的可能性。我前面提到的关键字与条件子句相关联,但是这个关键字与if命令本身相关联。这是if...else的常见结构:

if 条件子句 (
   rem 条件子句为true时执行
   > con echo true
) else (
   rem 条件子句为false时执行
   > con echo false
)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

前三行和第四行开始的右括号与我们在本文开头展示的一般形式相同。else关键字紧随其后,其后是设置在第二组括号之间的false代码块。这表示当条件子句计算结果为false时执行的代码。

这里有一个简单的 if...else 构造:

if %fahrenheit% gtr 70 (
   set pants=shorts
) else (
   set pants=jeans
)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

如果华氏温度的变量大于70,则pants变量被设置为shorts。否则,将pants变量设置为jeans。一个或另一个代码块将始终执行。

你可以将这个结构压缩成一行代码:

if %fahrenheit% gtr 70 (set pants=shorts) else (set pants=jeans)
  • 1.

在false代码块中,代码两边的括号在技术上是可选的,但为了可读性,应该包括它们。

除非你忽视那些将要阅读你代码的人,否则一行 if…else 构造通常是不好的做法,尽管您可能会对最简单的任务产生例外。

与其他语言不同,在批处理中,else 关键字不能单独编码在一行中;它甚至不能作为一行的开始或结束。为了清楚地划分这两个代码块,最好将夹在左括号和右括号之间的关键字编码成一行。

else if 结构

if…else 逻辑流中有两个分支,一个表示真,一个表示假时,else结构非常好。当有两个以上的分支时,else if 结构允许使用多个条件子句。以下代码有三个子句和四个分支,每个条件子句对应一个分支,当所有子句的计算结果都不为真时执行一个默认分支。

if %fahrenheit% gtr 80 (
   set pants=shorts
) else if %fahrenheit% gtr 60 (
   set pants=light khakis
) else if %fahrenheit% gtr 32 (
   set pants=jeans
) else (
   set pants=lined jeans
)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

这个逻辑假设华氏温度被设置为一个描述温度的整数。如果它大于80度,则执行第一个set命令。如果它大于60,也就是说在61到80(包括61和80)之间,则执行第二个set命令。如果前两个条件子句为假,并且水银显示在冰点以上,则执行第三个set命令。如果三个子句都为假,则温度为32度或更低,因此第四个也是最后一个set命令分配一条非常暖和的裤子。

在第一个else关键字后面没有左括号。相反,它后面跟着另一个带有自己的条件子句的if命令,并且只有在if命令后面加上左括号。

代码包含另外两个if子句,但是您可以根据需要编写任意数量的代码。解释器执行与计算结果为true的第一个条件子句对应的代码块;然后,控制跳转到整个结构的末尾,而不计算其他子句。

很多时候,如果条件子句都不为真,则需要执行最终代码块——即默认代码块。例如,如果代码未设置某个变量,则某人有可能在没有适当着装的情况下离开房子。最后一个else关键字后面没有if命令,因此它的代码块,即默认代码块,在它之前的三个条件子句都不为true时执行。

在代码中,正在询问fahrenheit以确定它落在四个范围中的哪一个。这是else if条件从句的常用用法,但它们不必如此紧密地联系在一起。每个条件子句可以查询完全不同的变量,或者使用前面介绍的三个关键字。例如,下面是对上述代码的重新构想,其中只更改了三个条件子句:

if /i "%season%" equ "Summer" (
   set pants=shorts
) else if exist D:\Batch\Spring.txt (
   set pants=light khakis
) else if %celsius% gtr 0 (
   set pants=jeans
) else (
   set pants=lined jeans
)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

第一个条件子句执行不区分大小写的比较,以确定已解析变量和硬编码文本是否相等。第二个子句查找文件是否存在,第三个子句查看celsius变量的值是否高于冰点。同样,由于默认代码块,此代码保证将变量设置为四个值之一。

增强的相等判断方法

如果我没有提到与这些条件子句相关的一个重要问题,那就是我的疏忽。在本文的一些示例中,我在条件子句中用双引号把等式的两边都括起来,但是没有它们,大多数情况下命令仍然可以工作。下面两个if命令非常相似,但在功能上并不等同:

if /i "%myMood%" equ "happy"  set do=something
if /i %myMood% equ happy  set do=something
  • 1.
  • 2.

如果myMood设置为happy,则该子句的计算结果为true,如果设置为sad,则结果为false。无论哪种方式,它都适用于两个命令。

这很好,但是现在假设变量没有被设置,或者它被设置为null或一些空格。没有双引号的命令将崩溃,但它暗示了以下神秘消息的问题(假设你没有使用前面文章中提到的echo off命令):

happy was unexpected at this time.

D:\Batch> if /i equ happy  set do=something
  • 1.
  • 2.
  • 3.

这里的第一行是错误消息,后面是让解释器感到困惑的内容。要理解错误,我们必须像解释者那样思考。一旦它看到if命令开始行,它就会从有限的项列表中取出一个(可能不存在、不存在、不定义或/i),任何无法识别的都假定是条件子句的左侧。显然,它找到了/i。假设三个关键字中的一个没有出现在后面,解释器现在就会按照特定的顺序期待三个项目:一些文本;equ、neq或==等运算符;还有一些文字。如果myMood的值几乎是任何东西,它将解析为第一个文本字段。然后,解释器会很高兴地找到equ运算符,并且知道它正在处理一个等式,它会将硬编码的happy解释为该等式的右侧。这里表示成功执行。

当变量解析为零或任意数量的空格时,这一切都会崩溃。解释器看到if /i开始语句,所以它不希望接下来看到的是equ。关键字not是有意义的,但不是equ。因此,它错误地认为equ是可能是等式的左边,后面是运算符。但接下来是happy文本,而操作符列表显然不包含这个词。正如消息所述,编译器不希望在这个时候看到happy。至此表示执行失败。

幸运的是,有两种计算变量的方法,否则可能什么也解决不了。

前置点的方法

解决这个问题的一种常用技术是在等式的两边加上一个点,或者几乎任何字符,只要它被一致地应用,这样解释器肯定会在等式的两边找到一些东西:

if /i .%myMood% equ .happy  set do=something
  • 1.

当变量被设置为null时,前面的点可以很好地工作,因为该命令解析为if /i . equ .happy。这个点不等于后面跟单词的点,所以它的计算结果是false,然后继续。如果将变量设置为happy,则该命令将被解析为if /i .happy equ .happy,并且将找到相等。则执行成功。

但我不喜欢这种方法,因为它很容易受到另一个bat的优化。现在想象一下,将变量设置为两个单词的情绪,比如烦躁地沮丧,这在许多与批处理代码无关的级别上并不好。再执行一次,它崩溃了:

depressed was unexpected at this time.
D:\Batch> if /i .irritably depressed equ .happy set do=something
  • 1.
  • 2.

别灰心。解释器被嵌入的空格受骗了。别告诉任何人,但这真的没那么好。它认为。irritably是子句的左侧,而发现depression是一个完全意想不到的操作符。执行失败。但还有另一种方法。

双引号的方法

兜了一圈,这把我们带回到等式两边都加了双引号的例子:

if /i "%myMood%" equ "happy"  set do=something
  • 1.

这里的双引号在等式两边提供了一些东西,就像点一样,但这不是全部。

解释器将双引号内的所有内容视为一个实体。当包含嵌入空格的变量被解析后,解释器看到如下:

if /i "irritably depressed" equ "happy"
  • 1.

尽管有嵌入的空间,解释器将“irritably depressed”视为一个实体,或者在这种情况下是等式的左边,右边是“happy”。结果是批处理正确地识别出这两个实体不相等。计算成功。

如果查询的字母数字变量可能没有设置,或者它可能包含嵌入的空格,那么我几乎总是用双引号把子句的两边都括起来。但是,您可能已经注意到,在条件子句中使用%errorlevel%时,我没有使用双引号。该变量将始终被设置为一个数字,因此不需要引号。更重要的是,当解释器看到没有引号的数字比较时,它会进行数字比较,这意味着000等于0。添加引号会导致文本比较,并且“000”不等于“0”。

前置点和双引号

当比较字母数字值时,我通常用双引号把等式的两边都括起来。当值为null时,它会工作;当一个值是一个或多个空格时,它起作用;当值有嵌入的空格时,它会起作用;它适用于更典型的非空无空格值。然而,我之所以使用不确定限定符,通常是因为一个非常细微的问题。

考虑一个变量的情况,它包含一个末尾有空格的值。也许值sad用尾随空格填充以形成一个四个字符的值。它是否等于三个字符的值sad?在最纯粹和最准确的意义上,不,它们是不相等的——正确使用双引号方法会发现它们是不同的。但是在不太严格的情况下,您可能认为这些值是相等的。

使用点的方法将发现两个值之间的相等,因为尾随空格变成了等式左侧和运算符之间的另一个空格。在这个尾随空格的狭义实例中,点方法更好,但它只在变量没有任何嵌入空格的情况下才有效。

在最后的分析中,双引号方法远远优于点的方法,有一个非常独特的情况下,它不是。养成在几乎所有非数字比较中使用双引号的习惯。

总结

if 命令在几乎所有编码语言中都很有用,批处理也不例外。在本文中,我们学习了条件子句,包括用于比较两个操作数的有效操作符和用于证明变量、路径或文件存在的关键字,以及当子句的计算结果为true和false时发生的情况。还学习了如何对多个子句求值,以便有条件地执行多个逻辑分支。

通常情况下,批处理会给您更多的考虑,因此我详细介绍了一些有用的方法,以增强您的条件子句对字母数字和数字值的比较。但是,是什么使一个值成为字母数字或数字呢?