VS Code源码学习(二):.sh & .bat &.cmd

688 阅读8分钟
  1. Shell脚本

Shell 脚本(.sh文件)是一种用于自动化执行命令的脚本语言,常用于 Unix/Linux 系统。

  1. 基本语法 已经熟悉的可以跳过 可以忽略碎碎念学习其他文档

  • 指定脚本解释器(必须放在第一行):

    • #!/bin/sh    # 使用系统默认的 Shell(通常是 Bourne Shell)
      #!/bin/bash   # 使用 Bash(功能更丰富)
      
  • 单行注释

# 这是单行注释
  • 变量
# 变量名区分大小写,无需声明类型:
name="Alice"     # 定义变量(等号两侧不能有空格!)
echo "Hello $name"  # 使用变量:输出 Hello Alice

# 环境变量
echo $PATH     # 输出系统环境变量
export MY_VAR="value"  # 将变量导出为环境变量

# 特殊变量:
echo $0   # 脚本名称
echo $1   # 第一个参数
echo $#   # 参数个数
echo $@   # 所有参数
echo $$   # 当前进程ID
echo $?   # 上一条命令的退出状态(0 表示成功)
  • 输入输出
# 输出内容
echo "Hello World"    # 普通输出
printf "Name: %s\n" $name  # 格式化输出

# 读取输入
read -p "Enter your name: " name
echo "You entered: $name"
  • 控制结构
# 条件判断  bash拓展为 [[]]
if [ $num -gt 10 ]; then
  echo "大于 10"
elif [ $num -eq 10 ]; then
  echo "等于 10"
else
  echo "小于 10"
fi

# for 循环
for i in {1..5}; do
  echo "Number: $i"
done

# while 循环
count=0
while [ $count -lt 5 ]; do
  echo "Count: $count"
  count=$((count + 1))
done
  • 函数
# 函数的定义与调用
greet() {
  echo "Hello, $1!"
}
greet "Bob"  # 调用函数,输出 Hello, Bob!

# 返回值
is_even() {
  if [ $(($1 % 2)) -eq 0 ]; then
    return 0  # 偶数返回 0
  else
    return 1  # 奇数返回 1
  fi
}
is_even 4 && echo "偶数" || echo "奇数"
  • 文件操作与测试
# 文件存在性检查
if [ -f "/path/to/file.txt" ]; then
  echo "文件存在"
fi

# 常见测试操作符
[ -d "/path" ]   # 是否是目录
[ -x "/path" ]   # 是否可执行
[ "$a" == "$b" ] # 字符串相等
  • 错误处理
# 退出脚本
if [ ! -f "required.txt" ]; then
  echo "文件不存在,退出!" >&2
  exit 1  # 非零退出码表示错误
fi

# 忽略错误
command_that_might_fail || true  # 即使失败也继续执行
  • 常见操作符

    • == : 相等
    • != : 不相等
    • -z : 字符串为空
    • -n : 字符串不为空
    • -f : 文件存在
    • -d : 目录存在
    • =~ :正则表达式匹配
  • OSTYPE存储操作系统的名称

#! /bin/bash
 
echo "use OSTYPE:"
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
    echo "Linux platform"
elif [[ "$OSTYPE" == "darwin"* ]]; then
    echo "Mac platform"
elif [[ "$OSTYPE" == "cygwin" ]]; then
    echo "Windows platform: Cygwin"
elif [[ "$OSTYPE" == "msys" ]]; then
    echo "Windows platform: MinGW"
# elif [[ "$OSTYPE" == "win32" ]]; then # not sure
#     echo "Windows platform"
elif [[ "$OSTYPE" == "freebsd"* ]]; then
    echo "FreeBSD platform"
else
    echo "Unknown platform: $OSTYPE"
fi
 
echo -e "\nuse uname:"
#os="`uname`" # os="$(uname)"
#echo "platform: ${os}" # echo "platform: $(uname)"
if [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then
    echo "Linux platform"
elif [ "$(uname)" == "Darwin" ]; then
    echo "Mac platform"
elif [ "$(expr substr $(uname -s) 1 9)" == "CYGWIN_NT" ]; then
    echo "Windows platform: Cygwin"
elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]; then
    echo "Windows platform: MinGW 64"
else
    echo "Unknown platform: $(uname)"
fi
 
echo -e "\nsystem architecture: $HOSTTYPE"
 
