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

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

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—

你可能感兴趣的:(Script)