Bat&Powershell语法总结笔记

温馨提示:远离BAT语法,幸福你我他(ಡωಡ)

1   推荐链接

2   检测并获取管理员权限

2.1   方式一:使用cacls工具检测权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@REM 检测权限:根据系统版本尝试访问系统文件路径
if "%PROCESSOR_ARCHITECTURE%" equ "amd64" (
"%SYSTEMROOT%\SysWOW64\cacls.exe" "%SYSTEMROOT%\SysWOW64\config\system" >nul 2>&1
) else (
"%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system" >nul 2>&1
)

@REM 根据权限情况跳转标签
if "%errorlevel%" neq "0" (
echo 当前检测无管理员权限。正在请求管理员权限...
echo.
goto uac_prompt
) else (
goto got_admin
)

@REM 获取管理员权限
:uac_prompt
powershell -Command "Start-Process -Verb RunAs -FilePath '%0' -ArgumentList 'am_admin'"
exit /b

@REM "已获取管理员权限,存储当前目录并切换到脚本程序目录"
:got_admin
pushd "%cd%"
cd /d "%~dp0"

2.2   方式二:使用fsutil dirty query检测权限

1
2
3
4
5
6
7
8
@echo off
echo.
set "params=%*"
cd /d "%~dp0" && ( if exist "%temp%\getadmin.vbs" del "%temp%\getadmin.vbs" ) && %systemdrive% 1>nul 2>nul || ( echo Set UAC = CreateObject^("Shell.Application"^) : UAC.ShellExecute "cmd.exe", "/c cd ""%~sdp0"" && %~s0 %params%", "", "runas", 1 >> "%temp%\getadmin.vbs" && "%temp%\getadmin.vbs" && exit /B )
@echo off & title Install latest office & color 70
@chcp 65001>nul
echo.
cd /d %~dp0

2.3   方式三:提权到System权限

1
2
3
4
@REM 获取管理员权限
:uac_prompt
powershell -ep bypass "Install-Module -Name NtObjectManager;start-Win32ChildProcess cmd"
exit /b

3   提权并传递参数

1
2
3
4
5
6
7
8
9
10
11
12
13
@REM 提权之前先保存当前的命令行参数
@REM 保存命令行参数到txt文件
set commandline_argument=%*
echo %commandline_argument% > "__temp__argument.txt"

@REM 提权代码《检测并获取管理员权限》

@REM 重新读取参数
@REM 读取txt文件中的首个参数
for /f "tokens=1" %%c in (__temp__argument.txt) do (
set get_argument=%%c
goto activate
)

4   打印输出

4.1   打印输出分割线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
:draw_line
@REM 局部环境设置打印的符号和数量,并传递到全局环境draw_line_chars变量中
setlocal enabledelayedexpansion
@REM 查询当前终端窗口的列数大小,delims后面:之后没有空格,或者有多个空格都是一样的效果,它无法trim多余的空格。并且:后面不能单独加一个空格,否则无法正确获取。
for /f "tokens=2 delims=:" %%c in ('mode con ^| findstr "列"') do set cols=%%c
@REM 移除变量中左边多余的空格
for /f "tokens=* delims=: " %%c in ('echo %cols%') do set cols=%%c
set "char=%~1"
set /a "count=%cols%"
set "line="
for /l %%i in (1,1,%count%) do set "line=!line!%char%"
endlocal & set "draw_line_chars=%line%"
echo %draw_line_chars%
exit /b

4.2   打印输出当前时间

1
2
3
4
5
6
7
8
:log
setlocal
for /f "tokens=1-3 delims=/ " %%a in ('date /t') do (set "yy=%%a" & set "mm=%%b" & set "dd=%%c")
for /f "tokens=1-3 delims=:." %%a in ('echo %time%') do (set "hh=%%a" & set "mn=%%b" & set "ss=%%c")
set time_stamp=%yy%-%mm%-%dd%T%hh%:%mn%:%ss%+08:00
echo %time_stamp% %~1
endlocal
exit /b

5   特殊字符

bat中有部分特殊字符,例如()<>&|之类的,若需要输出打印特殊字符到终端控制台,则需要转义符号^。如果传递变量值时需要转义的话,以&符号为例,一般写法为^^^&^^转义为^^&转义为&

