Started: 2024.03.12 09:00:00
Update: 2024.05.27 21:53:00

0. Purpose

在 Win11 中,默认的终端已经是 PowerShell, 包括 VSCode 里的默认终端, 这一方面改进了 cmd.exe 表达力不足的问题, 另一方面要求程序员们要学习一点 PowerShell 语法, 之前的 bat 语法虽然能继续通过 .cmd/.bat 脚本文件中使用, 但在执行单条命令时还是不如 PowerShell 命令来的直接。

1. 启用 PowerShell 执行 .ps1 文件的权限

有不止一个 PowerShell。 需要分配添加权限 ( about_Execution_Policies )

以管理员权限打开 PowerShell, 执行:

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned

以管理员权限打开 "Developer PowerShell for VS 2022", 执行:

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned

2. 获取程序执行结束时的返回值

在 Linux 下用 echo $?, 得到具体的数字。 在 powershell 下,echo $? 输出 True/False 不直观,有两种方法得到具体数值:

1). echo $LASTEXITCODE

int main() { return -1; }
clang t.c
./a.exe
echo $LASTEXITCODE

2). 创建进程,获取和输出进程对象的 ExitCode 属性:
执行方法:

$process = Start-Process -FilePath "你的可执行文件路径" -ArgumentList "参数列表(如果有)" -NoNewWindow -PassThru -Wait
# 获取 main() 函数的返回值
$exitCode = $process.ExitCode
# 输出返回值
Write-Output "程序退出代码: $exitCode"

样例输出:

PS D:\> $process = Start-Process -FilePath "./a.exe"
PS D:\> $exitCode = $process.ExitCode
PS D:\> Write-Output "程序退出代码: $exitCode"
程序退出代码:
PS D:\> $process = Start-Process -FilePath "./a.exe" -NoNewWindow -PassThru -Wait
a - b = 255
PS D:\> $exitCode = $process.ExitCode
PS D:\> Write-Output "程序退出代码: $exitCode"""""""""

3. 获取程序执行耗时

