乐闻世界logo
搜索文章和话题

面试题手册

pnpm 的全局 store 是什么?如何管理和清理?

pnpm 的全局 store 是其核心特性之一,用于存储所有项目的依赖包。Store 位置:# 默认位置~/.pnpm-store# Windows%LOCALAPPDATA%/pnpm/store# 自定义位置# .npmrcstore-dir = /path/to/custom/storeStore 结构:~/.pnpm-store/├── v3/ # store 版本│ ├── files/ # 实际文件存储│ │ ├── 00/ # 基于内容寻址的目录│ │ │ ├── abc123... # 文件内容│ │ │ └── def456...│ │ └── ...│ └── index/ # 索引文件│ └── ...└── metadata.json # 元数据内容寻址存储:pnpm 使用内容寻址(Content-Addressable Storage):// 文件存储路径基于内容 hashconst crypto = require('crypto');const content = fs.readFileSync('lodash.js');const hash = crypto .createHash('sha256') .update(content) .digest('hex');// 存储路径: ~/.pnpm-store/v3/files/00/abc123...Store 管理:# 查看 store 信息pnpm store status# 查看 store 路径pnpm store path# 清理未使用的包pnpm store prune# 验证 store 完整性pnpm store verifyStore 的优势:磁盘空间节省# 10个项目使用 lodash@4.17.21# npm: 10 × 1.4MB = 14MB# pnpm: 1 × 1.4MB = 1.4MB安装速度提升# 首次安装pnpm install lodash # 下载到 store,创建硬链接# 第二个项目安装pnpm install lodash # 直接从 store 创建硬链接,秒级完成跨项目共享# 项目 Acd project-apnpm install # 下载到 store# 项目 Bcd project-bpnpm install # 复用 store 中的包Store 清理策略:# 清理未引用的包pnpm store prune# 什么时候需要清理:# 1. 升级 pnpm 版本后# 2. 磁盘空间不足时# 3. 长时间未清理时多 Store 配置:# 不同项目使用不同 store# project-a/.npmrcstore-dir = /path/to/store-a# project-b/.npmrcstore-dir = /path/to/store-bStore 与硬链接的关系:全局 Store:~/.pnpm-store/v3/files/00/abc123 (实际文件)项目中的硬链接:project-a/node_modules/.pnpm/lodash@4.17.21/lodash.js → abc123project-b/node_modules/.pnpm/lodash@4.17.21/lodash.js → abc123project-c/node_modules/.pnpm/lodash@4.17.21/lodash.js → abc123# 所有硬链接指向同一个物理文件注意事项:跨文件系统问题# 硬链接不能跨文件系统# 如果项目在不同分区,需要配置 store 位置# .npmrcstore-dir = /same/filesystem/path权限问题# 确保 store 目录有正确权限chmod -R 755 ~/.pnpm-store
阅读 0·3月6日 21:35

pnpm 的性能优势体现在哪些方面?与 npm/Yarn 对比如何?