在本人代码实践中,如果使用使用双引号去设置变量的话,例如set "val=Mr. Kin ^^^<im.misterkin@gmail.com^^^>",传递过程中仍会出现解析异常,导致运行错误。如果不用双引号,直接使用单^符号直接转义,例如set val=^<im.misterkin@gmail.com^>则完全没问题。这点令人十分困扰。

6   字符串的处理

6.1   字符串的截取

%variable:~n,m%

  • n:开始截取字符串的偏移量;如果为正数,则从左边开始;如果没有为负数,则从右边开始
  • m:要截取字符的个数。如果没有指定个数,则采用默认值,即变量数值的余数(余数指剩余个数,如:%variable:~-5% 当前偏移量为倒数第6,将剩下的字符全部截取)。如果两个数字 (偏移量和长度) 都是负数,使用的数字则是字符串长度加上指定的偏移量或长度(参见实例2)。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    set ifo=abcdefghijklmnopqrstuvwxyz0123456789
    echo 原字符串:
    echo %ifo%

    rem abcde
    echo 截取前5个字符:
    echo %ifo:~0,5%

    rem fghijklmnopqrstuvwxyz0123456789
    echo 截取第六个字符直到最后一个字符
    echo %ifo:~5%

    rem 56789
    echo 截取最后5个字符:
    echo %ifo:~-5%
    echo %ifo:~-5,5%

    rem abcdefghijklmnopqrstuvwxyz01234
    echo 截取第一个到倒数第6个字符:
    echo %ifo:~0,-5%

    rem defgh
    echo 从第4个字符开始,截取5个字符:
    echo %ifo:~3,5%

    rem wxyz0
    echo 从倒数第14个字符开始,截取5个字符:
    echo %ifo:~-14,5%

6.2   字符串的字符替换

BAT语法:%variable:str1=str2%,缺陷:用以文本文件中整行处理中可能无法处理一些特殊字符,导致丢失某些内容。

1
2
@REM 替换str中的xxx为yyy
set str2=%str:xxx=yyy%

POWERSHELL语法:(Get-Content -Path '%file_path%') -replace '%search_string%', '%replace_string%' | Set-Content -Path '%file_path%',缺陷:用以文本文件中整行处理中可能无法处理一些特殊字符,导致丢失某些内容。不过比BAT语法使用起来更简单,而且无需特殊处理也能保留空格和换行符。推荐只修改特定字符串,%search_string%无需填入过多的字符,例如将17改为20,只需搜索17字符串即可。

1
powershell -Command "(Get-Content -Path '%file_path%') -replace '%search_string%', '%replace_string%' | Set-Content -Path '%file_path%'"

6.3   去掉变量字符串中的空格

1
2
3
4
5
6
7
8
9
10
11
12
set abc=                          aaabbbccc
:delleft
if "%abc:~0,1%"==" " set abc=%abc:~1%&&goto delleft
echo 去掉左边空格:%abc%
set abc=aaabbbccc
:delright
if "%abc:~-1%"==" " set abc=%abc:~0,-1%&&goto delright
echo 去掉右边空格:%abc%
@REM 去除所有空格,使用的方式就是字符替换
set abc= aaa bbb ccc
set "abc=%abc: =%"
echo 去掉所有空格:%abc%

7   读取内容

for /f”常用来解析文本,读取字符串。

  • delims参数负责切分字符串,指定分隔符。内容会根据实际分隔符分隔开。
  • tokens负责提取字符串,指提取的列数。例如tokens=12-4,表示提取第1列,第2列至第4列的内容,有多少列就要有多少个输出变量,并且各变量中的字母存在先后顺序。第一个变量字母是自己定义的,推荐是使用%%a,这样第二个列就是用%%b来表示。m*表示提取第m列和第m列之后的所有字符。

7.1   读取首行内容

1
for /f "usebackq delims=" %%a in ("!drive!\!config_file!") do set "image_name=%%a"

7.2   读取特定行数内容

1
for /f "tokens=* delims=" %%i in ('diskpart /s auto_partition_opts ^| findstr /n "^" ^| findstr "^9:"') do set disk_model_name=%%i

7.3   读取全部内容

FOR /F默认是不会返回空行,因为该命令本身设计就是如此。

若想实现完全读取,可以使用findstr对文本内容先赋予一个行号,再逐行读取。

1
2
3
4
5
6
7
8
9
10
11
12
::preserve blank lines using FIND, assume no line starts with ]
::long lines are truncated
for /f "tokens=1* delims=]" %%A in ('type "file.txt" ^| find /n /v ""') do echo %%B

