[XP SP2 中文]
DOS批处理非常低劣, 但那是你不用安装 ActivePerl等等东西就可以在Windows 上用来干一些小事的工具. 因为MS的缘故, 还不得不用.
1. @echo off
命令执行过程中都会被输出, 有可能你debug时, 希望它输出, 但正常运行时却不需要
echo off 可以关闭输出, 但注意这个命令本身却会被输出,
@echo off 可以让它该命令本身也不输出.
@command_name 是通用技法, 不管echo off/on 的设置如何, 都不显示命令
2. echo 的内容
echo on/off 是特殊的.
但很多其它东西不特殊, 使惯了bash你会希望它至少把 "" 给解释成arguments delimeter, 但它不会, 原样输出.
在使用echo 把东西送到管道上时, 注意这一点
echo a | grep "^a$"
不会匹配, 因为grep看到的内容是从a开始, 到|之前的内容, 包括那个空格, 更让人受不了的, 是
echo 之后只能有一个空格, 多余的空格也是内容本身的一部分, 有例为证:
C:\WINDOWS>echo a | sed "s#.*#[&]#"
[ a ]
C:\WINDOWS>echo a | sed "s#.*#[&]#"
[a ]
C:\WINDOWS>echo a | sed "s#.*#[&]#"
[ a ]
对于|<> 这些特殊字符, 要想原样输出, 需要用^
echo ^<^^>
你才会看到
<>
3. set var=content can contains spaces
不能用"" 引起来, 很类似echo 对后面参数的解释.
4. for 命令使用 %a 还是 %%a
在命令行上使用%a, 不能使用%%a
在.bat 中写时就得用 %%a, 不能用%a
这让你不能把.bat的内容copy到命令行上验证, 很不爽.
5. for命令中套的命令, 如要使用管道, 重定向, 得用^去除|<>的特殊含义
for /f "tokens=1 delims=<" %%a in ('echo %target_dir%^| sed -e s#\\#/#g') do set sed_target_dir=%%a
注意看上面|之前的^, 必需的, 它不象bash一样理解嵌套.
6. for 系列命令的语义
6.1 for %I in (file1.txt *.dat) do @echo %I
语义是: ()中的内容被视为空格分隔的文件列表, 支持通配符. 也就是说, 默认的for是让你遍历当前目录下的文件的. 连续的多个空格或视为单个的分隔符.
相当于:
dir file1.txt *.dat
6.2 for /D %I in (*.dat) do @echo %I
与6.1 中相比, 多出的/D 意为目录, 即把 *.dat理解作对目录名的匹配, 相当于:
dir/ad *.dat
6.3 for /R D:\ %I in (*docs ruby) do @echo %I
与6.1相比, 多出的/R D:\ 指定一个路径D:\ , 在它下面递归地查找所有与*docs 或ruby能匹配的文件名, 或目录名. 注意, 两者都会被搜出来. 相当于:
dir/s/b D:\*docs D:\ruby
6.1 与 6.2中都是只针对当前目录, 要么只处理文件(6.1), 要么只处理目录(6.2)
6.4 for /F %I in (file1.txt file2.txt) do @echo %I
/F 带给for根本性的不同, 与前面6.1-6.3相比, 它把() 中集中的内容看作文件, 不是遍历文件的名字, 而是处理文件的内容. 这个命令打开文件file1.txt, 逐行处理, 默认使用空格和跳格来把一行的内容拆解为多个域, 第一个域赋值给变量 %I, 后面的域在这个例子里都忽略了. 然后再打开文件 file2.txt, 也就是说, 它其实进行了2重循环, 外层循环处理()中的文件集合, 内层循环处理当前打开的文件中的每一行.
注意只要使用了/F , ()里指定文件名就不能使用通配符了.
6.5 for /F %I in ("abc 123 xyz") do @echo %I
与6.4相比, 双引号括起的内容, 不被视为文件名, 而是字面上的字符串, 把命令行上指定的字符串进行Split操作, 分隔成多个域之后把第一个域赋值给变量I. 对这个应用, 虽然是for命令, 可是没有循环的动作在.
6.5 for /F %I in ('cmd /c "echo a b c"') do @echo %I
单引号又不同了, 其中内容被作为命令来执行, 其执行结果一行一行被分析, 每一行分隔为域.
步骤为:
6.5.A 运行命令 cmd /c "echo a b c"
6.5.B 遍历6.5.A 产生的输出中的每一行
6.5.C 对6.5.B中遍历到的一行, 分隔为域, 第一个域赋值给%I 变量
等价于:
cmd /c "echo a b c">tmp.txt
for /F %I in (tmp.txt) do @echo %I
6.6 for /F "usebackq" ("file name with space.txt" log.txt) do @echo %I
要特别说明的是 usebackq 带给for命令对()集合中的单双引号完全不同的解释.
* 没有usebackq, 双引号表示处理命令行上的字符串, 有, 双引号用来处理文件名, 而且不能混合使用双引号表示文件名与不使用双引号表示, 如下:
for /F "usebackq" %I in ("tmp name.txt" log.txt) do @echo %I
会正确处理文件"tmp name.txt", 但之后说:
系统找不到文件 og.txt。
我猜测原因: 看到(之后的", 认为这是用双引号引起来的文件名, 处理完第一个文件名, 剥除前后的双引号, 得到正确的文件名, tmp name.txt, 处理完之后, 找到一个空格, 认为是文件之间的分隔符, 找到第一个非空白字符, 它不去判断该字符是不是双引号, 而是根据(之后的第一个"假设所有文件都是这样用双引号引起来的, 所以把碰到的第一个非空格非TAB字符给删除了. 在处理结束的"时, 它却没有武断地把)之前的最后一个非空白非TAB字符给删除, 那样的话它得到的文件名就是 og.tx了, 前后的l和t都被删除了. 所以它对文件名的处理是非常变态的.
上面我的分析看来, 好象
for /F "usebackq" %I in ("tmp name.txt" "log.txt") do @echo %I
应该是没问题的. 其实不然, DOS批处理解析器的丧心病狂还没结束, 它会在处理完 tmp name.txt文件之后报告说:
log.txt" 找不到, 也就是说, 它总是不删除文件结尾的那个字符.
看到这里, 下面的命令一定让你吃惊:
for /F "usebackq" %I in ("tmp name.txt" xlog.txt) do @echo %I
这个命令却正常了. 文件 xlog.txt 根本不存在, 但因为它总是要干掉第一个找到的字符, 所以随便写一个让它发疯好了. 它真正打开的是文件log.txt
使用这个病态的办法, 我终于可以在命令行上混合地使用双引号表示的文件名和不使用双引号表示的文件名了.
然而, 这个方法似乎暗含着另一个严重的问题, 你只能有第一个文件是包含空格的, 因为后面的不能再使用双引号了.
经过我反复的实验, 找到了下面的规律:
如果前一个文件名是用""包围起来的, 那么它的下一个文件名必需给它提供一个额外的废物字符被它吃掉, 它才能得到正确的文件名, 如下:
for /F "usebackq" %I in ("tmp name.txt" x"tmp name.txt" xlog.txt log.txt ) do @echo %I
是正确的, 因为第一个"tmp name.txt"之后的第二个"tmp name.txt"虽然也需要用双引号包围起来, 但是提供了一个字符x让它吃掉, 吃掉之后它就舒服了, 可以得到第二个正确的文件名, 第3个文件名 xlog.txt, 也多提供一个x让它吃掉, 最后一个log.txt 不提供额外的字符, 因为它的前一个文件名不是双引号包围起来的,
总结一下, 使用了 /F "usebackq" 选项时, 要在()集合里混合使用双引号包围起来的文件名, 需要遵循以下规则:
A. 当前文件名是()中的第一个, 不管是否使用双引号, 直接写, 如"tmp name.txt" 或 log.txt均可, 若否
B. 当前要写的文件名前一个文件名是否是"" 包围起来的, 若否, 直接写, 同上
C. 当前要写的文件名前一个文件名是由"" 包围起来的, 如果当前要写的文件名也要用双引号包围起来, 如tmp name.txt, 要写作 x"tmp name.txt", 注意x在第一个双引号之前, 如果当前要写的文件名不希望用双引号(不包含空格), 则写作 xlog.txt, 其中两种情况下的字符x 都代表一个非空白的普通字符, 它会在处理文件名时被吃掉.
6.7 for /F "usebackq" %I in ('1 2 3') do @echo %I
* 单引号在没有 usebackq 的情况下, 把其中的内容作为一个命令来运行, 使用了usebackq时, 降级为把它视为命令行上的字符串, 此例中, 显示1
6.8 for /F "usebackq" %I in (`new_command param1 param2 ...`) do @echo %I
这种情况下, 把new_command当作命令运行, 解析其输出. 相当于:
for /F %I in ('sub_command param1 param2 ...') do @echo %I
写到这里, 奉上几个有用的例子:
* DOS下的which 怎么写
for /F %I in ("sed.exe") do @echo %~$PATH:I
我机器上输出结果是:
C:\cygwin\bin\sed.exe
注意你只能在for里面使用这种神奇的语法, $是bash的, 谁知道微软怎么想的.
* 通过日期字符串组合出一个文件名/目录名:
写日志的经常要用:
D:\>for /F "tokens=1,2,3 delims=:-/ " %I in ('date /t ') do SET date_str=%I_%J_%K
D:\>SET date_str=2009_02_25
D:\>for /F "tokens=1,2 delims=:-/ " %I in ('time /t ') do SET time_str=%I_%J
D:\>SET time_str=21_44
* 要把一整行的内容读取到变量中:
for /F "tokens=*" %I in (tmp.txt) do @echo [%i]
注意你可能会对*的指定有误解, 我原来的理解是: tokens=1,2* 指定了2个域, 第二个变量的内容是第二个及其以后的, 因为 tokens也支持 tokens=1-3的写法, 但实际上, *是单独指定了一个域, 上面tokens=1,2* 实际上指定了3个域: %I, %J, %K, 其中%K 是第三域及其后的所有域.
写成 tokens=1,2,* 是等价的, 我认为也更清晰, 但help for中给出的例子却是 1,2*
* 变量区分大小写, %I 与 %i 不同, 必需是单个字母
* 只有最多52个变量可用.
* 如果实际上没有那么多域被split出来, %K 这样的字符串会原封不动地输出为 %K
* 通过delims指定的是一个字符的集合, 连续出现的分隔字符视为单个的分隔符, 如:
D:\>for /F "tokens=1,2,3 delims=," %I in ("1,,2,3") do @echo [%I,%J,%K]
[1,2,3]
注意, 没有空域被split出来.
* 在for中引用 %I 变量必需没有 结尾的%, 如%I%
* 与此对应的, 在for之外引用, 则必需使用结束的%, 如%time_str%
* /F 之后的参数, 最好把它们看成是css中那样指定的松散的空白分隔的关键字列表, 如usebackq tokens delims
* eol 也是/F的参数之一, 只接受一个字符, 很误导, 它不是指定行结束符, 象awk中那样. 而是指定一个字符, 当一行的第一个字符(包括空白)恰好是该字符时, 跳过去, 默认情况下, 对于空白行, 会跳过不处理, 所谓空白行, 是指仅由0个或多个 delims字符组成的行, 如:
for /F "delims=:,." %I in ("::::,,,,...,.,.") do @echo %I
该行内容不空白, 但全部由delims字符组成, 所以仍为空白行.
* 注意=号本身也可以是分隔符, 如/F "delims=="是完全合法的, 虽然看起来有点怪. 由于delims的=号之后所有东西都被看作是分隔符, 所以最好把它作为最后一项来指定, 这样包括空格在内就完全没有问题.
* command substitute 中, 也可以是组合命令
for /F "tokens=*" %I in ('date /t ^&^& time /t') do @echo %I
* for 循环中对外部变量的引用, 看起来是只在进入循环时读取一次, 此后对外部变量的取值只会取初始读取到的值, 说着费劲, 看个例子:
set a=x
type tmp.txt
1
2
3
希望: a=x,1,2,3
for /F %I in (tmp.txt) do set a=%a%,%I
想当然的, 每次的%I会追加在变量a之后, 但你只能得到 a=x,3, 因为每次对%a%的取值你都只能得到最初的x
7. if [NOT] exist 虽然帮助中举例用的是文件名, 经实验, 也能处理目录, 或许本身就应该把目录本身也视为一种文件.
8. () 对命令的分组, 不象bash一样产生子shell, 它只是对命令分组. 一行一个命令, 很方便
if exist filename (
command1
command2
) else (
command1
command2
)
9. for 循环内的重定向
for /F %I in ("a b c") do echo %I > tmp.txt
你一定会误会说a, b, c三行内容被存入tmp.txt, 其实只有c被存入, 重定向的是do后面的单个命令, 而不是把它整体的输出定向到tmp.txt, 需要用
(for /F %I in ("a b c") do @echo %I ) > tmp.txt
10. 也支持 2>&1 的语法. 完全bash兼容的.
阅读(870) | 评论(0) | 转发(0) |