Windows Batch 1-4

Guide to Windows Batch Scripting

http://steve-jansen.github.io/guides/windows-batch-scripting/index.html

Overview

batch可以帮助配置 DevOps , 提高每天的工作效率.

Part 1 – Getting Started

Launching the Command Prompt

keyboard shortcut Windows Logo Key + R
输入cmd.exe

Editing Batch Files

Windows Logo Key + R 输入 notepadnotepad++
batch文件是 ASCII text, 差不多所有的编辑器都可以用来编辑它;

Viewing Batch Files

可以直接在编辑器中查看batch文件;
在 DOS cmmand中可以使用下列命令查看文件:
- TYPE myscript.cmd
- MORE myscript.cmd
- EDIT myscript.cmd

Batch File Names and File Extensions

建议的后缀名: .cmd
90年代的 Windows使用 .bat

区别:

avoid some rare side effects with .bat files
The differences between .CMD and .BAT as far as CMD.EXE is concerned are: With extensions enabled, PATH/APPEND/PROMPT/SET/ASSOC in .CMD files will set
ERRORLEVEL regardless of error. .BAT sets ERRORLEVEL only on errors.

使用 .cmd后缀名, 你可以使用任何文件名; 建议不要在文件名中加入空格, 在 shell脚本中空格只会带来麻烦;

利用Pascal cast可以帮助避免空格;
e.g. 使用 HelloWorld.cmd代替 Hello World.cmd; 可以使用标点如 . or - or _ (e.g. Hello.World.cmd, Hello-World.cmd, Hello_World.cmd)

另外要注意的是, 避免和 built-in的 command, 系统文件或者流行的程序重名.
e.g. 避免使用 ping.cmd为已经有了一个广泛使用的系统文件 ping.exe ; 如果你无意间运行 ping, 有可能是调用了 ping.cmd而不是真正想要的 ping.exe, 那么情况会变得很令人困惑.
建议使用 RemoteHeartbeat.cmd或在脚本名字中添加一些细节, 这样还能避免和其他的可执行文件发生名字冲突.
当然, 也存在很特殊的情况: 就是你想把ping的默认行为修改掉, 那么可以无视这里的名字规范.

Saving Batch Files in Windows

Notepad默认会尝试将所有文件当做 plain jane text[普通]文件格式保存;
要让 Notepad将文件保存为 .cmd格式, 需要使用 “Save as type”下拉菜单选为 “All Files(.)”;
注意, Encoding选项, 英文一般为 ASCII.
e.g. 文件名设为 %USERPROFILE%\HelloWorld.cmd

%USERPROFILE%关键字是 Windows环境变量, 代表 user profile文件夹的目录地址; 在较新的 Windows系统中, user profile文件夹一般是: C:\Users\;
这个shortcut可以帮你省点时间, 因为一个新的 command prompt通常是默认是在你的 user profile文件夹的 “working directory”目录下;
有了这个shortcut, 就可以不用预先修改当前目录或者指定脚本路径了.

Running your Batch File

运行batch文件的简单方式是双击文件; 不过, 在command prompt中才有机会看到更多的output和error.
当脚本退出的时候, command prompt的窗口会立即消失. ()参见 Part 10 – Advanced Tricks)

运行一个新脚本时, 你可能向要在打开的command窗口中运行batch文件; 对于初学者, 可以直接把脚本拖到command prompt窗口中; command prompt在command line显示出脚本的全路径, 将包含空格的路径用引号括起.

Tips:
- 可以按上下箭头键. 在command line历史中查找用过的命令.
- %COMPSPEC% /C /D "C:\Users\User\SomeScriptPath.cmd" Arg1 Arg2 Arg3
这个命令让脚本在一个新的command prompt 子进程中运行. /C选项表示脚本结束时子进程退出; /D选项将disable auto-run脚本 (可选);
这么做的原因是: 防止command prompt窗口被自动关闭 – 被EXIT命令退出; EXIT命令会自动关闭command prompt窗口, 除非它是在command prompt的子进程中调用的. 窗口被关闭挺烦人的, 因为你来不及看脚本所输出的信息.