pnpm 的性能优势主要体现在安装速度、磁盘空间和内存使用三个方面。安装速度对比:# 测试场景:安装有 1000+ 依赖的项目# 首次安装(冷启动)npm install: 45syarn install: 38spnpm install: 25s# 二次安装(已有缓存)npm install: 15syarn install: 8spnpm install: 2s # 硬链接,几乎瞬间完成# 删除 node_modules 后重装npm install: 40syarn install: 35spnpm install: 3s # 从 store 创建硬链接磁盘空间节省:# 10个相同技术栈的项目# npm 方式每个项目 node_modules: ~500MB总占用: 500MB × 10 = 5GB# pnpm 方式全局 store: ~500MB每个项目硬链接: ~几KB(元数据)总占用: ~500MB# 节省空间: 90%性能优势原理:硬链接机制// npm/yarn 方式// 每个项目复制完整的文件project-a/node_modules/lodash.js // 1.4MBproject-b/node_modules/lodash.js // 1.4MBproject-c/node_modules/lodash.js // 1.4MB// 总计: 4.2MB// pnpm 方式// 所有项目共享同一物理文件~/.pnpm-store/lodash.js // 1.4MBproject-a/node_modules/lodash.js // 硬链接project-b/node_modules/lodash.js // 硬链接project-c/node_modules/lodash.js // 硬链接// 总计: 1.4MB内容寻址存储// 相同内容的文件只存储一份// 即使是不同包中的相同文件const hash = getContentHash(fileContent);// 存储路径: ~/.pnpm-store/files/${hash}并行下载# pnpm 默认并行下载依赖# npm 默认串行下载# .npmrc 配置network-concurrency=16 # 并发下载数内存使用优势:# pnpm 的 node_modules 结构更紧凑# 文件系统缓存更有效# 内存占用对比(大型项目)npm: ~800MByarn: ~750MBpnpm: ~400MB性能基准测试:// 使用 benchmark 测试const { execSync } = require('child_process');// 测试安装时间console.time('npm');execSync('npm install');console.timeEnd('npm'); // ~45sconsole.time('pnpm');execSync('pnpm install');console.timeEnd('pnpm'); // ~25s实际项目案例:# Vue 3 项目(约 1500 个依赖)npm install: 52s, 1.2GB 磁盘yarn install: 45s, 1.1GB 磁盘pnpm install: 28s, 450MB 磁盘# React 项目(约 800 个依赖)npm install: 35s, 800MB 磁盘yarn install: 30s, 750MB 磁盘pnpm install: 18s, 280MB 磁盘性能优化配置:# .npmrc# 增加并发数network-concurrency=16# 使用本地缓存prefer-frozen-lockfile=true# 禁用进度条(略微提升性能)reporter=silent性能对比总结:| 指标 | npm | Yarn | pnpm ||------|-----|------|------|| 首次安装 | 慢 | 中等 | 快 || 二次安装 | 中等 | 快 | 最快 || 磁盘占用 | 高 | 高 | 低 || 内存占用 | 高 | 中等 | 低 || 并行下载 | ❌ | ✅ | ✅ || 硬链接 | ❌ | ❌ | ✅ |适用场景:✅ 多项目开发环境✅ CI/CD 流水线✅ Monorepo 项目✅ 磁盘空间受限环境
阅读 0·3月6日 21:34

如何优化 React Query 的性能,有哪些常见的性能瓶颈和解决方案?