::preserve blank lines using FINDSTR, assume no line starts with :
::long lines > 8191 bytes are lost
for /f "tokens=1* delims=:" %%A in ('type "file.txt" ^| findstr /n "^"') do echo %%B

::FINDSTR variant that preserves long lines
type "file.txt" > "file.txt.tmp"
for /f "tokens=1* delims=:" %%A in ('findstr /n "^" "file.txt.tmp"') do echo %%B
del "file.txt.tmp"

8   判断网络连通状态执行不同命令

1
ping www.baidu.com -n 3 | findstr "TTL" >nul && goto network_well || goto network_bad

9   将批处理运行结果显示并保存到文件

9.1   wtee方案

linux上有tee命令可以实现这个目的,wtee程序则是windows实现的tee版本。

9.2   临时文件中转方案

原理:使用call将想要输出结果输出到临时文件,之后进行:

  1. 先清屏,将临时文本内容显示,实现窗口显示的本次运行结果的功能。
  2. 将临时文本内容追加到日志文件用于保存。
  3. 删除临时文件。
    1
    2
    3
    4
    5
    6
    7
    8
    @echo off
    call a.bat > log2.txt
    cls
    for /f "delims=" %%i in (log2.txt) do (
    echo %%i
    )
    type log2.txt >> log.txt
    del log2.txt

10   数组

创建数组:set a[0]=1

定义值列表并遍历值列表:

1
2
3
4
5
@echo off
set list=1 2 3 4
(for %%a in (%list%) do (
echo %%a
))

遍历数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
@echo off
setlocal enabledelayedexpansion
set topic[0]=comments
set topic[1]=variables
set topic[2]=Arrays
set topic[3]=Decision making
set topic[4]=Time and date
set topic[5]=Operators

@REM for /L %%变量 in (起始值,每次增值,结束时的比较值) do 命令 ,in (0,1,5) 会生成一个序列 [0,1,2,3,4]
for /l %%n in (0,1,5) do (
echo !topic[%%n]!
)

计算数组长度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@echo off
set Arr[0]=1
set Arr[1]=2
set Arr[2]=3
set Arr[3]=4
set "x=0"
:SymLoop

if defined Arr[%x%] (
call echo %%Arr[%x%]%%
set /a "x+=1"
GOTO :SymLoop
)
echo "The length of the array is" %x%

数组结构体:

1
2
3
4
5
6
7
8
9
10
11
@echo off
set obj[0].Name=Joe
set obj[0].ID=1
set obj[1].Name=Mark
set obj[1].ID=2
set obj[2].Name=Mohan
set obj[2].ID=3
FOR /L %%i IN (0 1 2) DO (
call echo Name = %%obj[%%i].Name%%
call echo Value = %%obj[%%i].ID%%
)

11   setlocal和endlocal的作用域

在setlocal和endlocal语句内的set变量,是属于局部变量。

如果用setlocal enabledelayedexpansion设置了延迟环境变量扩展,需要使用!variable!来引用变量。

12   生成特定命令的批处理帮助文件

1
2
3
4
5
6
@echo on
:z
@cls
@set /p "d=输入要BAT指令:"
@ %d% /? > %d%.txt
goto z

13   字符集引起的“is not recognized as an internal or external command”

知识背景:系统终端默认的字符集是ANSI,但ANSI并非某一种特定的字符编码,在不同的系统中,ANSI表示不同的编码。比如原生英文系统的ANSI编码是ASCII编码,原生中文系统的ANSI编码是GBK编码。(code page实际是由系统的「区域和语言」控制的)

问题现象:当bat程序脚本文件本身用默认的utf-8编码保存,并且使用命令@chcp 65001>nul切换代码页,仍然有概率执行是会导致echo命令在输出中文出现解析错误,提示“is not recognized as an internal or external command”,比如使用注释命令@rem 中文注释之后,再使用echo打印输出中文时就出现解析错乱。

问题原因:具体引起的原因未知,目前猜测是当脚本程序执行不少命令之后,环境变量可能会出现错乱,导致部分命令解析失败。

解决方法有两种:

  • @rem 中文注释命令之后重新执行@chcp 65001>nul即可确保后面的命令正确解析执行。
  • 将所有文件编码改为ANSI编码,比如简中系统,使用GBK编码。

14   在命令提示符中运行 PowerShell 命令