Comments

官方定义了 REM(Remark)关键字:

REM This is a comment!

power user方法是使用::, 这是利用两个lable操作符:的hack方式
多数人发现::比起REM来不那么容易分心; 不过要注意有几个地方’::’会造成error.

:: This is a comment too!! (usually!)

e.g. FOR循环会对 ::风格的注释输出error. 简单的回退方案是使用REM

Silencing Display of Commands in Batch Files

第一行非注释的batch命令一般是关闭打印(ECHO)

 @ECHO OFF

@是个特殊操作符, 用来抑制command line中的打印; 一旦将 ECHO设为off, 就不再需要 @操作符了.

恢复命令行打印:

ECHO ON

在退出脚本前, command prompt会自动恢复ECHO的上个状态.

Debugging Your Scripts

batch有许多的 trial和 error coding. 可惜这里没有 WIndows batch script的 debugger(调试器). 更糟的是, 这里也没有将 command processor放入 verbose state来帮助troubleshoot 的方法 (这是Unix/Linux脚本的通用技术).
使用ECHO打印自定义的ad-hoc(专门)信息可能是唯一的选择; 进阶的脚本开发者可以使用一些trickery来选择性地打印信息, 不过还是建议在脚本功能完善的时候把 debugging/instrumentation 代码移除掉.

Part 2 – Variables

变量, 在一个non-trivial batch程序中是必须的; 变量的语法会有些奇怪;

Variable Declaration

DOS无需声明变量. 未声明/未初始化的变量是个空的string, 或者"". 多数人喜欢这个, 因为可以减少代码量; 不过这样也容易出傻bug, 比如变量名有typo(type error打印错误);

Variable Assignment

SET命令将值赋给变量

SET foo=bar

NOTE:不要在名字和之间加入whitespace; SET foo = bar无法工作;

/A可以在赋值时打开arthimetic支持; 这个工具很有用, 可以检验用户输入是否是个数字值;

SET /A four=2+2
4

一般的约定是给变量使用小写名字; 系统级别的变量, 比如环境变量, 使用大写名字; 这些环境描述符指示了系统中某些东西的地址, e.g. %TEMP%是临时文件的目录;
DOS是大小写敏感的, 因此约定虽然没有强制, 但最好是这么配置, 让阅读和调试简单些.

WARNING: SET总是会覆盖(clobber)任何已有的变量; 在写脚本的时候最好先检验一下是否会将系统级别的变量覆盖; 快速方法是 ECHO %foo%, 确认 foo不是个已有变量;
e.g. 你可能想命名一个”temp”变量, 但并不想要改变环境变量“%TEMP%”; DOS包含一些”dynamic”环境变量, 它们更像是命令; 这些dynamic变量包含 %DATE%, %RANDOM%, %CD%; 覆盖这些dynamic变量是个糟糕的主意;

Reading the Value of a Variable

大多数情况下你可以用 %%来读取变量的值; 下面的例子在console中打印出foo变量的当前值:

C:\> SET foo=bar
C:\> ECHO %foo%
bar

有些特殊情况变量不使用%语法;

Listing Existing Variables

不带参数的 SET命令会将当前command prompt会话中的所有变量打印出来; 其中多数是系统级别的环境变量, 如 %PATH% ,%TEMP%.
NOTE: 调用 SET会列出当前对话中所有的 regular(static)变量; 列表中包含 dynamic环境变量如 %DATE%, %CD%; 可以通过SET的帮助文档来查看这些dynamic环境变量: 调用 SET /?.

Variable Scope (Global vs Local)