React Query 本身已经做了很多性能优化,但在大型应用中,仍然需要注意以下几点来进一步提升性能:常见性能瓶颈过度重新渲染:当查询数据更新时,可能导致组件不必要的重新渲染过多的并发查询:同时执行大量查询可能影响应用性能缓存配置不当:缓存策略不合理可能导致重复请求或内存占用过高查询键设计不当:过于复杂或频繁变化的查询键可能影响缓存效率大数据集处理:处理大量数据时的性能问题性能优化策略合理使用缓存配置staleTime:对于不常变化的数据,设置较长的 staleTimecacheTime:对于不常用的数据,设置较短的 cacheTime示例: javascript const { data } = useQuery('users', fetchUsers, { staleTime: 10 * 60 * 1000, // 10分钟 cacheTime: 30 * 60 * 1000, // 30分钟 });优化查询键使用稳定的查询键避免在查询键中包含频繁变化的值合理组织查询键的层次结构使用查询结果选择器只订阅组件需要的数据,减少不必要的重新渲染示例: javascript const { data: userName } = useQuery('user', fetchUser, { select: (data) => data.name, });批量查询和预取使用 useQueries 批量处理多个查询合理使用预取功能,提前获取可能需要的数据分页和无限滚动对于大数据集,使用分页或无限滚动避免一次性获取所有数据示例: javascript const { data, fetchNextPage, hasNextPage } = useInfiniteQuery( ['posts'], ({ pageParam = 1 }) => fetchPosts(pageParam), { getNextPageParam: (lastPage, pages) => lastPage.nextCursor, } );禁用不必要的特性根据需要禁用 refetchOnWindowFocus、refetchOnMount 等示例: javascript const { data } = useQuery('todos', fetchTodos, { refetchOnWindowFocus: false, refetchOnMount: false, });使用 React.memo 和 useMemo包装组件以避免不必要的重新渲染缓存计算结果监控和调试使用 React Query DevTools 监控查询状态和性能分析查询的执行时间和频率高级优化技巧查询合并:对于相似的查询,考虑合并为单个查询自定义缓存策略:根据业务需求实现自定义缓存逻辑使用持久化缓存:对于需要跨会话保存的数据,使用持久化缓存后台数据同步:使用后台同步功能,在空闲时更新数据通过合理应用这些优化策略,可以显著提升 React Query 在大型应用中的性能表现。
阅读 0·3月6日 21:34

React Query 的缓存机制是如何工作的,如何配置和管理缓存?

React Query 的缓存机制是其核心特性之一,它通过以下方式工作:缓存工作原理查询键(Query Keys):React Query 使用查询键(通常是字符串或数组)作为缓存的唯一标识符。相同查询键的请求会共享缓存数据。缓存存储:查询结果存储在内存中,按查询键组织。缓存状态:每个缓存项包含数据、时间戳、状态(新鲜/过期/失效)等信息。缓存生命周期:新鲜期(Fresh):数据最近获取,无需重新请求过期期(Stale):数据超过 staleTime,但未超过 gcTime失效期(Invalidated):数据被手动标记为失效垃圾回收:超过 gcTime 的数据会被从缓存中移除缓存配置全局配置:通过 QueryClient 配置默认缓存行为 const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 5 * 60 * 1000, // 5分钟 cacheTime: 10 * 60 * 1000, // 10分钟 retry: 3, }, }, });单个查询配置:在 useQuery 中覆盖默认配置 const { data } = useQuery('todos', fetchTodos, { staleTime: 10 * 60 * 1000, cacheTime: 30 * 60 * 1000, refetchOnWindowFocus: false, });缓存管理数据失效:手动标记查询为失效,触发重新获取 queryClient.invalidateQueries('todos');数据更新:直接更新缓存中的数据 queryClient.setQueryData('todos', oldTodos => [...oldTodos, newTodo]);数据预取:提前获取可能需要的数据 queryClient.prefetchQuery('user-1', () => fetchUser(1));缓存清除:从缓存中移除数据 queryClient.removeQueries('todos');缓存重置:清除所有缓存数据 queryClient.resetQueries();缓存策略最佳实践根据数据的更新频率设置合适的 staleTime对于频繁访问但不常变化的数据,增加 cacheTime利用预取提升用户体验在 mutations 后正确处理相关查询的失效使用查询键的层次结构(如 ['users', userId, 'todos'])实现更精细的缓存控制通过合理配置和管理缓存,可以显著提高应用性能,减少不必要的网络请求,同时确保数据的新鲜度。
阅读 0·3月6日 21:34

Shell 脚本中如何定义和调用函数?如何传递参数和返回值?

Shell 脚本中函数的定义、调用和参数传递机制如下:函数定义基本语法function_name() { commands}# 或者使用 function 关键字function function_name { commands}函数定义示例# 简单函数greet() { echo "Hello, World!"}# 带参数的函数greet_user() { echo "Hello, $1!"}# 带多个参数的函数add() { local sum=$(($1 + $2)) echo "Sum: $sum"}# 带默认值的函数backup_file() { local file=${1:-"default.txt"} local backup_dir=${2:-"./backup"} echo "Backing up $file to $backup_dir"}函数调用基本调用# 定义函数greet() { echo "Hello, World!"}# 调用函数greet# 输出: Hello, World!传递参数# 定义带参数的函数greet_user() { echo "Hello, $1!"}# 调用并传递参数greet_user "John"# 输出: Hello, John!# 传递多个参数add() { echo "$1 + $2 = $(($1 + $2))"}add 5 3# 输出: 5 + 3 = 8函数参数位置参数show_args() { echo "First argument: $1" echo "Second argument: $2" echo "Third argument: $3" echo "All arguments: $@" echo "Number of arguments: $#"}show_args "apple" "banana" "cherry"# 输出:# First argument: apple# Second argument: banana# Third argument: cherry# All arguments: apple banana cherry# Number of arguments: 3处理可变参数# 处理任意数量的参数sum_all() { local total=0 for num in "$@"; do total=$((total + num)) done echo "Total: $total"}sum_all 1 2 3 4 5# 输出: Total: 15局部变量local 关键字# 使用 local 声明局部变量counter=0increment() { local counter=10 echo "Inside function: $counter" counter=$((counter + 1)) echo "After increment: $counter"}incrementecho "Outside function: $counter"# 输出:# Inside function: 10# After increment: 11# Outside function: 0局部变量的重要性# 不使用 local 会导致全局变量被修改global_var="original"bad_function() { global_var="modified"}good_function() { local global_var="local only" echo "Local: $global_var"}bad_functionecho "After bad_function: $global_var"# 输出: After bad_function: modifiedgood_functionecho "After good_function: $global_var"# 输出: After good_function: modified返回值使用 return 返回状态码check_file() { if [ -f "$1" ]; then return 0 # 成功 else return 1 # 失败 fi}check_file "/etc/passwd"if [ $? -eq 0 ]; then echo "File exists"else echo "File does not exist"fi使用 echo 返回值# 返回字符串或计算结果get_username() { echo "John Doe"}username=$(get_username)echo "Username: $username"# 输出: Username: John Doe# 返回计算结果calculate() { echo $(($1 * $2))}result=$(calculate 5 4)echo "Result: $result"# 输出: Result: 20返回数组get_array() { local arr=("apple" "banana" "cherry") echo "${arr[@]}"}fruits=($(get_array))for fruit in "${fruits[@]}"; do echo "Fruit: $fruit"done函数库和导入创建函数库# 文件: mylib.sh#!/bin/bashlog_info() { echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $1"}log_error() { echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') - $1" >&2}check_command() { if command -v "$1" >/dev/null 2>&1; then return 0 else return 1 fi}导入函数库#!/bin/bash# 导入函数库source ./mylib.sh# 或者. ./mylib.sh# 使用库中的函数log_info "Script started"if check_command "git"; then log_info "Git is installed"else log_error "Git is not installed"fi实际应用示例#!/bin/bash# 函数 1: 检查文件是否存在check_file() { local file=$1 if [ -f "$file" ]; then return 0 else return 1 fi}# 函数 2: 创建备份create_backup() { local source=$1 local backup_dir=${2:-"./backup"} if ! check_file "$source"; then echo "Error: File $source does not exist" return 1 fi mkdir -p "$backup_dir" local timestamp=$(date +%Y%m%d_%H%M%S) local backup_file="$backup_dir/$(basename $source).$timestamp" cp "$source" "$backup_file" echo "Backup created: $backup_file" return 0}# 函数 3: 批量处理文件process_files() { local pattern=$1 local count=0 for file in $pattern; do if [ -f "$file" ]; then echo "Processing: $file" create_backup "$file" count=$((count + 1)) fi done echo "Processed $count files" return 0}# 函数 4: 显示使用帮助show_help() { echo "Usage: $0 [options]" echo "Options:" echo " -f <file> Backup single file" echo " -p <pattern> Backup files matching pattern" echo " -h Show this help"}# 主程序main() { case $1 in -f) if [ -n "$2" ]; then create_backup "$2" else echo "Error: No file specified" show_help fi ;; -p) if [ -n "$2" ]; then process_files "$2" else echo "Error: No pattern specified" show_help fi ;; -h) show_help ;; *) echo "Error: Invalid option" show_help ;; esac}# 执行主程序main "$@"函数最佳实践使用 local 声明局部变量: 避免污染全局命名空间函数名使用小写: 遵循 Shell 命名约定添加注释: 说明函数用途和参数检查参数: 验证输入参数的有效性返回适当的退出码: 0 表示成功,非 0 表示失败使用函数库: 将常用函数组织到单独的文件中避免副作用: 函数应该专注于单一职责
阅读 0·3月6日 21:33

Shell 脚本中常用的字符串操作有哪些?如何进行字符串拼接、截取和替换?

Shell 脚本中常用的字符串操作包括字符串拼接、截取、替换、比较和长度计算等。字符串定义基本定义# 定义字符串str1="Hello World"str2='Hello World'str3=Hello# 多行字符串str4="Line 1Line 2Line 3"字符串拼接简单拼接# 直接拼接str1="Hello"str2="World"str3=$str1" "$str2echo $str3 # 输出: Hello World# 使用引号str4="${str1} ${str2}"echo $str4 # 输出: Hello World# 多个字符串拼接str5="Hello" " " "World"echo $str5 # 输出: Hello World动态拼接# 拼接变量和文本name="John"greeting="Hello, $name!"echo $greeting # 输出: Hello, John!# 拼接命令输出date_str="Current date: $(date)"echo $date_str# 拼接数组元素arr=("apple" "banana" "cherry")str="${arr[0]}, ${arr[1]}, ${arr[2]}"echo $str # 输出: apple, banana, cherry字符串长度计算长度# 计算字符串长度str="Hello World"echo ${#str} # 输出: 11# 使用 exprexpr length "$str" # 输出: 11# 使用 awkecho "$str" | awk '{print length}'字符串截取基本截取str="Hello World"# 从指定位置开始截取echo ${str:0} # 输出: Hello Worldecho ${str:6} # 输出: World# 从指定位置截取指定长度echo ${str:0:5} # 输出: Helloecho ${str:6:5} # 输出: World# 从末尾截取echo ${str: -5} # 输出: Worldecho ${str: -5:3} # 输出: Wor删除子串str="Hello World"# 删除最短匹配的前缀echo ${str#He} # 输出: llo Worldecho ${str#*o} # 输出: World# 删除最长匹配的前缀echo ${str##*o} # 输出: rld# 删除最短匹配的后缀echo ${str%ld} # 输出: Hello Worecho ${str%o*} # 输出: Hello W# 删除最长匹配的后缀echo ${str%%o*} # 输出: Hell提取文件名和路径filepath="/path/to/file.txt"# 提取文件名filename=${filepath##*/}echo $filename # 输出: file.txt# 提取目录dirname=${filepath%/*}echo $dirname # 输出: /path/to# 提取扩展名extension=${filepath##*.}echo $extension # 输出: txt# 去除扩展名basename=${filename%.*}echo $basename # 输出: file字符串替换基本替换str="Hello World"# 替换第一个匹配echo ${str/World/Bash} # 输出: Hello Bash# 替换所有匹配echo ${str//o/O} # 输出: HellO WOrld# 删除匹配echo ${str/o/} # 输出: Hell Worldecho ${str//o/} # 输出: Hell Wrld前缀和后缀替换str="Hello World"# 替换前缀echo ${str/#Hello/Hi} # 输出: Hi World# 替换后缀echo ${str/%World/Bash} # 输出: Hello Bash大小写转换str="Hello World"# 转换为大写echo ${str^^} # 输出: HELLO WORLD# 转换为小写echo ${str,,} # 输出: hello world# 首字母大写echo ${str^} # 输出: Hello world字符串比较基本比较str1="Hello"str2="World"# 相等比较if [ "$str1" = "$str2" ]; then echo "Strings are equal"fi# 不等比较if [ "$str1" != "$str2" ]; then echo "Strings are not equal"fi# 使用 [[ ]]if [[ "$str1" == "$str2" ]]; then echo "Strings are equal"fi模式匹配str="Hello World"# 检查是否以指定字符串开头if [[ "$str" == Hello* ]]; then echo "Starts with Hello"fi# 检查是否以指定字符串结尾if [[ "$str" == *World ]]; then echo "Ends with World"fi# 检查是否包含指定字符串if [[ "$str" == *lo* ]]; then echo "Contains 'lo'"fi正则表达式匹配str="Hello123"# 使用 =~ 进行正则匹配if [[ "$str" =~ ^[A-Za-z]+$ ]]; then echo "Only letters"fiif [[ "$str" =~ ^[A-Za-z0-9]+$ ]]; then echo "Letters and numbers"fi# 提取匹配的子串if [[ "$str" =~ ([A-Za-z]+)([0-9]+) ]]; then echo "Letters: ${BASH_REMATCH[1]}" echo "Numbers: ${BASH_REMATCH[2]}"fi字符串分割使用 IFS 分割str="apple,banana,cherry"# 设置 IFS 并分割IFS=',' read -ra arr <<< "$str"echo "${arr[0]}" # 输出: appleecho "${arr[1]}" # 输出: bananaecho "${arr[2]}" # 输出: cherry# 遍历分割后的数组for item in "${arr[@]}"; do echo "Item: $item"done使用 cut 分割str="apple:banana:cherry"# 按分隔符分割echo "$str" | cut -d: -f1 # 输出: appleecho "$str" | cut -d: -f2 # 输出: bananaecho "$str" | cut -d: -f3 # 输出: cherry字符串去空去除空白字符str=" Hello World "# 去除前导空格str="${str#"${str%%[![:space:]]*}"}"# 去除尾部空格str="${str%"${str##*[![:space:]]}"}"echo "$str" # 输出: Hello World使用 sed 去空str=" Hello World "# 去除前导空格echo "$str" | sed 's/^[[:space:]]*//'# 去除尾部空格echo "$str" | sed 's/[[:space:]]*$//'# 去除所有空格echo "$str" | sed 's/[[:space:]]//g'实际应用示例验证输入# 验证邮箱格式validate_email() { local email="$1" if [[ "$email" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then echo "Valid email" return 0 else echo "Invalid email" return 1 fi}validate_email "user@example.com"格式化输出# 格式化字符串format_string() { local name="$1" local age="$2" local city="$3" printf "Name: %-20s Age: %3d City: %s\n" "$name" "$age" "$city"}format_string "John Doe" 25 "New York"批量重命名# 批量修改文件扩展名for file in *.txt; do new_name="${file%.txt}.bak" mv "$file" "$new_name"done日志解析# 解析日志行log_line="[2024-01-01 10:00:00] [INFO] User logged in"# 提取时间time="${log_line#\[}"time="${time%%\]*}"echo "Time: $time"# 提取日志级别level="${log_line#*\[}"level="${level%%\]*}"echo "Level: $level"# 提取消息message="${log_line#*\] }"echo "Message: $message"字符串操作最佳实践始终使用引号: 防止空格和特殊字符问题使用 ${} 而不是 $(): 字符串操作更高效检查字符串长度: 避免空字符串错误使用正则表达式验证输入: 提高数据质量使用 printf 格式化输出: 比 echo 更灵活注意大小写敏感: 使用 ^ 和 , 进行转换使用数组存储分割结果: 便于后续处理
阅读 0·3月6日 21:33

Shell 脚本中常用的循环结构有哪些?如何使用 for、while 和 until 循环?

Shell 脚本中常用的循环结构包括 for 循环、while 循环和 until 循环。for 循环基本语法for variable in listdo commandsdone遍历列表# 遍历字符串列表for fruit in apple banana orangedo echo "Fruit: $fruit"done# 遍历数字序列for i in 1 2 3 4 5do echo "Number: $i"done# 使用 seq 命令生成序列for i in $(seq 1 10)do echo "Number: $i"done# 使用 {start..end} 语法(Bash 3.0+)for i in {1..10}do echo "Number: $i"done# 指定步长for i in {0..10..2}do echo "Number: $i"doneC 风格 for 循环# C 风格语法for (( i=0; i<10; i++ ))do echo "Number: $i"done# 多变量for (( i=0, j=10; i<10; i++, j-- ))do echo "i=$i, j=$j"done遍历文件和目录# 遍历当前目录下的文件for file in *.txtdo echo "Processing: $file"done# 遍历命令输出for user in $(cut -d: -f1 /etc/passwd)do echo "User: $user"done# 递归遍历目录for file in $(find . -name "*.sh")do echo "Shell script: $file"donewhile 循环基本语法while conditiondo commandsdonewhile 循环示例# 计数器count=0while [ $count -lt 5 ]do echo "Count: $count" count=$((count + 1))done# 读取文件行while IFS= read -r linedo echo "Line: $line"done < input.txt# 读取用户输入while truedo read -p "Enter 'quit' to exit: " input if [ "$input" = "quit" ]; then break fi echo "You entered: $input"done# 等待进程结束while ps -p $PID > /dev/nulldo echo "Process is running..." sleep 1doneecho "Process finished"until 循环基本语法until conditiondo commandsdoneuntil 循环示例# 等待条件成立count=0until [ $count -ge 5 ]do echo "Count: $count" count=$((count + 1))done# 等待服务启动until curl -s http://localhost:8080 > /dev/nulldo echo "Waiting for service..." sleep 2doneecho "Service is ready"循环控制语句break 语句# 跳出循环for i in {1..10}do if [ $i -eq 5 ]; then break fi echo "Number: $i"done# 输出: 1 2 3 4continue 语句# 跳过当前迭代for i in {1..10}do if [ $((i % 2)) -eq 0 ]; then continue fi echo "Odd number: $i"done# 输出: 1 3 5 7 9break 和 continue 指定层级# 跳出多层循环for i in {1..3}do for j in {1..3} do if [ $i -eq 2 ] && [ $j -eq 2 ]; then break 2 fi echo "i=$i, j=$j" donedone实际应用示例#!/bin/bash# 示例 1: 批量处理文件echo "Processing text files..."for file in *.txtdo if [ -f "$file" ]; then echo "Processing: $file" # 在这里添加处理逻辑 cp "$file" "backup_$file" fidone# 示例 2: 监控进程echo "Monitoring process..."while truedo if ps aux | grep -q "[m]yprocess"; then echo "Process is running" else echo "Process stopped" break fi sleep 5done# 示例 3: 等待多个服务services=("service1" "service2" "service3")for service in "${services[@]}"do echo "Waiting for $service..." until systemctl is-active --quiet "$service" do sleep 2 done echo "$service is ready"done# 示例 4: 读取配置文件echo "Reading configuration..."while IFS='=' read -r key valuedo # 跳过注释和空行 [[ $key =~ ^#.*$ ]] && continue [[ -z $key ]] && continue echo "Config: $key = $value"done < config.txt# 示例 5: 并行处理max_parallel=3count=0for task in task1 task2 task3 task4 task5do # 启动后台任务 echo "Starting $task..." ( sleep 2 echo "$task completed" ) & count=$((count + 1)) # 控制并发数 if [ $((count % max_parallel)) -eq 0 ]; then wait fidonewaitecho "All tasks completed"循环最佳实践使用引号包裹变量: 防止空格和特殊字符问题设置 IFS 处理特殊分隔符: IFS= read -r line使用 break 和 continue 控制循环: 提高代码可读性避免无限循环: 确保 while 和 until 循环有退出条件使用 wait 等待后台进程: 确保所有任务完成优先使用 C 风格 for 循环: 处理数字序列时更清晰使用数组遍历: for item in "${array[@]}"
阅读 0·3月6日 21:33

Shell 脚本中的重定向和管道机制是什么?如何使用?

Shell 脚本中的重定向和管道机制是进程间通信和数据流控制的重要方式。标准输入输出三种标准流stdin (0) # 标准输入 - 默认从键盘读取stdout (1) # 标准输出 - 默认输出到屏幕stderr (2) # 标准错误 - 默认输出到屏幕输出重定向重定向标准输出# 覆盖重定向(>)echo "Hello" > file.txt # 创建或覆盖文件ls -l > filelist.txt# 追加重定向(>>)echo "World" >> file.txt # 追加到文件末尾date >> log.txt重定向标准错误# 重定向错误输出ls /nonexistent 2> error.log# 追加错误输出ls /nonexistent 2>> error.log# 同时重定向输出和错误command > output.txt 2>&1command &> output.txt # Bash 4.0+ 简写# 分别重定向command > output.txt 2> error.txt丢弃输出# 丢弃标准输出command > /dev/null# 丢弃错误输出command 2> /dev/null# 丢弃所有输出command > /dev/null 2>&1command &> /dev/null输入重定向从文件读取# 从文件读取输入wc -l < file.txt# 多行输入cat << EOF > script.sh#!/bin/bashecho "Hello, World!"EOFHere Document# 多行输入cat << EOFThis is line 1This is line 2This is line 3EOF# 使用变量name="John"cat << EOFHello, $name!EOF# 禁用变量替换cat << 'EOF'This is $name - not expandedEOFHere String# 单行输入wc -w <<< "Hello World"# 处理变量text="Hello World"grep "World" <<< "$text"管道基本管道# 将一个命令的输出作为另一个命令的输入ps aux | grep nginxcat file.txt | grep "pattern"ls -l | sort -k5 -n# 多个管道连接cat file.txt | grep "pattern" | wc -l管道与重定向结合# 管道输出到文件command | tee output.txt# 管道错误输出command 2>&1 | grep "error"# 从管道读取输入while read line; do echo "Line: $line"done < <(ls -l)进程替换基本语法# 输出进程替换(<())diff <(ls dir1) <(ls dir2)# 输入进程替换(>())tar -cf >(gzip > archive.tar.gz) directory/# 多个进程替换paste <(cut -f1 file1) <(cut -f2 file2)实际应用# 比较两个目录diff <(ls dir1) <(ls dir2)# 合并多个文件paste <(cut -d: -f1 /etc/passwd) <(cut -d: -f3 /etc/passwd)# 实时监控tail -f /var/log/syslog | grep --line-buffered "ERROR" | tee errors.log高级重定向文件描述符操作# 打开自定义文件描述符exec 3> output.txtecho "Line 1" >&3echo "Line 2" >&3exec 3>&-# 从文件描述符读取exec 4< input.txtread line <&4echo "$line"exec 4<&-# 交换文件描述符command 3>&1 1>&2 2>&3同时读写# 同时读写同一文件exec 3<> file.txtread -u 3 lineecho "New content" >&3exec 3>&-实际应用示例日志处理# 分离正常输出和错误输出./script.sh 1> success.log 2> error.log# 合并日志./script.sh > all.log 2>&1# 实时监控日志tail -f /var/log/app.log | grep --line-buffered "ERROR" | tee errors.log数据处理# 处理 CSV 文件cat data.csv | cut -d, -f1,3 | sort -u > output.txt# 统计信息cat access.log | awk '{print $1}' | sort | uniq -c | sort -rn > stats.txt# 批量处理find . -name "*.txt" -exec cat {} \; | grep "pattern" > results.txt系统管理# 备份重要命令输出df -h > disk_usage_$(date +%Y%m%d).txtps aux > process_list_$(date +%Y%m%d).txt# 监控系统while true; do date >> monitor.log free -m >> monitor.log echo "---" >> monitor.log sleep 60done脚本开发# 捕获命令输出result=$(command)echo "Result: $result"# 处理多行输出while IFS= read -r line; do echo "Processing: $line"done < <(find . -name "*.sh")# 创建临时文件tmpfile=$(mktemp)command > "$tmpfile"# 处理文件rm -f "$tmpfile"最佳实践使用 /dev/null 丢弃不需要的输出: 减少日志噪音分别处理 stdout 和 stderr: 便于调试和日志分析使用管道组合命令: 提高处理效率使用 tee 同时输出和保存: 便于实时监控注意管道的缓冲: 使用 --line-buffered 处理实时数据使用进程替换: 避免创建临时文件及时关闭文件描述符: 防止资源泄漏使用 mktemp 创建临时文件: 避免文件名冲突
阅读 0·3月6日 21:33