echo "test finish"
  • 注意事项

    • 引号使用:

      1. 单引号('):保持字符串字面值
      2. 双引号("):允许变量展开
      3. 反引号(`)或$():命令替换
    • 路径操作:

      1. dirname: 获取路径的目录部分
      2. readlink: 读取符号链接的目标
      3. pwd: 当前工作目录
    • 环境变量:

      1. 使用export设置环境变量
      2. 环境变量通常大写
    • 命令执行:

      1. command &: 后台执行
      2. command1 && command2: 前一个成功才执行后一个
      3. command1 || command2: 前一个失败才执行后一个
    • 权限问题:执行脚本前需添加权限 ``chmod +x script.sh

    • 调试脚本:使用 -x 参数跟踪执行:bash -x script.sh

  1. 示例:code-cli.sh 使用Bash

#!/usr/bin/env bash  # 使用 Bash(功能更丰富)

if [[ "$OSTYPE" == "darwin"* ]]; then     
    realpath() { [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"; }
    ROOT=$(dirname $(dirname $(realpath "$0")))
else
    ROOT=$(dirname $(dirname $(readlink -f $0)))
fi

function code() {
    cd $ROOT

    if [[ "$OSTYPE" == "darwin"* ]]; then
        NAME=`node -p "require('./product.json').nameLong"`
        CODE="./.build/electron/$NAME.app/Contents/MacOS/Electron"
    else
        NAME=`node -p "require('./product.json').applicationName"`
        CODE=".build/electron/$NAME"
    fi

    # Get electron, compile, built-in extensions
    if [[ -z "${VSCODE_SKIP_PRELAUNCH}" ]]; then
        node build/lib/preLaunch.js
    fi

    # Manage built-in extensions
    if [[ "$1" == "--builtin" ]]; then
        exec "$CODE" build/builtin
        return
    fi

    ELECTRON_RUN_AS_NODE=1 \
    NODE_ENV=development \
    VSCODE_DEV=1 \
    ELECTRON_ENABLE_LOGGING=1 \
    ELECTRON_ENABLE_STACK_DUMPING=1 \
    "$CODE" --inspect=5874 "$ROOT/out/cli.js" . "$@"
}

code "$@"

此脚本解析

  • "$OSTYPE" == "darwin"* 为 Mac platform

      • 是一个通配符,用于匹配任意字符序列 # 会匹配: darwin18.0 darwin19.0 darwin20.0 等等
    • *OSTYPE*存储操作系统的名称 前文有介绍
  • 根目录确定

    • if [[ "$OSTYPE" == "darwin"* ]]; then     
          realpath() { [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"; }
          ROOT=$(dirname $(dirname $(realpath "$0")))
      else
          ROOT=$(dirname $(dirname $(readlink -f $0)))
      fi
      
    • 根据操作系统类型(是否为macOS)使用不同的方式获取脚本的真实路径
    • macOS没有内置的realpath命令,所以定义了一个realpath函数
    • 最终得到VS Code项目的根目录路径
    • /* 是一个模式,表示以斜杠 / 开头的任意字符序列
    • [[ $1 = /* ]] 判断第一个参数是否以斜杠开头,即是否是绝对路径
    • # 是字符串操作符,从开头删除匹配的模式
    • 所以 ${1#./} 会删除参数开头的 ./(如果存在的话)
    • $PWD 是绝对路径。它是一个环境变量,始终包含当前工作目录的完整绝对路径。
    • 其他系统使用 readlink -f 命令获取符号链接的真实路径
    • 用实例解释
    • # 假设当前目录是 /home/user
      
      # 例子1:绝对路径
      realpath "/etc/passwd"
      # $1 = "/etc/passwd"
      # [[ "/etc/passwd" = /* ]] 为真,因为以 / 开头
      # 所以输出 "/etc/passwd"
      
      # 例子2:相对路径
      realpath "test.txt"
      # $1 = "test.txt"
      # [[ "test.txt" = /* ]] 为假,因为不以 / 开头
      # 所以输出 "$PWD/test.txt"
      # 即输出 "/home/user/test.txt"
      
      # 例子3:以 ./ 开头的相对路径
      realpath "./test.txt"
      # $1 = "./test.txt"
      # [[ "./test.txt" = /* ]] 为假,因为以 ./ 开头
      # ${1#./} 去掉开头的 ./,变成 "test.txt"
      # 所以输出 "$PWD/test.txt"
      # 即输出 "/home/user/test.txt"
      
    • $0 是脚本本身的路径
    • dirname 命令用两次来获取上上级目录
    • 最终 ROOT 变量包含项目的根目录路径
  • 转入函数code,首先切换到项目根目录

  • 可执行文件路径确定

    • if [[ "$OSTYPE" == "darwin"* ]]; then
          NAME=`node -p "require('./product.json').nameLong"`
          CODE="./.build/electron/$NAME.app/Contents/MacOS/Electron"
      else
          NAME=`node -p "require('./product.json').applicationName"`
          CODE=".build/electron/$NAME"
      fi
      
    • 根据操作系统类型设置不同的路径
    • 使用 node 读取 product.json 中的配置
    • macOS:使用 .app 包结构
    • 其他系统:直接使用可执行文件
  • 预启动处理

    • if [[ -z "${VSCODE_SKIP_PRELAUNCH}" ]]; then
          node build/lib/preLaunch.js
      fi
      
    • -z 检查变量是否为空
    • 如果 VSCODE_SKIP_PRELAUNCH 未设置,执行预启动脚本
  • 内置扩展处理

    • if [[ "$1" == "--builtin" ]]; then
          exec "$CODE" build/builtin
          return
      fi
      
    • 检查第一个参数是否为 --builtin
    • 如果是,执行内置扩展相关操作并返回
  • VS Code 启动

    • ELECTRON_RUN_AS_NODE=1 \
      NODE_ENV=development \
      VSCODE_DEV=1 \
      ELECTRON_ENABLE_LOGGING=1 \
      ELECTRON_ENABLE_STACK_DUMPING=1 \
      "$CODE" --inspect=5874 "$ROOT/out/cli.js" . "$@"
      
    • ELECTRON_RUN_AS_NODE=1:以 Node 模式运行 Electron
    • NODE_ENV=development:设置开发环境
    • VSCODE_DEV=1:启用开发模式
    • ELECTRON_ENABLE_LOGGING=1:启用日志记录
    • ELECTRON_ENABLE_STACK_DUMPING=1:启用堆栈转储
    • --inspect=5874:设置调试端口
    • "$ROOT/out/cli.js":指定入口文件
    • .:当前目录
    • "$@":传递所有命令行参数
  • 执行 code 函数

  1. Windows系统自动化任务(.bat & .cmd)

  1. 基础语法

  1. 基本命令和语法:
  • @echo off 关闭命令回显,通常放在批处理文件开头
  • rem:: 注释语句
  • pause 暂停执行并等待用户按键
  • exit 退出批处理程序
  1. 变量:
  • 设置变量:set variable=value
  • 使用变量:%variable%
  • 命令行参数:%1, %2 等表示第一个、第二个参数
  • %~dp0 表示当前批处理文件所在的目录路径
  1. 条件语句:
if condition (
    command
) else (
    command
)
  1. 循环语句:
for %%i in (item1 item2) do (
    command
)
  1. 函数(标签):
:function_name
commands
goto :eof

调用函数:call :function_name

  1. 错误处理:
if errorlevel 1 (
    rem 处理错误
)
  1. 常用操作符:
  • > 输出重定向
  • >> 追加输出
  • < 输入重定向
  • | 管道
  • & 顺序执行多个命令
  • && 仅在前一个命令成功时执行后面的命令
  • || 仅在前一个命令失败时执行后面的命令
  1. 实用命令:
  • cd 切换目录
  • mkdir / md 创建目录
  • del 删除文件
  • copy 复制文件
  • move 移动文件
  • dir 列出目录内容
  1. code-cli.bat

@echo off
setlocal

title VSCode Dev

pushd %~dp0..

:: Get electron, compile, built-in extensions
if "%VSCODE_SKIP_PRELAUNCH%"=="" node build/lib/preLaunch.js

for /f "tokens=2 delims=:," %%a in ('findstr /R /C:""nameShort":.*" product.json') do set NAMESHORT=%%~a
set NAMESHORT=%NAMESHORT: "=%
set NAMESHORT=%NAMESHORT:"=%.exe
set CODE=".build\electron%NAMESHORT%"

:: Manage built-in extensions
if "%~1"=="--builtin" goto builtin

:: Configuration
set ELECTRON_RUN_AS_NODE=1
set NODE_ENV=development
set VSCODE_DEV=1
set ELECTRON_ENABLE_LOGGING=1
set ELECTRON_ENABLE_STACK_DUMPING=1

:: Launch Code
%CODE% --inspect=5874 out\cli.js %~dp0.. %*
goto end

:builtin
%CODE% build/builtin

:end

popd

endlocal
@echo off
setlocal

title VSCode Dev
  1. 初始化设置

    1. @echo off - 关闭命令回显
    2. setlocal - 创建局部变量环境,确保变量修改不会影响全局环境
    3. title VSCode Dev - 设置命令窗口标题
pushd %~dp0..
  1. 设置工作目录:

    1. pushd 保存当前目录并切换到新目录
    2. %~dp0 获取脚本所在目录的路径
    3. .. 切换到上级目录
if "%VSCODE_SKIP_PRELAUNCH%"=="" node build/lib/preLaunch.js
  1. 预启动检查:

    1. 如果没有设置 VSCODE_SKIP_PRELAUNCH 环境变量,则运行预启动脚本
    2. 预启动脚本可能包含获取 electron、编译代码、准备内置扩展等操作
for /f "tokens=2 delims=:," %%a in ('findstr /R /C:""nameShort":.*" product.json') do set NAMESHORT=%%~a
set NAMESHORT=%NAMESHORT: "=%
set NAMESHORT=%NAMESHORT:"=%.exe
set CODE=".build\electron%NAMESHORT%"
//product.json
{
    "nameShort": "Code - OSS",
    "nameLong": "Code - OSS",
    "applicationName": "code-oss",
    "dataFolderName": ".vscode-oss",
    "win32MutexName": "vscodeoss",
}
  1. 获取应用程序名称:

    1. 使用 findstrproduct.json 文件中查找 "nameShort" 字段

      • findstr /R /C:""nameShort":.*" product.json

        1. findstr:Windows系统下的文本搜索命令
        2. /R:使用正则表达式模式匹配
        3. /C:""nameShort":.*":搜索包含 "nameShort": 的行
        4. product.json:要搜索的文件
        5.       将会找到以下行:
        6. "nameShort": "Code - OSS"
          
      • for /f "tokens=2 delims=:," %%a

        1. for /f:这条命令启动一个循环,用于处理命令的输出。/f 标志表示循环将从命令的输出中读取。

        2. "tokens=2 delims=:,":这部分指定如何拆分每一行输入:

          • tokens=2 表示将捕获拆分后的第二个标记(或部分)。
          • delims=:, 表示拆分行的分隔符是冒号(:)和逗号(,)。这在解析类似 JSON 的结构时非常有用。
      • do set NAMESHORT=%%~a

        1. 将提取的值赋值给 NAMESHORT 环境变量
        2. %%~a 会去除周围的引号 "Code - OSS" => Code - OSS
    2. 提取应用程序名称并处理字符串(移除引号和空格)

      • : ":这是一个字符串替换操作,表示要查找 NAMESHORT 中的所有引号和前面的空格(" )。
      • =%:表示将找到的引号和前面的空格替换为空字符串,也就是说,删除引号和前面的空格。
    3. 添加 .exe 扩展名

    4. 设置完整的可执行文件路径

if "%~1"=="--builtin" goto builtin
  1. 内置扩展处理:

    1. 检查第一个命令行参数是否为 --builtin
    2. 如果是,跳转到 builtin 标签处理内置扩展
set ELECTRON_RUN_AS_NODE=1
set NODE_ENV=development
set VSCODE_DEV=1
set ELECTRON_ENABLE_LOGGING=1
set ELECTRON_ENABLE_STACK_DUMPING=1
  1. 开发环境配置:

    1. 设置各种开发环境相关的环境变量
    2. 启用日志记录和堆栈转储
    3. 设置为开发模式
%CODE% --inspect=5874 out\cli.js %~dp0.. %*
goto end
  1. 启动 VS Code:

    1. 启动编辑器,启用调试端口 5874
    2. %* 传递所有命令行参数
    3. 跳转到结束标签
:builtin
%CODE% build/builtin
  1. 内置扩展处理部分:

    1. 处理内置扩展的标签
    2. 使用特定参数启动编辑器
    3. :builtin:这是一个标签,表示脚本中的一个跳转点。标签以冒号(:)开头,可以在脚本的其他地方通过 goto 命令跳转到这里。
    4. %CODE%:这是一个变量,通常在脚本的其他地方定义。它的值可能是一个命令或程序的路径,用于执行某个操作。
    5. build/builtin:这是传递给 %CODE% 命令的参数。它通常表示一个构建操作的目标或配置。
:end
popd
endlocal
  1. 清理和结束:

    1. popd:这个命令用于返回到之前的目录。它与 pushd 命令配合使用,pushd 用于保存当前目录并切换到新的目录,而 popd 则是恢复到之前保存的目录。这对于在脚本中处理多个目录时非常有用。
    2. endlocal:这个命令用于结束一个 setlocal 环境。setlocal 命令用于创建一个局部环境,任何在此环境中所做的变量更改都不会影响全局环境。使用 endlocal 可以结束这个局部环境并恢复之前的环境设置。