WindowsBatchScripting_B

本文介绍了批处理脚本中命令行参数的使用方法,包括参数的获取、循环遍历、传递给环境变量等技巧,并详细讲解了通配符的使用、用户输入方法、算术运算及函数定义等内容。

Command-line arguments

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)中然后传入. 不过引号也变为参数值的一部分.
当引用脚本中的参数时, 要将外面的引号剔除, 可以使用 %~<number> – 参见Percent tilde

Parameters / Arguments at ss64
Escape Characters, Delimiters and Quotes at ss64
Using batch parameters at Microsoft

Wildcards

许多命令接收文件名通配符–即并不代表自身意义的字符, 而是表示开启了文件名组的匹配模式.
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

User input

可以使用下列方法来获得用户输入:
- SET /P 命令
- CHOICE ml
- 使用 type con >myfile.txt, 对于多行的用户输入, 使用 Ctrl+Z来结束.

Percent tilde

当命令行参数(command-line argument )包含文件名, 可以用特殊语法来获取文件的各种信息.
下面的语法扩展了以%1形式传递的文件的各种信息.

SyntaxExpansion ResultExample
%~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
%~pn1p和 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

Functions

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
    参见 Command-line arguments

Calculation

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

Finding files

使用 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文件.

Keyboard shortcuts

使用Win+R, 输入cmd.exe, 即可在标准控制台(console)中使用Windows命令行, 这里有许多键盘快捷键, 包括功能键:

  • Tab: 当前文件夹下的文件名, 文件夹名补全. 可补全的部分通常是最后的 space-free(无空格)部分, 但如果用了引号就不一样了. 通常的命令都能补全文件和文件夹, 但 cd命令只对文件夹起作用.
  • Up Down arrow: 从command历史中搜索命令, 每次一个.
  • Escape: 擦除当前输入的命令.
  • F1: 一个个字符地打出commad历史中上次输入的命令.
  • F2: 请求输入一个字符, 会出现command 历史中的上个命令的最短的前缀, 其中不包含输入的那一个字符. e.g. 前一个命令: echo Hello world, 如果输入 o, 就出现 ech.
  • F3: 进入command历史中的前一个命令. 重复F3不起作用.
  • F4: 请求输入一个字符, 以当前光标位置开始, 删除一直到所输入的字符(不包括)中间的部分.e.g. 如果输入 echo Hello world, 光标在 H前, F4输入 w, 结果就是 echo world.
  • F5: 进入command历史中的前一个命令.
  • F6: Control+Z 字符.
  • F7: 弹出一个command历史的字符列表, 可以用Up,Down选择命令, 回车直接执行.
  • F8: 给出一个字符串, 会在command历史中搜寻以此字符串为前缀的命令以显示, 每次一个.
  • F9: 输入数字, 按序号执行command历史中的命令.
  • Alt + F7: 清除command历史.

上述也被认为是command prompt keyboard shortcuts.
上述快捷键的可用性看起来与运行中的 DOSKEY无关.

Windows Keyboard shortcuts at ss64.com
doskey at Microsoft

Perl one-liners

有些任务可以用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

Limitations

没有和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—

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值