Suppressing Program Output
pseudofile(伪文件)NUL
是用来丢弃程序的输出的. e.g. 通过针对 loopback address调用 ping, 模拟Unix命令 sleep
. 通过将 stdout重定向到 NUL
设备来防止将 output打印到command prompt屏幕上.
PING 127.0.0.1 > NUL
Redirecting Program Output As Input to Another Program
假设你想要将程序的output链接起来作为input传送给另一个程序中. 这种方式被称为是 “piping”输出到另一个程序, 使用的是 pipe character(管道符) |
.
e.g. 将DIR命令的输出进行排序:
DIR /B | SORT
A Cool Party Trick
可以通过command line重定向command prompt自己的 stdin来快速创建一个新的文本文件, 比如batch script: 调用 CON
, 到一个文本文件; 使用 CTRL+Z
来完成输入, 它会发送 end-of-file(EOF)字符:
TYPE CON > output.txt
[在
CTRL+Z
之前需要输入换行]
DOS中有多种特殊文件可以重定向, 不过, 其中大部分是过时的, 如 LPT1 – parallel portt printers 或 COM1 for serial devices(串行设备) 像是 modem.
Part 5 – If/Then Conditionals
DOS对 if/then/else conditions有很好的支持.
Checking that a File or Folder Exists
IF EXIST "temp.txt" ECHO found
反之:
IF NOT EXIST "temp.txt" ECHO not found
两者都有:
IF EXIST "temp.txt" (
ECHO found
) ELSE (
ECHO not found
)
NOTE: 在两边的IF检查operands(操作数)上都加上引号是个好主意; 这可以防止麻烦的bug: 当变量不存在的时候, 会造成operand在事实上消失, 造成语法错误.
Checking If A Variable Is Not Set
IF "%var%"=="" (SET var=default value)
或者
IF NOT DEFINED var (SET var=default value)
Checking If a Variable Matches a Text String
SET var=Hello, World!
IF "%var%"=="Hello, World!" (
ECHO found
)
或者大小写不敏感的比较:
IF /I "%var%"=="hello, world!" (
ECHO found
)
[用
NOT
表示!
]
Artimetic Comparisons
SET /A var=1
IF /I "%var%" EQU "1" ECHO equality with 1
IF /I "%var%" NEQ "0" ECHO inequality with 0
IF /I "%var%" GEQ "1" ECHO greater than or equal to 1
IF /I "%var%" LEQ "1" ECHO less than or equal to 1
[/A 表示表达式 expression]
Checking a Return Code
IF /I "%ERRORLEVEL%" NEQ "0" (
ECHO execution failed
)
Part 6 – Loops
在collection(集合)中进行looping(循环)遍历是个常见任务, 可以对文件或目录进行循环, 或者对文本文件的每一行进行循环;
Old School with GOTO
old-school(老式的)lopping方法在早期DOS中是使用 label和 GOTO语句. 现在不多见了, 虽然它对command line参数的循环遍历还是有用的.
:args
SET arg=%~1
ECHO %arg%
SHIFT
GOTO :args
New School with FOR
对文件或文本进行循环遍历的现代方式是 FOR
命令; FOR
可以说是DOS中最有用的一个命令.
GOTCHA: FOR
命令使用特殊的 %
语法, 后面跟单个的字母, 比如 %I
. 这个语法和 FOR
在 batch文件中使用的时候稍有不同, 因为它需要额外的一个百分号, 或者 %%I
. 在编写脚本的时候这是个常见的错误源头.
最好在遇到非法语句时退出循环, 确保你使用的是%%
风格的变量
Looping Through Files
FOR %I IN (%USERPROFILE%\*) DO @ECHO %I
Looping Through Directories
FOR /D %I IN (%USERPROFILE%\*) DO @ECHO %I
递归地循环遍历 %TEMP%文件夹下面所有子文件夹下的文件:
FOR /R "%TEMP%" %I IN (*) DO @ECHO %I
递归地循环遍历%TEMP%文件夹下面所有子文件夹:
FOR /R "%TEMP%" /D %I IN (*) DO @ECHO %I
Variable Substitution for Filenames
[FOR %I IN (%USERPROFILE%*) DO @ECHO %I – 其中的 %I]
Part 7 – Functions
Function实际上是procedural (过程式)编程语言中代码重用的方式; 当DOS缺乏真正好用的 function关键字的时候, 可以利用 label和 CALL
关键字来伪造一个.
gotchas:
1. quasi function(拟函数)要像label一样定义在脚本的末尾.
2. 脚本的主逻辑必须具备一个 EXIT /B [errorcode]
语句; 这防止main logic(主逻辑)不会由于你的function失败而停止(fall through into).
[/B 退出当前脚本而不是退出 CMD.exe, 如果是在外部执行则退出 CMD.exe]
Defining a function
下例中, 会实现一个poor man版本的脚本: *nix tee utility – 将信息写到文件和stdout流中. 在function中使用一个对脚本来说 global的变量, %log%.
@ECHO OFF
SETLOCAL
:: script global variables
SET me=%~n0
SET log=%TEMP%\%me%.txt
:: The "main" logic of the script
IF EXIST "%log%" DELETE /Q %log% >NUL
:: do something cool, then log it
CALL :tee "%me%: Hello, world!"
:: force execution to quit at the end of the "main" logic
EXIT /B %ERRORLEVEL%
:: a function to write to a log file and write to stdout
:tee
ECHO %* >> "%log%"
ECHO %*
EXIT /B 0
Calling a function
使用 CALL关键字来调用 quasi function, 标签为 :tee
; 可以像调用另一个batch文件一样在 command line上传递参数.
必须要记得在function底部加上 EXIT /B
关键字; 遗憾的是, 除了返回exit code之外无法返回任何其他东西.
Return values
CALL
的返回值总是function的 exit code. 就像其他可执行文件的调用一样, 调用者通过读取 %ERRORLEVEL%
来获得 exit code.
可以有创造性地传递任何东西作为return code, 不仅仅是数字. function可以 ECHO
到 stdout, 让调用者决定如何处理output, 将output作为input通过pipeling(管道传输)到另一个可执行文件, 重定向到文件或者通过 FOR
命令解析.
调用者也可以通过修改 global变量来传递数据, 不过最好避免这种方式.
Part 8 – Parsing Input
强健的解析力是区分好脚本和了不起的脚本的分界线.
The Easy Way to read Command Line Arguments
目前为止解析command line参数的最简单方式是通过ordinal position(顺位)读取想要的参数.
这里我们从第一个参数获取到本地文件的路径. 如果文件不存在就写下错误消息, 退出脚本:
SET filepath=%~f1
IF NOT EXIST "%filepath%" (
ECHO %~n0: file not found - %filepath% >&2
EXIT /B 1
)
Optional parameters
给参数设置一个默认值
SET filepath=%dp0\default.txt
:: the first parameter is an optional filepath
IF EXIST "%~f1" SET filepath=%~f1
Switches
@ECHO OFF
SET /P COLOR="Choose a background color (type red, blue or black): "
2>NUL CALL :CASE_%COLOR% # jump to :CASE_red, :CASE_blue, etc.
IF ERRORLEVEL 1 CALL :DEFAULT_CASE # if label doesn‘t exist
ECHO Done.
EXIT /B
:CASE_red
COLOR CF
GOTO END_CASE
:CASE_blue
COLOR 9F
GOTO END_CASE
:CASE_black
COLOR 0F
GOTO END_CASE
:DEFAULT_CASE
ECHO Unknown color "%COLOR%"
GOTO END_CASE
:END_CASE
VER > NUL # reset ERRORLEVEL
GOTO :EOF # return from CALL
http://stackoverflow.com/questions/18423443/switch-statement-equivalent-in-windows-batch-file
Named Parameters
@ECHO OFF
SET man1=%1
SET man2=%2
SHIFT & SHIFT
:loop
IF NOT "%1"=="" (
IF "%1"=="-username" (
SET user=%2
SHIFT
)
IF "%1"=="-otheroption" (
SET other=%2
SHIFT
)
SHIFT
GOTO :loop
)
ECHO Man1 = %man1%
ECHO Man2 = %man2%
ECHO Username = %user%
ECHO Other option = %other%
REM ...do stuff here...
:theend
http://stackoverflow.com/questions/3973824/windows-bat-file-optional-argument-parsing
Variable Number of Arguments
myapp foo bar
shell
- # -> 2
-* -> foo bar
- 0?>myapp?1 -> foo
- $2 -> bar
batch
- ?? -> 2 <—- what command?!
- %* -> foo bar
- %0 -> myapp
- %1 -> foo
- %2 -> bar
set argC=0
for %%x in (%*) do Set /A argC+=1
echo %argC%
https://en.wikibooks.org/wiki/Windows_Programming/Programming_CMD
Reading user input
:confirm
SET /P "Continue [y/n]>" %confirm%
FINDSTR /I "^(y|n|yes|no)$" > NUL || GOTO: confirm
Part 9 – Logging
在运行时和运行后都可以使用log工具来查错. 有时候比如在查看服务器端log的时候可能刷新太快来不及用肉眼观察, 此时, 一份简单的 log文件即可解决问题.
Log function
在Part7中有个基本的 tee
实现:
@ECHO OFF
SETLOCAL ENABLEEXTENSIONS
:: script global variables
SET me=%~n0
SET log=%TEMP%\%me%.txt
:: The "main" logic of the script
IF EXIST "%log%" DELETE /Q %log% >NUL
:: do something cool, then log it
CALL :tee "%me%: Hello, world!"
:: force execution to quit at the end of the "main" logic
EXIT /B %ERRORLEVEL%
:: a function to write to a log file and write to stdout
:tee
ECHO %* >> "%log%"
ECHO %*
EXIT /B 0
这个tee
quasi function可以把输出写到console(控制台)或者log文件中; 这里重用了 log文件路径, 保存在用户的 %TEMP%
文件夹下面, 文件名为 batch文件名加上 .txt后缀.
如果想为每次执行都保留一份log, 可以简单地解析出 %DATE%
和 %TIME%
变量(利用command line扩展)来产生一个独特的文件名(至少在1秒钟的维度上是唯一的)
REM create a log file named [script].YYYYMMDDHHMMSS.txt
SET log=%TEMP%\%me%.%DATE:~10,4%_%DATE:~4,2%_%DATE:~7,2%%TIME:~0,2%_%TIME:~3,2%_%TIME:~6,2%.txt
除了从 *nix world学到的队列(queue)的方式, 这里还包含一个预定义的自定义output: script: some message
. 这个技术对理清在有error的case中谁在complain极有帮助.
Displaying startup parameters
对于 non-interactive(非交互式)的脚本, 可以显示各种运行时的情况(runtime condition), 重定向到一个build log中.
现在还不知道是否有 DOS trick来辨别是 non-interactive会话还是 interactive会话; C#和 .Net有 System.Environment.UserInteractive
属性来检测这种情况; *nix有些trick, 可以用 tty file descriptor(文件描述符).
或许可以hack一个方案, 通过观察custom environment variable(自定义环境变量), 比如 %MYSCRIPT_DEBUG%
默认是 false的.
http://stackoverflow.com/questions/8514735/what-is-special-about-dev-tty
Part 10 – Advanced Tricks
Boilplate info
建议在所有脚本都加上header, 记录 who/what/when/why/how. 可以使用 ::
的注释技巧来描写文档:
:: Name: MyScript.cmd
:: Purpose: Configures the FooBar engine to run from a source control tree path
:: Author: stevejansen[email protected]
:: Revision: March 2013 - initial version
:: April 2013 - added support for FooBar v2 switches
@ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
:: variables
SET me=%~n0
:END
ENDLOCAL
ECHO ON
@EXIT /B 0
Conditional commands based on success/failure
条件操作符 ||
和 &&
提供了方便的捷径来基于第一个命令的成功或失败来运行第二个命令.
&&
语法是 AND操作符, 当第一个命令返回zero(成功)exit code时运行第二个命令.
DIR myfile.txt >NUL 2>&1 && TYPE myfile.txt
||
语法是 OR操作符, 当第一个命令返回non-zero(失败)exit code时运行第二个命令.
DIR myfile.txt >NUL 2>&1 || CALL :WARNING file not found - myfile.txt
还可以组合两种方式. 注意如何使用 ()
组来用 &&
在第一个命令失败时, 创建运行两个命令的脚本:
DIR myfile.txt >NUL 2>&1 || (ECHO %me%: WARNING - file not found - myfile.txt >2 && EXIT /B 1)
Getting the full path to the parent directory of the script
:: variables
PUSHD "%~dp0" >NUL && SET root=%CD% && POPD >NUL
Making a script sleep for N seconds
使用 PING.EXE
来伪造一个 *nix风格的 sleep
命令.
:: sleep for 2 seconds
PING.EXE -N 2 127.0.0.1 > NUL
Supporting “double-click” execution (aka invoking from Windows Explorer)
测试 %CMDCMDLINE%
是否和 %COMSPEC%
相等. 如果相等, 就可以认为是允许在一个交互式对话中. 如果不相等, 可以在脚本的最后插入一个 PAUSE, 以显式output. 你可能需要目录改变到一个合法的工作目录中.
@ECHO OFF
SET interactive=0
ECHO %CMDCMDLINE% | FINDSTR /L %COMSPEC% >NUL 2>&1
IF %ERRORLEVEL% == 0 SET interactive=1
ECHO do work
IF "%interactive%"=="0" PAUSE
EXIT /B 0
—END—
—YCR—