默认情况下, 变量对于整个command prompt会话来说是global的; 调用 SETLOCAL目录来使得变量对于脚本来变成local; 调用 SETLOCAL后, 变量的赋值会在调用 `ENDLOCAL’, ‘EXIT’或者执行到达脚本的EOF(end of file)之后被revert(恢复);

下例演示了改变一个已有变量名foo, 脚本为 HelloWorld.cmd; shell在HelloWorld,cmd退出时恢复变量%foo%的原始值;

>TYPE HelloWorld.cmd
SETLOCAL
SET v=Local Value
ECHO v=%v%

>SET v=Global Value
>ECHO v=%v%
v=Global Value

>HelloWorld.cmd
>SETLOCAL
>SET v=Local Value
>ECHO v=Local Value
v=Local Value

>ECHO v=%v%
v=Global Value
>

真实世界中的例子可能会是一个修改系统级别%PATH%环境变量的脚本,

>TYPE LocalPath.cmd
SETLOCAL
SET PATH=%SystemRoot%\system32
ECHO %PATH%


>ECHO %PATH%
REM Original %PATH%
"C:\Windows\system32;C:\Windows\System32\WindowsPowerShell\v1.0\"

>LocalPath.cmd
>SETLOCAL
>SET PATH=%SystemRoot%\system32
>ECHO %PATH%
REM %PATH% modified locally
"C:\Windows\system32"

>ECHO %PATH%
REM Original %PATH% restored
"C:\Windows\system32;C:\Windows\System32\WindowsPowerShell\v1.0\"

Special Variables

有少数特殊情况下变量工作起来有些不同; 在command line上传递给脚本的参数也是变量, 然而, 却不使用 %var%语法;
代替的是: 使用单个%带数字0-9来读取每个参数, 每个数字依次代表参数的位置; 在随后的bacth脚本中可以看到一个创建function/subroutine的hack方式有一样的风格;

还有些变量语法使用 !, !var; 这是个调用delayed expansion(延迟扩展)的特殊情况; 在后面讨论 condition(if/then)和looping的时候可以了解更多细节;

Command Line Arguments to Your Script

通过特殊语法可以读取传递给脚本的command line参数;
语法: 单个%字符后面跟参数的位置0-9; zero顺位参数是batch文件的名称; 因此脚本HelloWorld.cmd中变量%0代表”HelloWorld.cmd”;

command line参数:
- %0 脚本/程序名, 总是个非空值
- %1 第一个command line参数, 如果参数没提供, 就为空;
- %2 第二个command line参数, 如果参数没提供, 就为空;
- …
- %9 第九个参数

NOTE: DOS支持超过9个command line参数, 不过你无法直接读取第10个或更多的参数; 因为特殊变量语法不支持 %10或更多; 事实上, shell会将 %10 读作 %0 的后缀 –> sting “0”;
使用 SHIFT目录将第一个参数从参数列表中pop出去, 这样就把所有的参数左移一格; e.g. 第二个参数从位置 %2移到 %1, 这样第10个参数就成了 %9;
后面会看到如何在循环中处理大量的参数;

Tricks with Command Line Arguments

Command Line Arguments也支持一些有用的可选语法, 在command line参数是文件名的时候, 对其运行quasi-macros(类似宏)的操作;
这些 marco被称为 变量substitution support, 可以从command line参数中解析path, timestamp, 或者file size;
对于这个超有用的特性来说文档有些难找 – 运行 FOR /?来查看.

  • %~1对第一个command line参数移除quotes(引号), 这个在参数作为文件路径时很有用; 你会需要在文件路径上加上引号, 但是, 在一个文件路径上两次加上引号会造成 file not found error.
    SET myvar=%~1
  • %~f1 是第一个参数的folder(文件夹)的全路径
  • %~fs1 和上面的一样, 但是多出来的 s选项 yield出 DOS 8.3 short name path到第一个参数中; (e.g. C:\PROGRA~1是常用的8.3 short name变体 – 对于 C:\Program Files)
    在使用第三方脚本或程序的时候这很有用, 这样就无需处理文件路径中的空格了;
  • %~dp1是第一个参数的 parent folder的全路径; 在每个测试脚本文件自身位置的batch文件中都可以用这个trick;
    语法 SET parent=%~dp0 会将脚本的 folder的路径放入变量 ‘%parent%’.
  • %~nx1 代表第一个参数中的文件名以及文件后缀名; 可以用来在运行时检测脚本的名字; 如果需要将信息打印出来, 可以给消息加上脚本名称的前缀: e.g. ECHO %~n0: some message 代替 ECHO some message;
    这里的前缀可以帮到终端用户, 可以知道输出是从脚本打印而不是另一个脚本调用的程序所打印的; 要是花几个小时来追踪一个脚本生成的obtuse error message的话, 其实是挺蠢的. 这是从 Unix/Linux世界中学到的一招改进;

Some Final Polish

在batch脚本顶部加入

SETLOCAL ENABLEEXTENSIONS
SET me=%~n0
SET parent=%~dp0

SETLOCAL命令确保在脚本退出时就不会再clobber已有变量; ENABLEEXTENSIONS参数启用一个很有用的特性, 称为 command processor extension;
%me%中保存的是脚本名(不包括文件后缀); 可以给打印的message加上前缀 (e.g. ECHO %me%: some message)
%parent%中存储的是脚本的parent path(父路径); 可以用来给当前脚本相同目录下的文件提供 fully qualified filepath.

Part 3 – Return Codes

return code是和脚本外部的执行者进行交流的正确方法; 可惜的是很多Windows开发者都忽视了它.

Return Code Conventions

根据约定, 当执行成功, command line execution应该返回 zero, 而执行失败则返回 non-zero; Warning message则不影响return code.

Checking Return Codes In Your Script Commands

环境变量%ERRORLEVEL%包含了最后执行的程序或脚本的 return code; 一个有用的特性: built-in的 DOS命令如 ECHO, IF, SET 会保留现有的 %ERRORLEVEL%的值;

检查 non-zero return code的conventional technique(传统方法)是在 IF命令中使用 NEQ(Not-Equal-To)操作符;

IF %ERRORLEVEL% NEQ 0 (
  REM do something here to address the error
)

另一个通用的方法:

IF ERRORLEVEL 1 (
  REM do something here to address the error
)

当return code是任何数字–等于1或大于1时, ERRORLEVEL 1 语句为true;
不过程序返回正数的同时也有可能返回负数, 这时候这个语句就会有问题;
大多程序不会描述每个可能的return code, 因此最好是显式地检查 non-zero: NEQ 0风格, 而不要假设 return code在error时会是1或大于1;

一些特殊的error code. e.g. 测试一个可执行程序或脚本是在你的PATH中, 调用程序然后检查 return code 9009;

SomeFile.exe
IF %ERRORLEVEL% EQU 9009 (
  ECHO error - SomeFile.exe not found in your PATH
)

不是多查看trial和尝试error的话很难预先了解到这些特殊用法; 记住, 这是 duct tape programming. 它不总是很漂亮, 但可以完成工作;

Conditional Execution Using the Return Code

这里有个超赞的shorthand(便利方法)用来基于第一个命令的成功或失败, 执行第二个命令; 第一个程序/脚本必须遵从成功返回0, 失败返回non-0的规则;

要在成功后执行一个 follow-on(跟随)命令, 使用 && 操作符;

SomeCommand.exe && ECHO SomeCommand.exe succeeded!    

要在失败后执行一个 follow-on命令, 使用 || 操作符;

SomeCommand.exe || ECHO SomeCommand.exe failed with return code %ERRORLEVEL%

和面向对象中的 &&, || 操作符不同, 0 && xxx 竟然是执行xxx, non-0 || xxx 竟然是执行 xxx; 而且 0 || xxx不执行后面的, non-0 && xxx也不执行后面的… 简直是反过来的短路规则

这项技术可以用来在发生错误的时候halt(叫停)脚本; 默认情况下, command processor会在error出现的时候继续执行; 为了实现 halt on error(错误时中止), 不得不要进行编码;

一个halt on error的简单方案是使用 带 /B switch(开关)的 EXIT命令(退出当前的batch脚本内容, 而不是command prompt process);

如果是从外部执行batch脚本, 则还是会退出CMD.exe;

还要从失败的命令中返回一个特定的 non-zero return code来通知调用者:

SomeCommand.exe || EXIT /B 1

类似的方法还有使用 隐式的 GOTO lable :EOF (End-Of-File); 这样跳到EOF可以退出当前脚本, 并且return code为 1;

SomeCommand.exe || GOTO :EOF

Tips and Tricks for Return Codes

推荐成功的 return code都为 zero, 对于 DOS batch文件都返回正值; 正值是因为调用者可能会使用 IF ERRORLEVEL 1 语法来检测脚本;

另外建议给可能的return code都加上文档, 在脚本头上使用便于阅读的 SET语句:

SET /A ERROR_HELP_SCREEN=1
SET /A ERROR_FILE_NOT_FOUND=2

注意这里打破了之前的约定, 使用了大写的变量名 – 这是想表示这些变量是常量, 而且在任何地方都不该被修改; DOS不像 Unix/Linux shell那样可以支持常量值, 这挺糟糕的.

Some Final Polish

一个小小的优化: 按2的幂次定义 return code;

SET /A ERROR_HELP_SCREEN=1
SET /A ERROR_FILE_NOT_FOUND=2
SET /A ERROR_FILE_READ_ONLY=4
SET /A ERROR_UNKNOWN=8

这样就可以有更多灵活性: 使用 bitwise OR(按位或)多个 error number, 可以在一个error code中记录多个问题; 这在interactive(交互式)的情况下很少用到, 但在编写脚本时, 如果你缺乏对目标系统的权限, 就会非常有用;

@ECHO OFF
SETLOCAL ENABLEEXTENSIONS

SET /A errno=0
SET /A ERROR_HELP_SCREEN=1
SET /A ERROR_SOMECOMMAND_NOT_FOUND=2
SET /A ERROR_OTHERCOMMAND_FAILED=4

SomeCommand.exe
IF %ERRORLEVEL% NEQ 0 SET /A errno^|=%ERROR_SOMECOMMAND_NOT_FOUND%

OtherCommand.exe
IF %ERRORLEVEL% NEQ 0 (
    SET /A errno^|=%ERROR_OTHERCOMMAND_FAILED%
)

EXIT /B %errno%

如果 和 都失败了, return code会是 0x2和 0x4的bitwise combination(按位组合), 或者是数字6; 这个 return code告知我们两个 error都出现了; 更进一步, 可以用相同的 error code反复地调用 bitwise OR, 仍然可以解读到哪些 error出现过.

Part 4 – stdin, stdout, stderr

DOS, 就像 Unix/Linux, 使用三个universal “”files” - keyboard input(键盘输入), printing text on screen(屏幕字符输出), printing errors on screen(屏幕错误输出);
“Standard In(标准输入)”文件–stdin, 包含程序/脚本的输入;
“Standard Out(标准输出)”文件–stdout, 用来将输入写到屏幕显示;
“Standard Err(标准错误)”文件–stderr, 包含显示到屏幕上的错误消息;

File Numbers

对于这三个标准文件, 作为standard stream(标准流), 使用数字 0,1,2来引用; stdin是 file 0, stdout是 file 1, stderr是 file 2;

Redirection

batch文件的一个任务是将程序的输出传到 log文件; >操作符send(传送), 或者 redirect(重定向) stdout或 stderr到另一个文件;
e.g. 可以将当前目录结构列表写入文件:

DIR > temp.txt

>操作符会使用从 DIR命令返回的 stdout覆盖 temp.txt的内容; >>操作符是个slight variant(轻量级变量), 它把输出 append到目标文件, 而不是覆盖目标文件.

一个通用的技术是使用 >来创建/覆盖 log文件, 然后使用 >> 在之后append到 log文件中.

SomeCommand.exe   > temp.txt
OtherCommand.exe >> temp.txt

默认情况下, >>>操作符重定向 stdout. 可以在操作符前面使用文件编号 2来重定向 stderr:

DIR SomeFile.txt  2>> error.txt

甚至还可以用文件编号和 &前缀来结合stdout和stderr流:

DIR SomeFile.txt 2>&1

如果想要把 stdout和 stderr都写入同一个文件时这很有用.

DIR SomeFile.txt > output.txt 2>&1

要将文件内容作为一个程序的输入, 代替手动一个个字地从键盘输入, 可以用 <操作符:

SORT < SomeFile.txt

—TBC—
—YCR—

你可能感兴趣的:(Script,Tools)