command-line arguments即 command-line parameters(命令行参数)在batch脚本中可以通过 %1, %2,....,%9
来获取. 可以有多于9个的参数 – 参见 how to loop over all of them.
%0
语法不指向命令行参数, 而是执行batch文件自身.
e.g. 测试是否提供了第一个命令行参数
if not -%1-==-- echo Argument one provided
if -%1-==-- echo Argument one not provided & exit /b
使用 SHIFT
(for each command-line argument, …)来更强健地循环遍历命令行参数:
:argactionstart
if -%1-==-- goto argactionend
echo %1 & REM Or do any other thing with the argument
shift
goto argactionstart
:argactionend
使用 SHIFT
循环遍历命令行参数, 但不修改 %1, %2…
call :argactionstart %*
echo Arg one: %1 & REM %1, %2, etc. are unmodified in this location
exit /b
:argactionstart
if -%1-==-- goto argactionend
echo %1 & REM Or do any other thing with the argument
shift
goto argactionstart
:argactionend
exit /b
将命令行参数传递给环境变量:
setlocal EnableDelayedExpansion
REM Prevent affecting possible callers of the batch
REM Without delayed expansion, !arg%argno%! used below won't work.
set argcount=0
:argactionstart
if -%1-==-- goto argactionend
set /a argcount+=1
set arg%argcount%=%1
shift
goto argactionstart
:argactionend
set argno=0
:loopstart
set /a argno+=1
if %argno% gtr %argcount% goto loopend
echo !arg%argno%! & REM Or do any other thing with the argument
goto loopstart
:loopend
遍历所有命令行参数, 虽然不是个强健(robust)的方案:
for %%i in (%*) do (
echo %%i
)
代码很优雅但不够强健, 没有考虑包含通配符wildcards(*,?
)的参数. 特别是上面的命令会用符合的文件名替换包含通配符(*,?
)的参数, 或者没有文件符合的时候丢弃它们.
不管怎样, 上面的循环在参数不包含通配符的时候可以很好地工作.
e.g. 非强健方式, 找到命令行参数个数
set argcount=0
for %%i in (%*) do set /a argcount+=1
再一次, 对于包含通配符的参数不成立.
按Windows Vista机器的测试经验, 参数个数最大可以达到4000. 在XP和Win7上个数可能不同.
传递参数到batch脚本时, 参数使用的分隔字符可以是:
- space(空格)
- comma(逗号)
- semicolon(分号)
- equal sign(等于号)
- tab character(制表符)
下面的命令行中的参数都是一样的:
- test.bat a b c d
- test.bat a,b,c,d
- test.bat a, b, c, d
- test.bat a;b;c;d
- test.bat a=b=c=d
- test.bat a b,c;,;=d
即使是 a b,c;,;=d
也可以, 一系列的分隔符可以被当做单个分隔符.
要将 space, comma, semicolon放入参数值中, 可以将值包围在引号(quotation marks)中然后传入. 不过引号也变为参数值的一部分.
当引用脚本中的参数时, 要将外面的引号剔除, 可以使用 %~
– 参见Percent tilde
Parameters / Arguments at ss64
Escape Characters, Delimiters and Quotes at ss64
Using batch parameters at Microsoft
许多命令接收文件名通配符–即并不代表自身意义的字符, 而是表示开启了文件名组的匹配模式.
Wildcards(通配符):
- *(asterisk)-星号: 任何字符序列
- ? (question mark)-问号: 单个字符, 除了(“.”), 或者是在一个maximum period-free(无句号)文件名的末尾的一系列问号的一部分, 可能是 zero number of characters(零个字符), 参见澄清的例子.
ex.
- dir *.txt
匹配 Myfile.txt, Plan.txt以及其他有 .txt后缀的文件
- dir *txt
无需加入句号(period). 这可以匹配没有遵循句号约定的文件名, 如 myfiletxt.
- ren *.cxx *.cpp
将所有以 .cxx作为后缀名的文件, 重命名为 .cpp后缀的文件.
- dir a?b.txt
- 匹配 aab.txt, abb.txt. a0b.txt, etc.
- 不匹配 ab.txt, 问号后面跟的字符不是单个问号或句号, 和zero character(零字符)不匹配.
- 不匹配 a.b.txt, 问号无法匹配一个句号.
- dir ???.txt
匹配 .txt, a.txt, aa.txt, aaa.txt. 序列中的问号后面跟句号, 可以和 zero number of characters 匹配.
- dir a???.b???.txt???
匹配 a.b.txt. 当最后一个问号后面没有跟句号, 它仍然是在文件名的maximum period-free部分的一个序列.
- dir ????????.txt & @REM eight question marks
和 *.txt匹配的类似, 因为每个文件都有在 .txt前不超过8字符的短名称(short file name).
短名称的离奇之处:
通配符匹配同时会作用在长文件名, 以及隐藏的 short 8 chars+period+3 chars(8字符名.3字符后缀) 文件名上. 这样的意外情况会引起问题.
不像其他操作系统的 shell, cmd.exe自身不会应用 wildcard expansion(通配符扩展) (将包含通配符的pattern用匹配pattern的文件名替换). 这其实是每个程序处理通配符时的责任.
这可以让 ren *.txt *.bat
这样的命令工作, 因为 ren命令实际上看到的是 *
通配符, 而不是一列匹配通配符的文件. 于是echo *.txt
就不会显示当前文件夹下符合pattern的文件, 而是按字面意思显示 *.txt
.
另一个结果: 可以写 findstr a.*txt
, 无需担心 a.*txt
被一些当前文件夹下的文件名替换掉. 还有, 可以递归findstr /s pattern *.txt
, 在一些其他操作系统中, *txt
部分可能会被所找到的文件名所替换, 从而就无视了嵌套的文件夹.
接受通配符的命令包括: ATTRIB, COPY, DIR, FINDSTR, FOR, REN, etc.
Wildcards at ss64
Using wildcard characters at Microsoft
可以使用下列方法来获得用户输入:
- SET /P 命令
- CHOICE ml
- 使用 type con >myfile.txt
, 对于多行的用户输入, 使用 Ctrl+Z来结束.
当命令行参数(command-line argument )包含文件名, 可以用特殊语法来获取文件的各种信息.
下面的语法扩展了以%1形式传递的文件的各种信息.
Syntax | Expansion Result | Example |
---|---|---|
%~1 | %1去除包围的引号 | n/a |
%~f1 | 带盘符的全路径 | C:\Windows\System32\notepad.exe |
%~d1 | 盘符 | C: |
%~p1 | 末尾带反斜杠的无盘符路径 | \Windows\System32\ |
%~n1 | 对于文件, 是不带路径和扩展名的文件名. 对于目录则是目录名 |
notepad |
%~x1 | 包括句号的文件扩展名 | .exe |
%~s1 | 修改 f, n, x 以使用短名称 | n/a |
%~a1 | 文件属性(attribute) | –a—— |
%~t1 | 文件上次被修改的日期和时间 | 02.11.2006 11:45 |
%~z1 | 文件大小 | 151040 |
%~pn1 | p和 n的组合 | \Windows\System32\notepad |
%~dpnx1 | 多个字母的组合 | C:\Windows\System32\notepad.exe |
%~$PATH:1 | 当前PATH变量下的文件夹中找到的第一个匹配的路径, 没有匹配的话返回空string |
n/a |
%~n0 | 将 %~n应用到 %0; 不带扩展名的batch名称 | tildetest |
%~nx0 | 将 %~nx应用到 %0; batch的名称 | tildetest.bat |
%~d0 | 将 %~f应用到 %0; batch盘符 | C: |
%~dp0 | 将 %~dp应用到 %0; batch的文件夹, 后面跟反斜杠 | C:\Users\Joe Hoe\ |
由 FOR命令创建的相同语法可以作用于single-letter(单字母)变量, 如%%i
. 更多信息可以参见 call /?
或 for /?
.
Parameters / Arguments at ss64
Using batch parameters at Microsoft
for at Microsoft
function(函数)即 subprogram(子程序)可以通过CALL, labels, SETLOCAL, ENDLOCAL来实现.
e.g. 检测 arithmetic power(算术平方)
@echo off
call :power 2 4
echo %result%
rem Prints 16, determined as 2 * 2 * 2 * 2
goto :eof
rem __Function power______________________
rem Arguments: %1 and %2
:power
setlocal
set counter=%2
set interim_product=%1
:power_loop
if %counter% gtr 1 (
set /a interim_product=interim_product * %1
set /a counter=counter - 1
goto :power_loop
)
endlocal & set result=%interim_product%
goto :eof
在function最后的 goto :eof
不是必要的, 通常存在超过一个function的时候他才是必须的.
result变量可以在command line上保存以及指定:
@echo off
call :sayhello result=world
echo %result%
exit /b
:sayhello
set %1=Hello %2
REM Set %1 to set the returning value
exit /b
上例中, exit /b
用来代替 goto :eof
, 效果一样.
Note: equal sign(等号) 是用来分隔参数的. 下面各项都一样:
call :sayhello result=world
call :sayhello result world
call :sayhello result,world
call :sayhello result;world
Batch脚本可以做简单的32-bit integer arithmetic(整数运算) 以及 bitwise manipulation(比特位操作): 使用 SET /a
命令. 支持的最大整数: 2147483647 = 2 ^ 31 - 1
, 最小整数 -2147483648 = - (2 ^ 31)
, 通过赋值的trick来达成: set /a num=-2147483647-1
. 语法和老式C语言一样.
算术操作包括*, /, % (modulo), +, -
. batch中的modulo(取模)必须输入 %%
.
Bitwise操作将数字解译为 32 binary digits(32位二进制数). 它们是 ~
(complement补), &
(and与), |
(or或), ^
(xor异或), <<
(left shift左移), >>
(right shift右移).
negation (取反)的逻辑操作是!
: 0变成1, 非0变成0;
combination(组合操作) ,
: 允许一个set命令中有多个计算.
组合赋值操作的模式为: +=
, 即 a+=b
意思是 a=a+b
; a-=b
意思是 a=a-b
; 类似的: *=, /=, %=, &=, ^=, |=, <<=, and >>=
.
precedence order(有限次序)
1. ( )
2. * / % + -
3. << >>
4. &
5. ^
6. |
7. = *= /= %= += -= &= ^= |= <<= >>=
8. ,
字面量可以按如下输入: 十进制: decimal (1234), 十六进制 hexadecimal (0xffff, leading 0x), 八进制 octal (0777, leading 0).
对于负数, 内部的bit表示是 two’s complement. 在算术运算和bit操作之间提供了一种联系; e.g. -2147483648 用 0x80000000表示, set /a num=~(-2147483647-1)
产生 2147483647, 等于 0x7FFFFFFF(set /a num=0x7FFFFFFF
)
对于命令解释器来说一些操作有特殊含义, 表达式需要用引号包围它们来使用:
set /a num="255^127"
set /a "num=255^127"
set /a num=255^^127
ex.
- set n1=40 & set n2=25
set /a n3=%n1%+%n2%
使用标准百分号来进行变量扩展.
- set n1=40 & set n2=25
set /a n3=n1+n2
有了 /a选项, 就无需在变量名上用百分号包围
- set /a num="255^127"
将 “^”放在引号里面可以防止命令解释器解析到它的特殊含义.
- set /a n1 = (10 + 5)/5
有 /a选项情况下, 等号 =前后的space不起作用.
However, getting used to it lends itself to writing “set var = value” without /a, which sets the value of “var ” rather than “var”.
- set /a n1=2+3,n2=4*7
两次计算
- set /a n1=n2=2
和 n1=2,n2=2有一样效果
- set n1=40 & set n2=25 & set /a n3=n1+n2
和预期的一样.
- set /a n1=2,n2=3,n3=n1+n2
和预期的一样.
- set n1=40 & set n2=25 & set /a n3=%n1%+%n2%
除非n1和 n2预先设置好, 否则无法工作. “%n1%” 和 “%n2%”的变量规格会在第一个 set命令被执行前进行扩展. 去掉百分号则可以工作.
- set /a n1=2,n2=3,n3=%n1%+%n2%
除非n1和 n2预先设置好, 否则无法工作, 和上例的理由一样.
- set /a n1=0xffff
用16进制记号设置 n1.
- set /a n1=0777
用8进制记号设置n1.
e.g. 打印prime numbers(质数):
使用从2到n的开根的数作为除数 http://coolshell.cn/articles/3738.html
@echo off
setlocal
set n=1
:print_primes_loop
set /a n=n+1
set cand_divisor=1
:print_primes_loop2
set /a cand_divisor=cand_divisor+1
set /a cand_divisor_squared=cand_divisor*cand_divisor
if %cand_divisor_squared% gtr %n% echo Prime %n% & goto :print_primes_loop
set /a modulo=n%%cand_divisor
if %modulo% equ 0 goto :print_primes_loop & REM Not a prime
goto :print_primes_loop2
set at ss64.com
set at Microsoft
使用 DIR, FOR, FINDSTR, FORFILES, WHERE 来寻找文件.
ex.
- dir /b /s *base*.doc*
输出当前文件夹下以及子文件夹下的文件, 扩展名前的文件名包含”base”, 扩展名以”doc”开始, 包括”doc”和”docx”. 文件输出全路径, 一行一个文件.
- dir /b /s *.txt | findstr /i pers.*doc
文件包括全路径以及 findstr过滤命令所支持的有限的regular expression(正则表达式)的组合结果, 产生了一个多功能又强大的组合, 可以通过文件名和目录名来寻找文件;
- for /r %i in (*) do @if %~zi geq 1000000 echo %~zi %i
当前文件夹以及子文件夹下, 文件size大于或等于1,000,000 bytes的, 以byte为单位输出文件size, 以及文件全路径. 对于%~zi
语法, 参见 Percent tilde.
- forfiles /s /d 06/10/2015 /c "cmd /c echo @fdate @path"
当前文件夹以及子文件夹下, 在2015/6/10及之后修改过的文件, 打印文件修改日期和全路径. /d后面的日期格式以locale为准. 这样, 即可寻找最近修改的文件.
- (for /r %i in (*) do @echo %~ti :: %i) | findstr 2015.*::
在当前文件夹下递归查找, 输出最后修改日期在2015年内的文件. 将修改日期和事件放在double colon(双冒号::)前面. 只要Windows和locale版本是包含four-digit年份的格式, 就可以工作.
双冒号是为了确保 findstr命令和日期及时间匹配, 而不是文件名.
- for /r %i in (*) do @echo %~ti | findstr 2015 >NUL && echo %i
同上, 在2015年内修改的. 不同的是, 只输出文件, ,没有修改日期.
- findstr /i /s /m cat.*mat *.txt
通过内容查找文件. 通过 正则表达式cat.*mat
实施全文本查找, 条件是所有以 .txt结尾的文件, 输出文件名. /m开关确保只有文件名被输出.
- where *.bat
输出在当前目录以及 PATH目录下所有的 .bat文件.
使用Win+R, 输入cmd.exe, 即可在标准控制台(console)中使用Windows命令行, 这里有许多键盘快捷键, 包括功能键:
上述也被认为是command prompt keyboard shortcuts.
上述快捷键的可用性看起来与运行中的 DOSKEY无关.
Windows Keyboard shortcuts at ss64.com
doskey at Microsoft
有些任务可以用Perl one-liners方便地完成. Perl脚本语言源自另一个操作系统. 由于许多Windows环境安装了Perl, Perl one-liners对Windows batch脚本来说是自然兼容的后缀.
ex.
- echo "abcbbc"| perl -pe "s/a.*?c/ac/"
让Perl像sed(流编辑器)一样, 功能是使用正则表达式, 以支持文本替换.
- echo a b| perl -lane "print $F[1]"
Perl当做cut命令, 显示列中第二个区域内容, 本例为 b. 使用 $F[2]
显示第三块区域; 索引从0开始. Native方案是 FOR /f
.
- perl -ne "print if /\x22hello\x22/" file.txt
Perl当做grep 或 FINDSTR, 输出file.txt中符合if后面正则表达式的各行. Perl regular expressions比FINDSTR强大许多.
- perl -ne "$. <= 10 and print" MyFile.txt
Perl当做 head -10 command, 输出文件中的前10行.
- perl -e "sleep 5"
等待5秒.
- for /f %i in ('perl -MPOSIX -le "print strftime '%Y-%m-%d', localtime"') do @set isodate=%i
将ISO格式的当前时间放入 isodate变量.
- perl -MWin32::Clipboard -e "print Win32::Clipboard->Get()"
将文本内容输出到剪贴板. 把命令存储到 getclip.bat中, 就有了一个方便的 getclip命令来实现 CLIP.
在网上, Perl one-liners经常在另一个操作系统中的command-line convention中贴出, 用单引号apostrophe (‘) 代替 Windows的quotation marks双引号. 这些都需要在Windows中调整.
Perl One-Liners by Peteris Krumins at github.com
Why doesn’t my Perl one-liner work on Windows? at stackoverflow.com
W:One-liner program#Perl
没有和Linux类似的 touch命令. 这里的 touch会修改文件的 last-modification timestamp (最后修改的时间戳), 而不会改变其内容.
workaround, 可读性和可适应性较模糊:
- copy /b file.txt+,,
Windows recursive touch command at superuser.com
Windows version of the Unix touch command at stackoverflow.com
—TBC—
—YCR—