命令形式:powershell -command "xxx"

如果命令中有双引号,记得使用反斜杠转义双引号字符,避免最外层的双引号和里面的双引号一起被匹配解析。在PowerShell语法中,双引号 "" 视为文字字符串值的。因此需要确保符号被正确解析。

15   语法问题

15.1   ==equ的对比区别

  • ==是用来比较相同的,即比较字符串是否完全相同的。
  • EQU是比较运算符,含有运算的功能,换句话说,是可以在比较之前转换数字为对应的数值,然后再比较。

数值是指十进制的数字,在前缀加 (这里x忽略大小写)则表示十六进制数字,加 0 则表示八进制数字。因此,0x1218 相同,也与 022 相同,八进制表示法容易引起混淆。例如,0809 不是有效数字,因为 89 不是有效的八进制数字。

所以,在使用 == 比较时,0x1202218是不相等的,但在使用EQU比较时,他们是相等的,因为他们对应的数值都是18

16   ps脚本无法运行

出于安全考虑,默认的 Windows PowerShell 策略不允许执行脚本。

设置运行策略:

1
2
3
4
5
6
Get-ExecutionPolicy
Set-ExecutionPolicy unrestricted
# Restricted:默认设置,不允许运行任何脚本
# AllSigned:仅运行受信任脚本
# RemoteSigned:运行本地脚本,不管这些脚本是否受信任
# Unrestricted:允许运行所有脚本,甚至是不受信任的

17   参考文献

[1] How to request Administrator access inside a batch file[EB/OL]. https://stackoverflow.com/questions/1894967/how‑to‑request‑administrator‑access‑inside‑a‑batch‑file.
[2] How to write a batch file can run parameters as command with administrative privi‑leges?[EB/OL]. https://stackoverflow.com/questions/38364617/how‑to‑write‑a‑batch‑file‑can‑run‑parameters‑as‑command‑with‑administrative‑priv.
[3] 批处理通过 ping 判断网络[EB/OL]. https://www.52pojie.cn/thread-1021861-1-1.html.
[4] 批处理 ping命令判断是否可以连接到一个网站[EB/OL]. https://lanlan2017.github.io/blog/318f3e6b/.
[5] Windows10一句话从administrator权限提升到system权限[EB/OL]. https://blog.csdn.net/mochu7777777/article/details/106842901.
[6] 在命令提示符中运行 PowerShell 命令[EB/OL]. https://www.delftstack.com/zh/howto/powershell/running-powershell-commands-in-cmd/.
[7] [系统相关] [已解决]批处理if中,== 与equ 这2个,在什么情况下会有区别?[EB/OL]. http://www.bathome.net/thread-15170-1-1.html.
[8] cmd 字符串替换[EB/OL]. https://blog.csdn.net/scimence/article/details/52816878.
[9] Bat 去掉变量字符串中的空格[EB/OL]. https://blog.csdn.net/a71468293a/article/details/127747191.
[10] 字符串截取操作[EB/OL]. https://www.hxstrive.com/subject/windows_bat/122.htm.
[11] 在使用bat 批处理 时将运行结果显示并保存到文件中 echo[EB/OL]. https://www.cnblogs.com/qiyuexin/p/11701362.html.
[12] for /f命令之—Delims和Tokens用法&总结[EB/OL]. https://blog.csdn.net/kagurawill/article/details/114982328.
[13] 批处理(bat)中的数组问题[EB/OL]. https://blog.csdn.net/qq_34414530/article/details/117839838.
[14] BAT批处理文件 setlocal,endlocal命令详解[EB/OL]. https://blog.csdn.net/csqxy547/article/details/89856034.
[15] [数值计算] [已解决]批处理()嵌套的变量延时与endlocal作用域问题[EB/OL]. http://www.bathome.net/thread-32880-1-1.html.
[16] How can I replace every occurrence of a String in a file with PowerShell?[EB/OL]. https://stackoverflow.com/questions/17144355/how-can-i-replace-every-occurrence-of-a-string-in-a-file-with-powershell.
[17] DOS batch FOR loop with FIND.exe is stripping out blank lines?[EB/OL]. https://stackoverflow.com/questions/8811992/dos-batch-for-loop-with-find-exe-is-stripping-out-blank-lines.
[18] Special Characters in Batch File[EB/OL]. https://stackoverflow.com/questions/37333620/special-characters-in-batch-file.