及格线上的shell
- shell是古老的
- shell是可爱的
- shell是标准的
- 变量(Variable)
- 环境变量(evn)
- 参数(parameters)
- 内置命令(buildin)
- 引用(quote)
- 通配符(globs)
- 重定向(redirect)
- 各类括号
- 非标准特性
- 条件(if)
- 循环(for/while)
- 读取输入(read)
- 函数(Function)
- 管道(pipe)
- 参数扩展(
${var}) - 后台进程
- 子shell(subshell)
- 信号捕获(trap)
- 错误处理
- 调试 (Debug)
了解以下内容, 在shell的世界应该能做到生活自理...
shell是古老的
有些老毛病...
x = 2赋值不好使!- 光引号就有四种, 反引号
\, 半引号`, 双引号", 单引号'!! if居然需要启动一个进程来执行判断!!!
shell是可爱的
很容易上手
ls /tmp| | |------------------------------------------------------------------------| | 2f093fe9-a235-5d1c-9a62-3fae2cdf7eb1 | | 7c3338c5-8eec-50ae-bc4a-1f6969419936 | | 9e923688bfa34288aa93e2070da546bf | | A262AD1E-C7E0-42C3-8A20-E532F767EEEE~16308~ | | KoDF0sohej4R~bzJiALaHLuEGahtZOBlPKVzyXue1pw~ | | com.apple.launchd.FMcex23J1V | | com.logi.optionsplus.agent.logs | | com.logi.optionsplus.logivoice.logs | | com.logi.optionsplus.updater.logs | | devio~semaphorelogihppOptionsPlusA7D4B139~-F9F9-44A6-9F5D-7BC0A9B8B80F | | homebrew.mxcl.emacs-plus.stderr.log | | image | | logitech~kirosagent~-80c9ef0fb86369cd25f90af27ef53a9e | | logitech~kiroslogivoice~-80c9ef0fb86369cd25f90af27ef53a9e | | logitech~kirosupdater~ | | perfcount | | powerlog |管道和重定向非常简单易用
ls /tmp | wc -l17很适合批处理文件操作
for f in *.png; do echo "$f" '->' "${f%.png}.jpg" doneloop.png -> loop.jpg可以多进程执行
for i in {1..12}; do sh -c 'echo 来自进程 $$ 的❤️' & done wait来自进程 90138 的❤️ 来自进程 90139 的❤️ 来自进程 90140 的❤️ 来自进程 90142 的❤️ 来自进程 90141 的❤️ 来自进程 90143 的❤️ 来自进程 90144 的❤️ 来自进程 90145 的❤️ 来自进程 90146 的❤️ 来自进程 90147 的❤️ 来自进程 90149 的❤️ 来自进程 90148 的❤️功能非常稳定, 30年前如此, 10年后依然如此.
非常适合一些任务, 但也有一些局限
- 不能显示图
- 没有
数字这个数据类型
shell是标准的
多种 Unix shell, 包括 bash, zsh, fish, tcsh 等等, 查看用户的默认 shell.
echo $SHELL/bin/zshPOSIX标准定义了Unix shell的工作方式. 如果脚本符合POSIX标准, 可以在不同的shell上正常运行.
不同的shell具有额外的功能, 这些功能不在POSIX标准中,
本文介绍的都是 Bash/zsh 脚本, 大部分内容适用于其他shell.
变量(Variable)
定义变量, 不能有空格, 以下为错误示例
a = 100; echo $aa=100; echo $a100a = 100相当与调用函数a, 并且传给它两个参数=和100.何使用变量
echo $USER " " $HOME " " $PATHa123 /Users/a123 /usr/local/opt/sqlite/bin:/Users/a123/.jenv/shims:/usr/local/Cellar/apache-flink/1.17.1/libexec/bin:/usr/local/opt/dotnet@6/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/Library/Apple/usr/bin:/Library/TeX/texbin:/Applications/Wireshark.app/Contents/MacOS:/Applications/VMware Fusion.app/Contents/Public:/usr/local/share/dotnet:~/.dotnet/tools:/Users/a123/.cargo/bin:/Users/a123/.dotnet/tools:/usr/local/opt/fzf/binBash 中只有字符串, 没有数字这个 类型.
a=2 b="2"用变量的时候, 双引号可以避免错误.
fileName="abc edf.txt" echo "$fileName" echo $fileNameabc edf.txt abc edf.txt防不小心加上的 后缀, 使用
${}.a=大熊猫 echo '$a2:' "$a2" echo '${a}2:' ${a}2$a2: ${a}2: 大熊猫2
环境变量(evn)
每个进程都有环境变量, 查看当前 shell 的环境变量.
envDISPLAY=123deiMac.local TERM=dumb MANPATH= LANG=en_US.UTF-8 __CF_USER_TEXT_ENCODING=0x1F5:0x19:0x34 LOGNAME=a123 HOME=/Users/a123 SHLVL=1 XPC_SERVICE_NAME=0 XPC_FLAGS=0x0 PATH=/usr/local/opt/sqlite/bin:/Users/a123/.jenv/shims:/usr/local/Cellar/apache-flink/1.17.1/libexec/bin:/usr/local/opt/dotnet@6/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/Library/Apple/usr/bin:/Library/TeX/texbin:/Applications/Wireshark.app/Contents/MacOS:/Applications/VMware Fusion.app/Contents/Public:/usr/local/share/dotnet:~/.dotnet/tools:/Users/a123/.cargo/bin:/Users/a123/.dotnet/tools:/usr/local/opt/fzf/bin SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.FMcex23J1V/Listeners USER=a123 TMPDIR=/var/folders/gl/10x44fsn6bn1799lkf_g096r0000gn/T/ SHELL=/bin/zsh PWD=/Users/a123/sandbox/rc/learn-clojure/essays OLDPWD=/Users/a123/sandbox/rc/learn-clojure/essays _=/usr/bin/envBash 脚本中两种类型的变量: 环境变量和 shell 变量, 这两种变量的访问方式相同.
echo 系统环境变量: '$HOME' "$HOME" shellVar=shell变量 echo shell变浪 $shellVar系统环境变量: $HOME /Users/a123 shell变浪 shell变量设置环境变量, 可以使用
export命令, 也可以将 shell 变量转换为环境变量.export abc="10000" env | grep abcabc=10000子进程会继承父进程的环境变量, 在shell启动配置
.bashrc/.zshrc文件中设置的 变量会影响所有从终端启动的程序.shell 变量不会被继承, 也就是说, 在当前进程中设置的 shell 变量不会被子进程继承.
## 定义一个在当前Shell中的变量, 但不导出 PARENT_VAR="shell变量, 不导出." ## 启动一个子Shell, 检查该变量是否可见 bash -c 'echo "在子shell: PARENT_VAR = $PARENT_VAR"' ## 导出变量后再次检查 export PARENT_VAR="I am exported." echo "当前 shell: PARENT_VAR = $PARENT_VAR" bash -c 'echo "在子shell: PARENT_VAR = $PARENT_VAR"'在子shell: PARENT_VAR = 当前 shell: PARENT_VAR = I am exported. 在子shell: PARENT_VAR = I am exported.如何在启动程序时设置环境变量
MY_VAR="some_value" ./my_program或者
export MY_VAR="some_value" ./my_program
参数(parameters)
如何获取脚本的参数, 使用
$0,$1,$2等变量来访问.svg2png a.svg a.png | | | v v v $0 $1 $2参数在编写简单脚本时的作用
#!/bin/bash inkscape "$1" -b white --export-png="$2"以上脚本保存为
svg2png.sh执行的时候
svg2png a.svg a.png$@变量可以获取除了$0之外的所有参数#!/bin/bash bash -c 'echo "$@"' arg1 arg2 arg3arg2 arg3loop循环
#!/bin/bash bash -c 'for i in "$@" do echo $i done' arg1 arg2 arg3arg2 arg3shift命令可以移除第一个参数#!/usr/bin/env bash bash -c 'echo "$@" shift echo "$@"' arg1 arg2 arg3arg2 arg3 arg3
内置命令(buildin)
大多数 Bash 命令都是程序
which ls/bin/lsBash 中也有一些命令 比如 type, source alias read declare printf echo cd
type sh type echosh is /bin/sh echo is a shell builtinalias的用处
alias gc="git commit"放到
.bashrc,.zshrc都很有用
引用(quote)
双引号会展开变量, 而单引号不会展开变量.
echo '$HOME' echo "$HOME"$HOME /Users/a123字符串可以是多行
echo "这是一个 多行的 字符串"这是一个 多行的 字符串字符串靠在一起就会拼在一起,
+符号不能用于字符串拼接.echo "你好""青岛"你好青岛echo "你好"+"青岛"你好+青岛如果要输出
"和', 需要用反引号\echo ""''双引号单引号不"引起来"是看不到的 echo \' 前面有单引号 echo \" 前面有双引号双引号单引号不引起来是看不到的 ' 前面有单引号 " 前面有双引号
通配符(globs)
通配符的作用, 它可以用来匹配字符串, 通配符和正则表达式不太一样
symbol globs regex ? 1 char optional * 0+ char multiple [] any char class Bash 如何使用通配符来匹配文件名, 并用一个例子说明了如何使用通配符来查找所有以
.txt结尾的文件.echo cat *.txtcat a.txt abc.txt b.txt 谭总问题.txt- cat 是完全不知道通配这个事情的
- shell展开为
exec(cat, a.txt, abc.txt, b.txt)
通配符中常用的三个特殊字符:
*,?和[], 并说明了它们的含义.如何在命令中传递一个字面意义的星号, 并说明了需要使用引号来避免 Bash 将星号解释为通配符.
重定向(redirect)
Unix 程序通常使用标准输入(stdin), 标准输出(stdout)和标准错误输出(stderr)与终端交互
wc shell.org330 912 10530 shell.org把文件重定向到wc程序的stdin
wc < shell.org330 912 10530cat的stdout作为wc的stdin
cat shell.org | wc347 945 108012>&1符号用于将标准错误输出重定向到标准输出 重定向本质上是x>y重定义x到yx operator y usage [1,2,file, default to 1] [<>] [&1,&2,file] redirect x to y 举例
operator usage >x stdout to x 1>x stdout to x 2>x stderr to x 1>&2 stdout to stderr 合并stderr到stdout
ls 2>&110things-learned.org 2022-books.org 数学雨伞下.org 文档的重要性.org 加缪写给老师的信.org/dev/null文件的作用, 它可以用来忽略程序的输出ls > /dev/null前面的
sudo命令不会影响重定向操作sudo echo x > abcecho x | sudo tee abc
各类括号
(...)用于声明一个数组a=(1 2 3) echo "$a"1 2 3(...)用于在子 shell 中执行命令 在新的进程中执行命令(cd .. ; pwd) pwd结果可以被赋值给变量
a=$(cd .. ; pwd) echo $a/Users/a123/sandbox/rc/learn-clojure{...}用于将多个命令分组, 并在同一个进程中执行{cd .. ; pwd} pwd/Users/a123/sandbox/rc/learn-clojure /Users/a123/sandbox/rc/learn-clojure$((...))用于进行算术运算echo $((1 + 1))2[...]用于判断条件 说来可能很难让人相信, 至少在linux上/usr/bin/[是个程序, 对应test, [_ _]_位置必须留空格.if [ "abc" = "abc" ]; then echo good figood[[...]]用于判断条件 是首选, 它可以更便捷地处理模式匹配, 正则表达式以及更直观的逻辑条件if [[ "abc" = "abc" ]]; then echo good figood
非标准特性
Bash/zsh 中一些功能不符合 POSIX 标准, 并且在 dash 和 sh 等 POSIX shell 中无法使用.
[[...]]运算符, 扩展语法, 替代POSIX[...]运算符.a.{png,svg}语法, 它可以用来生成多个文件echo *.{org,md}diff <(./cmd1) <(./cmd2)语法, 它使用进程替换来比较两个命令的输出, POSIX 只能用命名管道(named pipe).local关键字, 它可以用来声明局部变量, POSIX 只有全局变量.for ((i=0; i < 3; i++))循环, 它类似于 C 语言中的循环, POSIX只能使用for x in ...循环.
条件(if)
shell 中每个命令都有一个退出状态, 0 表示成功, 其他数字表示失败. shell 将最后一个命令的退出状态存储在一个名为
$?的特殊变量中.0 代表成功, 因为成功只有一种情况, 而失败有很多种情况.
grep命令为例, 说明了grep命令的退出状态可能为 1 或 2, 分别代表未找到匹配项和文件不存在.grep "shell 中每个" shell.org > /dev/null; echo $?0grep "!!!!!!!!" shell.org ; echo $?1grep "!!!!!!!!" no-such-file ; echo $?2shell 中
if语句的语法, 它会执行COMMAND, 如果COMMAND返回 0(表示成功), 则执行then后面的语句.if grep "shell 中每个" shell.org > /dev/null; then echo "成功" fi成功
循环(for/while)
for循环的语法, 并用一个例子说明了如何使用for循环来遍历一个列表.fruits=("苹果" "香蕉" "橙子") for fruit in "${fruits[@]}" do echo "我喜欢吃 $fruit" done我喜欢吃 苹果 我喜欢吃 香蕉 我喜欢吃 橙子for循环中分号的使用, 分号需要放在do和done之前, 而不能放在done之后.for i in 1 2 3; do echo "数字: $i"; done数字: 1 数字: 2 数字: 3for循环的实际作用, 它会遍历文件中的每个单词, 而不是每行text="Hello World This is a test" for word in $text do echo "单词: $word" done单词: Hello 单词: World 单词: This 单词: is 单词: a 单词: testzsh的行为是每行
text="Hello World This is a test" for word in $text do echo "单词: $word" done单词: Hello World This is a testwhile循环的语法, 并说明了它的工作原理类似于if语句, 它会执行COMMAND, 如果COMMAND返回 0(表示成功), 则执行do后面的语句.count=1 while [ $count -le 5 ] do echo "计数: $count" count=$((count + 1)) done计数: 1 计数: 2 计数: 3 计数: 4 计数: 5
读取输入(read)
read命令用于读取标准输入, 并用一个例子说明了如何使用read命令读取用户输入并将其存储到一个变量中.echo "请输入你的名字: " read name echo "你好, $name!"请输入你的名字:read命令可以同时读取多个变量, 并用一个例子说明了如何使用read命令读取两个变量.echo "请输入你的名字和年龄, 用空格分隔: " read name age echo "你好, $name! 你 $age 岁了. "请输入你的名字和年龄, 用空格分隔:read命令默认会去除空白字符, 并用一个例子说明了如何使用IFS变量来控制去除空白字符的行为.echo "请输入一个带空格的字符串: " read input echo "你输入的字符串是: '$input'"请输入一个带空格的字符串:echo "请输入以逗号分隔的名字和年龄: " IFS=',' read name age echo "名字: $name" echo "年龄: $age"请输入以逗号分隔的名字和年龄: 年龄:IFS变量在循环遍历文件时, 可以用来控制循环遍历的是每个单词还是每行.echo "按行遍历文件内容: " while IFS= read -r line do echo "行内容: $line" done < example.txt按行遍历文件内容:
函数(Function)
- 定义函数: 使用
function_name() { ... }语法定义. - 调用函数: 函数调用只需写函数名, 后面跟参数, 不需要括号.
- 参数: 函数参数以
$1,$2,$3等形式传递. - 退出码: 函数可以返回退出码(0 表示成功, 非零表示失败).
- 局部变量:
local关键字声明局部变量, 这些变量只在函数内部有效, 不会影响全局范围. - 返回值: 函数只能返回退出码, 不能返回字符串.
- 错误抑制:
local x=VALUE语法可以抑制访问可能不存在的变量时的错误.
#+BEGIN_SRC bash
#!/bin/bash
## 定义一个函数
function say_hello() {
echo "Hello, $1!"
}
## 调用函数, 注意不需要有括号!
say_hello "World"
## 定义一个返回退出码的函数
function failing_function() {
return 1
}
## 调用返回退出码的函数
failing_function
echo $?
## 定义一个使用局部变量的函数
function local_var() {
local x=$(date)
echo "Local variable x: $x"
}
## 调用使用局部变量的函数
local_var
Hello, World!
1
Local variable x: Tue Dec 31 09:40:07 CST 2024
管道(pipe)
管道的作用, 它可以将一个进程的输出作为另一个进程的输入
ls | wc -l108管道的工作原理, 它实际上是一对特殊的"文件描述符" ,一个用于写入, 另一个用于读取.
## 使用 exec 创建一个匿名管道, fd 3用于写入, fd 4用于读取 exec 3> >(wc -l) echo -e "Line1\nLine2\nLine3" >&3 exec 3>&-3- exec 3> >(wc -l): 创建一个匿名管道, 将文件描述符 3 指向 wc -l 的标准输入.
- echo -e "Line1
\nLine2{=latex}\nLine3{=latex}" >&3: 将三行文本写入文件描述符 3, 即传递给 wc -l. - exec 3>&-: 关闭文件描述符 3.
- wc -l 接收到三行输入, 并输出 3, 表示有三行.
管道是单向的, 只能从写入端写入数据, 不能从读取端写入数据.
## 创建一个管道 ls | wc -l ## 尝试从管道的读取端写入数据(错误示例) echo "Test" >&0100 Test命名管道, 它可以创建一个文件, 该文件可以像管道一样工作:
## 创建命名管道 mkfifo my_pipe ## 在后台运行一个进程, 从命名管道中读取数据并进行处理 cat my_pipe | wc -l & ## 将数据写入命名管道 echo -e "Line1\nLine2\nLine3" > my_pipe ## 清理 rm my_pipe3管道是进程通讯的基本方式之一, 所有的编程语言都可以利用管道, 不仅仅是shell
#include <stdio.h> #include <unistd.h> #include <sys/wait.h> // Include the header file for wait() function int main() { int pipefd[2]; pid_t pid; pipe(pipefd); // 创建管道 pid = fork(); // 创建子进程 if (pid == 0) { // 子进程 close(pipefd[1]); // 关闭写端 char buffer[100]; read(pipefd[0], buffer, sizeof(buffer)); // 读取数据 printf("子进程读取到: %s\n", buffer); close(pipefd[0]); // 关闭读端 } else { // 父进程 close(pipefd[0]); // 关闭读端 write(pipefd[1], "Hello from parent process!", 27); // 写入数据 close(pipefd[1]); // 关闭写端 wait(NULL); // 等待子进程结束 } return 0; }子进程读取到: Hello from parent process!
参数扩展(${var})
有别于 "$var" ${} 非常强大, 除了 保护变量 之外可以进行很多操作:
${var/bear/panda}和${var//bear/panda}用于替换字符串中的特定字符 text="Hello Bear eat Bear World"echo "${text/Bear/panda}"
echo "${text//Bear/Panda}"
#+end_srcHello panda eat Bear World Hello Panda eat Panda World${#var}用于获取字符串或数组的长度.var="World" ## 需要在变量后直接拼接字符 echo "Hello $var123" ## 尝试访问变量 var123 echo "Hello ${var}123" ## 尝试访问变量 var123 echo "Hello ${#var}123" ## 正确拼接 "World123"Hello Hello World123 Hello 5123${var:-$othervar}用于设置默认值, 如果var未定义, 则使用$othervar作为默认值.## var 未设置 echo "变量 var 的值: ${var:-默认值}" ## 设置 var var="实际值" echo "变量 var 的值: ${var:-默认值}"变量 var 的值: 默认值 变量 var 的值: 实际值${var#pattern}和${var%pattern}用于去除字符串的前缀或后缀filename="document.tar.gz" ## 删除最短匹配的后缀 ".gz" echo "${filename#document.}" ## 删除最短匹配的后缀 ".gz" echo "${filename%.gz}" ## 删除最长匹配的后缀 ".tar.gz" echo "${filename%%.tar.gz}" ## 使用表达式去除前缀 echo "${filename#d*nt.}" ## 使用表达式去除后缀 echo "${filename%.tar*}" ## 如果没有匹配到, 则返回原字符串 echo "${filename#goodbye_}"tar.gz document.tar document tar.gz document document.tar.gz${var:offset:length}用于获取字符串的子字符串.text="Hello World" ## 从位置6开始提取5个字符 echo "${text:6:5}" ## 从位置0开始提取5个字符 echo "${text:0:5}" ## 从位置6开始提取到末尾 echo "${text:6}"World Hello World
后台进程
Bash 脚本可以同时运行多个进程
#!/bin/bash ## 后台运行一个脚本 cat "shell.org" > /dev/null & ## 后台运行一个 curl 命令 curl http://example.com -o output.html & echo "两个进程已在后台运行"wait命令的作用, 它会等待所有后台进程完成## 后台运行任务1 sleep 3 & pid1=$! ## 后台运行任务2 sleep 5 & pid2=$! echo "等待两个后台进程完成..." wait $pid1 $pid2 echo "所有后台进程已完成. "后台进程有时会在关闭终端时退出, 并说明了如何使用
nohup或tmux/screen来保持后台进程运行.## 使用 nohup 运行 python 脚本, 并将输出重定向到 nohup.out nohup python long_running_script.py > nohup.out 2>&1 & echo "脚本已在后台运行, 即使关闭终端也不会终止. "jobs,fg,bg和disown管理后台进程disown和nohup的区别在于disown支持在进程 执行之后 放到后台执行.#!/bin/bash ## 启动一个后台进程 sleep 2 & ## 查看后台进程 jobs ## 将后台进程带到前台 fg %1 ## 当进程在前台运行时, 按下 Ctrl+Z 暂停进程 ## 使用 bg 将暂停的进程放入后台继续运行 bg %1 ## 使用 disown 将后台进程从当前Shell中分离 disown %1
子shell(subshell)
子 shell 是一个子进程, 并用一个例子说明了如何使用子 shell 来执行一段代码.
创建子 shell 的几种方法, 包括使用括号, 管道重定向, 以及进程替换.
## 括号会创建sub shell (cd /tmp; ls -l) ## 使用管道符 "|" 将命令连接起来, 会创建一个子 shell 来执行后面的命令. ls -l | grep "shell.org" ## 进程替换 wc -l <(ls)total 16 srwx------@ 1 a123 wheel 0 Dec 31 09:12 2f093fe9-a235-5d1c-9a62-3fae2cdf7eb1 srwx------@ 1 a123 wheel 0 Dec 31 09:12 7c3338c5-8eec-50ae-bc4a-1f6969419936 prw-rw-rw- 1 root wheel 0 Dec 24 15:19 9e923688bfa34288aa93e2070da546bf prw-rw-rw- 1 root wheel 0 Dec 24 15:18 A262AD1E-C7E0-42C3-8A20-E532F767EEEE_16308 srwxr-xr-x@ 1 a123 wheel 0 Dec 26 17:45 KoDF0sohej4R_bzJiALaHLuEGahtZOBlPKVzyXue1pw drwx------ 3 a123 wheel 96 Dec 24 15:17 com.apple.launchd.FMcex23J1V drwxr-xr-x@ 6 a123 wheel 192 Dec 31 09:12 com.logi.optionsplus.agent.logs drwxr-xr-x@ 2 a123 wheel 64 Dec 29 00:00 com.logi.optionsplus.logivoice.logs drwxr-xr-x@ 2 root wheel 64 Dec 29 00:00 com.logi.optionsplus.updater.logs d----w--w- 2 root wheel 64 Dec 29 21:37 devio_semaphore_logi_hpp_OptionsPlus_A7D4B139-F9F9-44A6-9F5D-7BC0A9B8B80F -rw-r--r-- 1 a123 wheel 5863 Dec 28 13:39 homebrew.mxcl.emacs-plus.stderr.log drwxr-xr-x 3 a123 wheel 96 Dec 29 00:00 image srwxrwxrwx@ 1 a123 wheel 0 Dec 24 15:18 logitech_kiros_agent-80c9ef0fb86369cd25f90af27ef53a9e srwxrwxrwx@ 1 a123 wheel 0 Dec 24 15:19 logitech_kiros_logivoice-80c9ef0fb86369cd25f90af27ef53a9e srwxrwxrwx@ 1 root wheel 0 Dec 24 15:16 logitech_kiros_updater drwxrwxrwx@ 10 root wheel 320 Dec 28 17:37 perfcount drwxr-xr-x 2 root wheel 64 Dec 29 12:15 powerlog -rw-r--r--@ 1 a123 staff 15458 Dec 30 10:42 emacs-as-shell.org -rw-r--r--@ 1 a123 staff 33124 Dec 31 09:39 shell.org 108 /dev/fd/10子shell和父shell完全独立
(cd /tmp; pwd) ## 在 /tmp 目录下执行 ls -l 命令, 不会影响父 shell 的当前目录/tmp
在子 shell 中设置的变量不会影响父 shell 中的变量值
var1="你好" (var1="hello") echo "$var1"你好在函数中创建子 shell 可能会导致一些意想不到的行为, 因为函数中的子 shell 不会影响父 shell 的变量值.
信号捕获(trap)
trap命令的作用, 它可以用来设置回调函数, 当发生某些事件时, 就会执行回调函数.trap命令的语法, 它需要两个参数, 第一个参数是回调函数, 第二个参数是事件类型.trap命令可以用来处理各种事件, 包括 Unix 信号, 脚本退出, 调试信息和函数返回值.trap命令可以用来处理脚本退出时的清理工作, 并用一个例子说明了如何使用trap命令来删除临时文件.
#!/bin/bash
## 创建临时文件
temp_file=$(mktemp)
## 设置 trap, 在脚本退出时删除临时文件
trap "rm -f $temp_file" EXIT
## 示例操作: 向临时文件写入内容
echo "This is a temporary file." > $temp_file
## 模拟脚本的其他操作
echo "Script is running..."
sleep 2
## 脚本正常退出时, trap 会自动删除临时文件
解释:
mktemp创建一个临时文件, 并将其路径存储在temp_file变量中.trap "rm -f $temp_file" EXIT设置了一个回调函数, 当脚本退出(无论是正常退出还是因错误退出)时, 自动删除临时文件.- 脚本执行完毕后, 临时文件会被自动清理, 无需手动删除.
错误处理
- shell 默认情况下会忽略错误, 即使命令执行失败, 脚本也会继续执行.
- shell 默认情况下不会检查未定义的变量, 即使变量不存在, 脚本也会继续执行.
- shell 默认情况下不会检查管道中的错误, 即使管道中的某个命令执行失败, 脚本也会继续执行.
为了更严格地处理错误, 可以使用以下选项:
set -e:使脚本在命令失败时立即退出.set -u:使脚本在遇到未定义的变量时立即退出.set -o pipefail: 使管道中的任何命令失败时, 整个管道失败.
以下是一个补充了错误处理的例子:
#!/bin/bash
## 启用严格错误处理
set -euo pipefail
## 示例命令
echo "Starting script..."
## 检查未定义的变量
echo "Undefined variable: $UNDEFINED_VAR"
## 执行可能失败的命令
ls /nonexistent_directory
## 管道中的错误处理
cat /nonexistent_file | grep "something"
echo "Script completed."
set -e: 如果ls /nonexistent_directory失败, 脚本会立即退出.set -u: 如果$UNDEFINED_VAR未定义, 脚本会立即退出.set -o pipefail: 如果cat /nonexistent_file失败, 整个管道会失败, 脚本会退出.
这样可以确保脚本在遇到错误时立即停止执行, 避免潜在的问题.
调试 (Debug)
set -x 命令
set -x 用于开启脚本的调试模式,
打印出脚本执行的每行代码以及所有变量的值.
实际执行的过程中, 会在stderr输出每一行代码本身.
#!/bin/bash
set -x ## 开启调试模式
echo "Start"
var="Hello"
echo $var
set +x ## 关闭调试模式
输出:
+ echo Start
Start
+ var=Hello
+ echo Hello
Hello
+ set +x
bash -x 命令
bash -x 与在脚本开头添加 set -x 的效果相同, 直接以调试模式运行脚本.
bash -x script.sh
trap read DEBUG 命令
trap read DEBUG 使脚本在执行每行代码之前暂停, 并等待用户输入.
#!/bin/bash
trap 'read -p "Press Enter to continue..."' DEBUG
echo "Line 1"
echo "Line 2"
执行时, 每行代码执行前都会暂停, 等待用户按 Enter 继续.
自定义 die 函数
die 函数可以在命令执行失败时打印错误信息并退出程序.
#!/bin/bash
die() {
echo "Error: $1" >&2
exit 1
}
## 示例用法
mkdir /tmp/test || die "Failed to create directory"
如果 mkdir 失败, die 函数会打印错误信息并退出脚本.