在 Linux 下可以用 time ./testbed。 PowerShell 不支持 time 命令, 提供了如下三种方式来获取耗时:

  • 获取程序执行耗时, 但屏蔽了命令本身的输出:
  • 测量 ls 命令执行的耗时(只显示耗时,ls的输出会被隐藏)

    Measure-Command { ls }
    

    2)获取程序执行耗时, 同时保持命令本身的输出:

    测量 ls 命令执行的耗时, 并保持ls自身的输出

    Measure-Command { ls | Out-Default }
    
  • 获取程序的耗时, 并且以毫秒为单位进行输出:
    Measure-Command 会返回一个 TimeSpan 对象,该对象包含了执行所需的总时间, 因此可以写的更复杂一些:
  • # 使用 Measure-Command 测量命令执行时间
    $result = Measure-Command {
        # 在这里放置你要执行的命令或脚本
        Start-Process "你的程序路径" -ArgumentList "参数列表(如果有)" -NoNewWindow -Wait
    # 输出程序运行耗时
    Write-Output "程序运行耗时: $($result.TotalMilliseconds) 毫秒"
    

    4. 查看和修改环境变量

    4.1 查看所有环境变量

    Linux 下使用 env 显示所有环境变量。 PowerShell 使用

    Get-ChildItem Env:
    

    获取所有环境变量, 不过像 PATH 这样的环境变量通常由于内容太多,显示不全(只显示单行,结尾截断了)。

    PS D:\github\xxxx> Get-ChildItem Env:
    Name                           Value
    ----                           -----
    ALLUSERSPROFILE                C:\ProgramData
    ANDROID_NDK                    D:/soft/android-ndk/r21e
    APPDATA                        C:\Users\aczz\AppData\Roaming
    ChocolateyInstall              C:\ProgramData\chocolatey
    CMAKE_EXPORT_COMPILE_COMMANDS  1
    hexagon-sdk_env_root           D:\soft\Qualcomm\HexagonSDK\5.5.0.1
    HOMEDRIVE                      C:
    HOMEPATH                       \Users\aczz
    LOCALAPPDATA                   C:\Users\aczz\AppData\Local
    LUA_DEV                        d:\soft\Lua\5.1
    LUA_PATH                       ;;d:\soft\Lua\5.1\lua\?.luac
    NO_ASAN_SAVE_DUMPS             MyAsanCrash.dmp
    NUMBER_OF_PROCESSORS           12
    OS                             Windows_NT
    Path                           C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPo...
    PATHEXT                        .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.wlua;.lexe;.CPL
    POWERSHELL_TELEMETRY_OPTOUT    1
    UseMultiToolTask               true
    USERPROFILE                    C:\Users\aczz
    VCPKG_DISABLE_METRICS          1
    VCPKG_ROOT                     D:\github\vcpkg
    

    4.2 打印某个环境变量

    Linux 下使用 echo $PATH 的方式打印 PATH 环境变量的值, 使用 echo $ANDROID_NDK 打印环境变量 ANDROID_NDK 的值。

    PowerShell 下也有 echo 命令, 但是环境变量要用 $env:PATH$env:ANDROID_NDK 的形式来获取, 即:

    PS D:\> echo $env:PATH
    C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0;C:\Windows\System32\OpenSSH;
    
    PS D:\> echo $env:ANDROID_NDK
    D:/soft/android-ndk/r21e
    

    4.3 临时修改某个环境变量

    Linux 下使用 export PATH=$PATH:/usr/local/cmake-3.29.0/bin 这样的用法, PowerShell 不支持 export 命令。 直接给环境变量赋值即可, 变量可以是新的,也可以是已经存在的:

    $env:MY_VAR = "somevalue"
    

    注意,需要用引号引起来,没有引号会报错。等号左右的空格是可选的。

    PS D:\> $env:MY_VAR = "somevalue"
    PS D:\> echo $env:MY_VAR
    somevalue
    PS D:\> $env:MY_VAR = "somevalue2"
    PS D:\> echo $env:MY_VAR
    somevalue2
    

    5. 重定向

    Linux 下使用 ./testbed > log.txt 2>&1 来重定向标准输出和错误输出。 PowerShell 下仍然可以用 2>&1.

    6. 拼接字符串

    在 Linux shell 中使用:

    TOOLCHAIN=$ANDROID_NDK/build/cmake/android.toolchain.cmake
    

    在 PowerShell 也是直接这样用的:

    $TOOLCHAIN = "$env:ANDROID_NDK/build/cmake/android.toolchain.cmake"
    

    也可以用 Join-Path (它是一个 cmdlet), 会自动处理操作系统特定的路径分隔符:

    $TOOLCHAIN = Join-Path $Env:ANDROID_NDK "build\cmake\android.toolchain.cmake"
    

    7. 执行打印

    在 Linux 中习惯了是使用 echo 来打印,例如

    echo $PATH
    

    在 PowerShell 中, 可以继续用 echo, 它是 Write-Output 的别名, 是将参数(被打印的东西)发送到 PowerShell 的管道中, 意味着输出内容可以被其他 PowerShell 命令处理:

    echo $env:PATH
    echo "ANDROID_NDK is $($Env:ANDROID_NDK)"
    

    但如果输出内容仅仅做为显示使用, 并不需要作为其他命令的输入, 可以使用 Write-Host:

    Write-Host $env:PATH
    Write-Host "ANDROID_NDK is $($Env:ANDROID_NDK)"
    

    8. 连行符

    在 Linux shell 中, 使用 \ 作为连行符:

    cmake \
        -S . \
        -B build
    

    在 cmd 中, 使用 ^ 作为连行符:

    cmake ^
        -S . ^
        -B build
    

    在 PowerShell 中, 使用 ``` (单个 backtick 符) 作为连行符:

    cmake `
        -S . `
        -B build
    

    9. 使用双引号包裹变量,避免出错

    例如执行android ndk的交叉编译脚本中

    $env:ANDROID_NDK = "D:/soft/android-ndk/r21e"
    $TOOLCHAIN = "${env:ANDROID_NDK}/build/cmake/android.toolchain.cmake"
    -DCMAKE_TOOLCHAIN_FILE=$TOOLCHAIN # 调用 cmake 时候传入这行, 会失效, 因为没有用双引号把变量抱起来
    
    $env:ANDROID_NDK = "D:/soft/android-ndk/r21e"
    $TOOLCHAIN = "${env:ANDROID_NDK}/build/cmake/android.toolchain.cmake"
    -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN" # 调用 cmake 时候传入这行, 有效
    

    完整的有效示例代码:

    # check if ANDROID_NDK environment variable is not set
    if (-not $env:ANDROID_NDK) {
        $env:ANDROID_NDK = "D:/soft/android-ndk/r21e"
    $TOOLCHAIN = "${env:ANDROID_NDK}/build/cmake/android.toolchain.cmake"
    Write-Host "ANDROID_NDK is: $env:ANDROID_NDK"
    Write-Host "TOOLCHAIN is: $TOOLCHAIN"
    $BUILD_DIR = "android-arm64"
    cmake `
        -S .. `
        -B $BUILD_DIR `
        -G "Ninja" `
        -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN" `
        -DANDROID_ABI="arm64-v8a" `
        -DANDROID_PLATFORM=android-24 `
        -DCMAKE_BUILD_TYPE=Release
    cmake --build $BUILD_DIR -j 4
    

    10. PowerShell 中的注释

    Linux shell 里的注释:

    # This is a comment
    
    @REM this is a commet
    

    PowerShell:

    # This is a single line comment
        this is a multi-line comment
        this is its second line
    

    11. 按任意键继续 (pause)

    在 cmd 中使用 pause, 放到一段命令执行的最后, 用于查看结果。

    在 PowerShell 中有三种等价写法:

  • 一行执行的
  • Read-Host -Prompt "Press Enter to continue"
    
  • 两行执行的
  • "Press any key to continue . . ."
    [Console]::ReadKey($true)
    
  • 执行 cmd 命令(不推荐)
  • cmd /c pause
    

    不推荐 cmd /c 的原因是, 它相当于临时创建了进程, 执行后就理解结束了, 像修改环境变量这样的操作,后续命令中是没有效果的:

    PS D:\github\x> cmd /c echo %ROCKPKG_ROOT%
    D:/.rockpkg
    PS D:\github\x> cmd /c set ROCKPKG_ROOT=E:/.rockpkg
    PS D:\github\x> cmd /c echo %ROCKPKG_ROOT%
    D:/.rockpkg
    

    12. 运行 vcvarsall.bat 脚本后继承环境变量

    想进入特定版本的 Visual Studio 对应的环境, 例如 vs2022 x64 native command prompt, 一种方法是手动运行 vcvarsall.bat:

    call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
    

    上述写法在 powershell 中, 基本无效,因为不会继承 cmd 里的环境变量。

    解决办法:自行定义 Invoke-CmdScript 命令,替代 &, 然后再执行。

    具体步骤:

    1)进入 PowerShell

    2)修改 profile 文件

    echo $profile # 查看路径
    code $profile # 编辑文件
    

    填入内容:

    # Invokes a Cmd.exe shell script and updates the environment.
    # https://stackoverflow.com/questions/41399692/running-a-build-script-after-calling-vcvarsall-bat-from-powershell
    function Invoke-CmdScript {
      param(
        [String] $scriptName
      $cmdLine = """$scriptName"" $args & set"
      & $Env:SystemRoot\system32\cmd.exe /c $cmdLine |
      select-string '^([^=]*)=(.*)$' | foreach-object {
        $varName = $_.Matches[0].Groups[1].Value
        $varValue = $_.Matches[0].Groups[2].Value
        set-item Env:$varName $varValue
    
  • 加载 profile
  • . $profile
    
  • 使用 Invoke-CmdScript, 例如我要执行 Ninja-MultiConfig 作为 generator 的构建:
  • # 设置Visual Studio环境变量
    Invoke-CmdScript "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
    # 设置构建目录
    $BUILD_DIR = "vs2022-x64-ninja"
    # 设置编译器
    $env:CC = "cl"
    $env:CXX = "cl"
    # 配置项目
    cmake -S .. -B $BUILD_DIR -G "Ninja Multi-Config"
    # # 构建项目 - 调试配置
    # cmake --build $BUILD_DIR --config Debug
    # # 构建项目 - 发布配置
    # cmake --build $BUILD_DIR --config Release
    # 暂停,以便查看输出
    Read-Host -Prompt "Press Enter to continue"
    

    而以前的 cmd 脚本则是:

    @echo off
    call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
    set BUILD_DIR=vs2022-x64-ninja
    set CC=cl
    set CXX=cl
    cmake ^
      -S .. ^
      -B %BUILD_DIR% ^
      -G "Ninja Multi-Config"
    cmake --build %BUILD_DIR% --config Debug
    cmake --build %BUILD_DIR% --config Release
    pause
    

    13. 在 PowerShell 执行 .ps1 脚本

    对于 Linux shell, 执行一个 .sh 脚本有如下方法:

    # 方法1
    bash ./hello.sh
    # 方法2
    chmod +x ./hello.sh
    ./run.sh
    

    对于 .cmd/.bat 文件, 在 cmd 中这样执行:

    run hello
    .\hello.bat
    .\hello.cmd
    

    对于 PowerShell 中的 .ps1 脚本, 这样运行:

    .\hello.ps1
    

    14. 遇到报错立即停止

    在 Linux bash 脚本中, 通常在最开头会写这样一行:

    set -e
    cmake -S . -B build .... # 其他命令, 这里只是模拟
    

    PowerShell 中对应的写法是:

    $ErrorActionPreference = "Stop"
    cmake -S . -B build .... # 其他命令, 这里只是模拟
    

    15. which 命令的替代

    Linux shell 中, 使用 which xxx 来给出 xxx 命令的完整路径。 当存在多个版本的 xxx 时, 这尤其有用。

    1) Get-Command xxx

    在 PowerShell 中使用 Get-Command 来达到类似效果:

    PS C:\Users\zz> Get-Command ninja
    CommandType     Name                                               Version    Source
    -----------     ----                                               -------    ------
    Application     ninja.exe                                          0.0.0.0    D:\soft\ninja\1.11.1\ninja.exe
    

    2) 改为单行显示

    Get-Command ninja | Select-Object -ExpandProperty Source
    

    运行结果:

    PS C:\Users\zz9555> Get-Command ninja | Select-Object -ExpandProperty Source
    D:\soft\ninja\1.11.1\ninja.exe
    

    3) 更进一步: 创建 which 命令

    其实是增加命令别名。

    notepad $PROFILE
    

    增加如下内容:

    function Get-CommandPath {
        param($cmd)
        Get-Command $cmd | Select-Object -ExpandProperty Source
    New-Alias -Name which -Value Get-CommandPath
    

    16. 创建 Alias (命令别名)

    举例:映射 v 命令为 nvim 的别名。 在 Linux shell 下, 是这样做的(在 ~/.bashrc 中配置,或在当前shell临时配置):

    alias v='nvim'
    

    对应的 PowerShell 配置方式: 编辑配置文件:

    nvim $PROFILE
    

    增加如下内容:

    New-Alias -Name v -Value nvim
    

    17. 查看 Alias (命令别名)

    在 Linux shell 中, 使用 alias xx 查看 xx 是哪个命令的别名。例如:

    $ alias ls
    ls='ls -G'
    

    在 PowerShell 下使用 Get-Alias 查询:

    Get-Alias dir,echo,type
    

    ref: games002 课件 https://games-cn.org/games002-slides/

    18. 重命名目录

    在 Linux shell 中,使用 mv old_dir new_dir 来重命名目录。

    在 PowerShell 下使用 Rename-Item 命令, 以及 -NewName 选项:

    Rename-Item -Path "old_folder" -NewName "new_folder"
    

    如果没提供 -NewName 选项和新目录名字, 则会进入交互界面,提示输入新的目录名字。

    19. 删除目录

    在 Linux shell 中, 使用 rm -rf dir 来删除目录。

    在 PowerShell 下使用 Remove-Item -Path dir 来删除目录:

    Remove-Item -Path "d:\dbg\a1" -Recursive
    Remove-Item -Path "d:\dbg\a1" -Recursive -Force
    

    20. 输入 Python 命令,啥报错也没有

    当几乎清空了 PATH 时, PATH 里已经没有 Python 的路径, 此时输入 Python, 预期是报错。 但是 PowerShell 不报错, 什么提示都没有。

    解决办法: