Bash 版本#
Dash: Debian Almquist shell
现在 zsh 比较常用,配合 oh-my-zsh 使用更香,zsh 相比 bash 还支持更多脚本功能。
Shell 的父子关系#
通过 ps -f 可以查看 PID 和 PPID 号来判断父子关系,创建了子 Shell 就意味着创建了子进程。
PID进程号PPID父进程号
分号#
可以在单行中指定要依次运行的一系列命令。这可以通过命令列表来实现,只需将命令之间以分号 ; 分隔即可:
pwd; ls test*; cd /etc; pwd; ls创建子 shell#
如果要成为进程列表,命令列表必须将命令放入圆括号内。
(pwd; ls test*; cd /etc; pwd; ls)圆括号的加入使命令列表摇身变成了进程列表,生成了一个子 shell 来执行这些命令。
➜ test ls
gitdemo makefile table.csv tarfile test.rar unrar.py
➜ test echo $ZSH_SUBSHELL
0
➜ test (ls; echo $ZSH_SUBSHELL)
gitdemo makefile table.csv tarfile test.rar unrar.py
1括号还可以继续嵌套。
(pwd; ls test*; cd /etc; (pwd; ls))后台执行#
子 Shell 在处理多进程是比较有用,尤其需要在后台运行时:
(sleep 2;pwd; ls test*; cd /etc; pwd; ls; echo $ZSH_SUBSHELL)&子 Shell 在 sleep 命令睡眠 2 秒钟后执行命令列表,同时通过 & 表示在后台运行。
协程#
命令 coproc 可以帮助子 Shell 创建并运行协程。
coproc sleep 10甚至还可以获取后续的协程返回值。
#!/bin/bash
# 使用 coproc 启动一个协程,执行一个简单的命令
coproc MYCOPROC { echo "Hello, World!"; }
# 从协程的输出中读取数据
read -r output <&"${MYCOPROC[0]}"
# 打印输出
echo "The output from coproc is: $output"
# 等待协程结束,并获取其返回值
wait "${MYCOPROC_PID}"
echo "The return value of coproc is: $?"这里的关键步骤解释如下:
coproc MYCOPROC { command; }:使用coproc启动一个名为MYCOPROC的协程,执行大括号内的命令。MYCOPROC变量会被赋值为一个数组,其中MYCOPROC[0]是协程的标准输出(STDOUT)的文件描述符,MYCOPROC[1]是标准输入(STDIN)的文件描述符。read -r output <&"${MYCOPROC[0]}":使用read命令从协程的标准输出中读取数据。<&"${MYCOPROC[0]}"表示将文件描述符MYCOPROC[0]作为输入。wait "${MYCOPROC_PID}":coproc启动的协程的 PID 存储在特殊变量${MYCOPROC_PID}中。使用wait命令等待协程结束,并通过$?获取其返回值。
外部命令和内建命令#
使用 type 命令可以检查一个命令是否是外部命令或内建命令。外部命令程序通常位于 /bin、/usr/bin、/sbin 或 /usr/sbin 目录中。
➜ [test] type cd
cd is a shell builtin
➜ [test] type vim
vim is /usr/bin/vim外部命令#
外部命令是独立的程序文件,存储在系统的文件系统中。当你执行一个外部命令时,shell 会在系统的 PATH 环境变量定义的目录中查找这个命令的可执行文件。比如 ls(列出目录内容)、grep(文本搜索工具)、awk(文本处理工具)、python(Python 解释器)等。
内建命令#
内建命令是 shell 的一部分,它们是 shell 程序的一部分,而不是独立的程序。这些命令直接由正在运行的 shell 进程执行,而不需要调用额外的程序文件。比如 cd(改变目录)、echo(打印文本)、history(显示命令历史)、export(设置或显示环境变量)等。
区别#
可以说,外部命令总是需要创建新的进程来执行,而内建命令可以在当前的 Shell 直接快速执行,这是最明显的区别。
环境变量#
局部环境变量#
set 命令既会显示全局和局部环境变量、用户自定义变量以及局部 shell 函数,还会按照字母顺序对结果进行排序。
$ my_var=Hello
echo $my_var
Hello
$ my_var="Hello World"
echo $my_var
Hello World注意 my_var = "hello world 会报错 zsh: command not found: my_var,所以在 Shell 中的 = 两侧带空格是不对的。另外,按照规范,用户自定义的局部变量用小写字母,系统环境变量用的是大写字母。
全局变量#
可以在 Shell 中使用 env 或 printenv 命令来查看全局变量。创建全局环境变量的方法是先创建局部变量,然后再将其导出到全局环境中。
$ my_var=Hello
echo $my_var
export my_var通过 export 导出后,会生成一个子 Shell,该变量只会在子 Shell 中存在,并不会反映到父 Shell 环境中。
PATH 环境变量#
持久化环境变量#
/etc/profile 是 Shell 默认的主启动文件,只要登录 Linux 系统,bash 就会执行 /etc/profile 启动文件中的命令。但这些是用于系统功能,类似的如果要用于用户自己,可以尝试下面的文件:
$HOME/.bash_profile$HOME/.bashrc$HOME/.zshrc
创建 Shell 脚本#
创建 Shell 脚本时,必须在文件的第一行指定要用的 Shell,格式如下:
#!/bin/bash命令替换#
命令替换允许将 Shell 命令的输出赋给变量,有以下两种方式。
反引号#
testing=`date`$() 格式#
testing=$(date)重定向输入和输出#
输出重定向#
# 输出内容到文件 file1
date > file1
# 追加输出到 file2
date >> file2输入重定向#
# 使用 wc 命令统计文件 file1 中的文本。
wc < file1
# 内联输入重定向
wc << EOF在命令行使用内联输入重定向时,Shell 会进入次提示符状态并持续显示,直到输入了作为文本标记的那个字符串。wc 命令会统计内联输入重定向提供的数据包含的行数、单词数和字节数。
➜ ~ wc << EOF
heredoc> hello world
heredoc> foo bar
heredoc> 1234
heredoc> EOF
3 5 25
➜ ~管道#
管道帮助你将前一个命令的输出作为下一个命令的输入。虽然重定向也可以实现,只是比较笨拙。
管道的工作原理基于 Unix 哲学中的一个核心概念:“一切皆文件”。在 Unix 和类 Unix 系统中,数据可以通过文件描述符(File Descriptor)来访问。标准输入(stdin)、标准输出(stdout)、和标准错误输出(stderr)分别被分配了文件描述符 0、1 和 2。
当使用管道时,Shell 会创建一个匿名管道(一个内存中的缓冲区),并将 command1 的标准输出连接到这个管道,同时将 command2 的标准输入连接到同一个管道。这样,command1 的输出就直接成为了 command2 的输入。
数学运算#
命令 expr#
很多数学符号比如 * 在 Shell 另有含义,为了避免表达式误会有两种方式:
# 使用反斜杠
expr $var1 \* $var2
# 使用方括号
var3=$[$var1 * $var2]浮点数解决方案 bc#
Shell 中的数学计算只支持整数运算,如果要支持浮点数运算还需要借助 Bash 的 bc 计算器。
退出状态码 $?#
Shell 中运行的每个命令都使用退出状态码来告诉shell自己已经运行完毕。退出状态码是一个 0~255 的整数值,在命令结束运行时由其传给 Shell。
你可以获取这个值并在脚本中使用。Linux 提供了专门的变量 $? 来保存最后一个已执行命令的退出状态码。该变量的值会随时变成 Shell 所执行的最后一个命令的退出状态码。
| 状态码 | 含义 |
|---|---|
| 0 | 成功。程序执行成功完成。 |
| 1 | 通用错误。通常表示命令行语法错误或命令无法成功执行。 |
| 2 | 不适用的 shell 内置命令。 |
| 126 | 命令不可执行。通常是因为没有执行权限或不是有效的执行格式。 |
| 127 | 命令未找到。尝试执行的命令不存在。 |
| 128 | 无效的退出参数。使用了超出 0-255 范围的退出状态。 |
| 128+n | 通过信号 n 终止。这表示程序因为接收到信号 n 而异常终止。例如,128+9 表示因为接收到 SIGKILL (信号 9) 而终止。 |
| 130 | 通过 Ctrl+C 终止。等同于 128+2,因为 Ctrl+C 发送 SIGINT (信号 2)。 |
| 255* | 退出状态超出范围。在某些情况下,如果状态码超过 255,有些 shell 可能会报告为 255。 |
退出状态码的数字含义只是规范,不同的程序和脚本可以自定义使用 1 到 255 之间的任何值来表示特定的错误条件或状态。另外,exist 命令可以在脚本结束时指定一个退出状态码。
if 判断#
if 语法的例子#
if-then
if [ 条件 ]; then
# 条件为真时执行的命令
fiif-then-else
if [ -e /path/to/file ]; then
echo "文件存在。"
else
echo "文件不存在。"
fiif-then-elif-else
if [ 条件1 ]; then
# 条件1为真时执行的命令
elif [ 条件2 ]; then
# 条件2为真时执行的命令
else
# 上述条件都不为真时执行的命令
fiif 嵌套
num=10
if [ $num -eq 5 ]; then
echo "数字等于 5"
elif [ $num -lt 10 ]; then
echo "数字小于 10"
elif [ $num -le 15 ]; then
# 在这个 elif 块中嵌套另一个 if
if (( num % 2 == 0 )); then
echo "数字小于或等于 15 且为偶数"
else
echo "数字小于或等于 15 但不是偶数"
fi
else
echo "数字大于 15"
fitest 命令#
在 Shell 脚本中,test 命令用于检查某个条件是否成立,并根据检查结果返回退出状态码。退出状态码为 0 表示条件成立(真),非 0 表示条件不成立(假)。test 命令可以用于数值比较、字符串比较、文件属性检查等多种场景。[ ] 是 test 命令的另一种写法,更常见于 if 语句中。
if test $num1 -eq $num2; then
echo "两个数字相等。"
fi数值比较#
-eq:等于-ne:不等于-gt:大于-ge:大于等于-lt:小于-le:小于等于
if [ $num1 -eq $num2 ]; then
echo "两个数字相等。"
fi字符串比较#
相等性比较:
=:等于!=:不等于-z:字符串长度为零-n:字符串长度非零
str1="hello"
str2="world"
if [ "$str1" = "$str2" ]; then
echo "两个字符串相等。"
else
echo "两个字符串不相等。"
fi如果对字符串用 > 或 < 进行比较,需要加 \ 反斜线进行转义,避免被当成重定向符号使用。注意,比较字符串的时候是按照字母表顺序进行大小比较的,也就是按照 Unicode 编码的值来比较。
if [ $string1 \> $string2 ]; then
echo "$string1 is greater than $string2."
fi使用 -n 和 -z 用来测试字符串变量是否为空。其中 -z 操作符用于检查字符串长度是否为零,如果为空则返回真(true)。
if [ -z "$string" ]; then
echo "String is empty"
fi-n 操作符用于检查字符串长度是否非零,如果不为空则返回真(true)。
if [ -n "$string" ]; then
echo "String is not empty"
fi文件判断#
-e:文件或目录是否存在-f:文件存在且为常规文件-d:文件存在且为目录-r:文件存在且可读-w:文件存在且可写-x:文件存在且可执行
file="/path/to/file"
if [ -e "$file" ]; then
echo "文件存在。"
else
echo "文件不存在。"
fi检查是否可写
file="/path/to/file"
if [ -w "$file" ]; then
echo "文件可写。"
else
echo "文件不可写。"
fi复合条件测试#
即 && 与 || 用来表示 and 和 or 。
if [[ condition1 && condition2 ]]; then
echo "Both conditions are true."
elif [[ condition1 || condition2 ]]; then
echo "At least one of the conditions is true."
fi高级特性#
使用单括号#
上面提到过,当使用括号将一行命令包起来后,实际上是创建了一个子 Shell 去执行,然后再返回。在 if 语句中,如果子 Shell 的退出状态码为 0,即为真,执行 then 的部分。
if (command1; command2); then
echo "Commands succeeded."
else
echo "Commands failed."
fi使用双括号#
双括号用来执行高级的数学表达式。
if ((a > b)); then
echo "a is greater than b"
fi很多时候,还是能看到相对应的旧语法,不再推荐使用下面的例子。
var1=5
var2=3
result=$[var1 + var2] # 推荐使用:result=$((var1 + var2))
echo $result使用双方括号#
双方括号用来针对高级的字符串比较,是对传统单方括号的增强,这些增强功能和灵活性使得它在 Bash 脚本中被广泛使用,特别是在需要进行模式匹配或正则表达式匹配时。主要包括:
字符串比较:
使用
==和!=进行字符串比较。在双方括号内,==右侧可以使用通配符。if [[ $a == $b ]]; then echo "a equals b" fi if [[ $a != $b ]]; then echo "a is not equal to b" fi模式匹配:
在双方括号中,可以使用
==和!=进行模式匹配。如果右侧是一个模式(包含通配符如*或?),则左侧的字符串会被检查是否匹配该模式。if [[ $filename == *.txt ]]; then echo "$filename ends with '.txt'" fi正则表达式匹配:
使用
=~进行正则表达式匹配。if [[ $string =~ ^[0-9]+$ ]]; then echo "$string is a number." fi逻辑运算:
双方括号支持使用
&&和||进行逻辑与和逻辑或操作,而不需要像单方括号那样分隔多个测试条件。if [[ $a == 100 && $b == 200 ]]; then echo "Both conditions are true." fi安全性:
双方括号在处理变量时更加安全。即使变量未引用或为空,也不会导致语法错误或意外的行为。
if [[ $a == $b ]]; then echo "a equals b" fi
注意事项:
- 在双方括号内,
==和=是等价的,都可以用于字符串比较。 - 使用正则表达式时,正则表达式部分不应被引号包围,否则会被视为普通字符串。
- 双方括号是 Bash 和一些其他现代 Shell 的特性,不是 POSIX 标准的一部分,因此在非 Bash 环境下可能不可用。
注意事项 ⚠️#
语法#
- 确保在
[和]之间留有空格,这是 Shell 语法的要求,因为[实际上是一个命令,而]是它的参数! - 使用
(( ))可以执行算术比较和操作,而使用[ ]或[[ ]]可以执行字符串和文件比较。 - 在使用变量时,最好用双引号将变量名括起来,以避免由于变量值中可能包含的空格或特殊字符而导致的错误。
if “零” 会怎样?#
在 Shell 脚本中,条件判断通常依赖于命令的退出状态码。退出状态码为 0 表示命令执行成功(即“真”),非 0 表示命令执行失败或有错误(即“假”)。这与许多编程语言中的布尔逻辑不同,其中 0 通常被解释为“假”,而非零值被解释为“真”。
因此,如果你直接在 if 语句中使用数字作为条件进行判断,应该这样理解:
- 如果使用数字 0,Shell 会将其解释为成功的退出状态码,即“真”。
- 如果使用非 0 的数字,Shell 会将其解释为失败的退出状态码,即“假”。
但是,直接在 if 语句中使用数字并不是一个常见的做法。通常,我们会使用命令执行的结果或者使用 test 命令(或 [ 和 ])来进行条件判断。
下面是一个特殊的例子,直接使用数字作为条件。在这个例子中,exit 0 会导致子 shell 退出并返回状态码 0,表示成功,因此会执行 then 部分的代码,输出“这是真”。
if (exit 0); then
echo "这是真。"
else
echo "这是假。"
fi如果你想通过 test 命令或 [ 和 ] 来比较数字,应该使用 -eq、-ne 等操作符,而不是直接使用数字。
num=0
if [ $num -eq 0 ]; then
echo "数字等于 0"
else
echo "数字不等于 0"
fi在这个例子中,脚本会输出“数字等于 0”,因为我们使用了 -eq 操作符来比较 $num 和 0。
case 语句#
在 Shell 脚本中,case 语句提供了一种根据模式匹配来执行不同代码块的方法。这在处理多个条件分支时非常有用,可以使代码更加清晰和易于管理。
#!/bin/bash
echo "Enter a number (1-3):"
read number
case $number in
1)
echo "You entered one."
;;
2)
echo "You entered two."
;;
3)
echo "You entered three."
;;
*)
echo "Invalid number. Please enter 1, 2, or 3."
;;
esac循环控制#
for 语句#
Shell的 for 循环提供了灵活的方式来重复执行命令,无论是遍历一系列的值,处理文件和目录,还是处理命令的输出。
for variable in list
do
command1
command2
...
done这里,list可以是一系列值(如数字或字符串),variable是循环中的变量,它会依次取list中的每个值。每次循环时,variable会被设置为list中的当前值,然后执行do和done之间的命令。
for i in 1 2 3 4 5
do
echo "Number $i"
donefor循环可以与Shell的通配符(如*和?)一起使用,以遍历匹配特定模式的文件名。
for file in *.txt
do
echo "Processing $file"
doneBash Shell支持C语言风格的for循环语法,这为编写复杂的循环提供了更多的灵活性。
for (( i=1; i<=5; i++ ))
do
echo "Number $i"
done在Bash中,你还可以使用花括号{}生成序列,并在for循环中遍历这些序列。
for i in {1..5}
do
echo "Number $i"
done或者指定步长:
for i in {0..10..2} # 从0到10,步长为2
do
echo "Number $i"
done还可以遍历一个命令的输出。例如,遍历ls命令列出的文件:
for file in $(ls)
do
echo "Found file $file"
donewhile 语句#
- 基本示例
下面的脚本使用 while 循环打印数字1到5:
#!/bin/bash
counter=1
while [ $counter -le 5 ]
do
echo "Counter: $counter"
((counter++))
done在这个例子中,只要 counter 的值小于或等于5,循环就会一直执行。每次循环,脚本都会打印当前的 counter 值,然后 counter 的值增加1。
- 读取文件
while 循环与 read 命令结合使用,可以逐行读取文件内容:
#!/bin/bash
file="sample.txt"
while IFS= read -r line
do
echo "$line"
done < "$file"这个脚本逐行读取 sample.txt 文件中的内容,并打印每一行。IFS=(输入字段分隔符设置为空)和 -r 选项确保每行的内容被准确无误地读取,包括空格和反斜线。
until 语句#
- 基本示例
下面的脚本使用until循环打印数字1到5:
#!/bin/bash
counter=1
until [ $counter -gt 5 ]
do
echo "Counter: $counter"
((counter++))
done在这个例子中,循环会一直执行,直到 counter 的值大于5。每次循环,脚本都会打印当前的 counter 值,然后 counter 的值增加1。
- 读取文件
你也可以使用 until 循环来读取文件中的行。以下是一个示例,它使用 until 循环和 read 命令逐行读取文件内容:
#!/bin/bash
file="sample.txt"
# 打开文件用于读取
exec 3< "$file"
until [ $done ]
do
# 尝试从文件描述符3中读取一行
read line <&3 || done=1
[ $done ] || echo "$line"
done
# 关闭文件描述符3
exec 3<&-这个脚本使用文件描述符 3 来读取文件 sample.txt 中的每一行。read 命令尝试从文件中读取一行,如果读取失败(比如到达文件末尾),read 命令的退出状态不为 0,done 变量被设置为 1,这导致 until 循环的条件为真,循环结束。
中断控制#
其他语言常见的 break 和 continue 关键词在 Shell 中同样适用。
重定向循环的输出#
在 Shell 脚本中,处理循环的输出是一项常见的任务,可以通过多种方式实现,包括但不限于重定向输出到文件、通过管道传递给其他命令处理,或者将输出赋值给变量。下面是一些处理循环输出的常见方法。
- 重定向输出到文件
你可以将循环的输出重定向到一个文件中,无论是覆盖文件还是追加到文件末尾。
for i in {1..5}
do
echo "Line $i"
done > output.txt这个例子会将循环的输出重定向到 output.txt 文件中,每次执行脚本时都会覆盖原有内容。
for i in {1..5}
do
echo "Line $i"
done >> output.txt这个例子会将循环的输出追加到 output.txt 文件末尾,而不是覆盖原有内容。
- 通过管道传递给其他命令
你可以通过管道将循环的输出传递给其他命令进行进一步处理。
for i in {1..5}
do
echo "Line $i"
done | sort -r这个例子会将循环的输出传递给 sort 命令,并使用 -r 选项进行逆序排序。
- 将输出赋值给变量
有时你可能想要将循环的输出赋值给一个变量,以便之后使用。
output=""
for i in {1..5}
do
output="$output Line $i\n"
done
echo -e $output这个例子会将每次循环的输出累加到 output 变量中。使用 echo -e 可以正确处理换行符 \n。
- 使用数组
对于复杂的情况,你可能需要将循环的输出存储到数组中。
output=()
for i in {1..5}
do
output+=("Line $i")
done
# 打印数组内容
printf "%s\n" "${output[@]}"这个例子会将每次循环的输出追加到 output 数组中。然后,使用 printf 和数组展开 "${output[@]}" 来打印数组的所有元素。
用户输入#
脚本参数 $1、$2…#
Shell 脚本还可以通过命令行参数接收用户输入。这些参数在脚本内部可以通过$1、$2等特殊变量访问。
#!/bin/bash
echo "你好,$1。"运行此脚本时,你可以将用户的名字作为命令行参数传递给脚本:
bash script.sh 张三如果脚本需要的命令行参数不止 9 个,则仍可以继续加入更多的参数,但是需要稍微修改一下位置变量名。在第 9 个位置变量之后,必须在变量名两侧加上花括号,比如 ${10}。
脚本名 $0#
前面提到的参数是从 $1 开始的,脚本名当然就是 $0 了。但通常在代码中需要使用 basename $0 来使用,basename 命令可以返回不包含路径的脚本名。
#!/bin/bash
name=$(basename $0)
echo "file name is ${name}"特殊变量 $#、$@ 和 $*#
特殊变量 $# 都含有脚本运行时携带的命令行参数的个数。那么最后一个参数的应该用 ${$#} 表示,但是花括号内不能使用 $,必须换成 !,即 ${!#} 来表示 Shell 脚本的最后一个参数。
特殊变量 $@ 和 $* 用来引用所有的位置参数。其他 $* 会将所有参数视为单个参数,而 $@ 变量会单独处理每一个参数(数组)。
获取用户输入 read 命令#
在这个例子中,read 命令同时读取用户输入的名字和年龄,并将它们分别赋值给 name 和 age 变量。
#!/bin/bash
echo "请输入你的名字和年龄:"
read name age
echo "你好, $name, 你$age 岁了."-p 选项允许直接指定提示符。
#!/bin/bash
read -p "请输入你的名字和年龄:" name age
echo "你好, $name, 你$age 岁了."还有 -t 选项会指定 read 命令等待输入的秒数。如果计时器超时,则 read 命令会返回非 0 退出状态码。
其他命令#
shift 命令,可以帮助你跳过不需要的参数,尤其在分离参数和选项的时候非常管用。
getopt 和 getopts 命令。
输入和输出#
标准文件描述符#
在Unix和类Unix操作系统中,文件描述符是一个非负整数,用于表示一个打开的文件、管道或网络连接等。在Shell脚本和程序设计中,文件描述符0、1、2有特殊的含义,它们分别代表标准输入、标准输出和标准错误输出。
文件描述符0:标准输入(stdin)#
- 文件描述符编号: 0
- 用途: 用于读取输入。这可以是来自键盘的输入,或者是通过管道或重定向从其他程序或文件中读取的数据。
- 示例: 在命令行中,
<符号用于将文件重定向到程序的标准输入。
文件描述符1:标准输出(stdout)#
- 文件描述符编号: 1
- 用途: 用于输出数据。默认情况下,标准输出会被发送到终端(即屏幕),但也可以通过管道或重定向输出到其他程序或文件。
- 示例: 在命令行中,
>符号用于将程序的标准输出重定向到文件。
文件描述符2:标准错误输出(stderr)#
- 文件描述符编号: 2
- 用途: 用于输出错误信息或诊断信息。默认情况下,标准错误输出也会被发送到终端,但它可以被独立于标准输出重定向到其他地方,这有助于将错误信息与正常输出分开处理。
- 示例: 在命令行中,
2>符号用于将程序的标准错误输出重定向到文件。
重定向示例#
- 将标准输入重定向:
command < inputfile将inputfile作为command的标准输入。 - 将标准输出重定向:
command > outputfile将command的标准输出重定向到outputfile。 - 将标准错误输出重定向:
command 2> errorfile将command的标准错误输出重定向到errorfile。 - 同时重定向标准输出和标准错误输出到同一个文件:
command > outputfile 2>&1或command &> outputfile。
永久重定向 exec 命令#
exec 还可以用来重定向当前shell环境的标准输入、输出和错误输出。例如:
- 重定向标准输出到文件:
exec > outputfile之后,所有的标准输出都会被重定向到 outputfile 文件中。
- 重定向标准输入从文件读取:
exec < inputfile之后,所有的标准输入都会从 inputfile 文件中读取。
- 重定向标准错误输出到文件:
exec 2> errorfile之后,所有的标准错误输出都会被重定向到 errorfile 文件中。
- 同时重定向标准输出和标准错误输出到同一个文件:
exec > outputfile 2>&1
# 或
exec &> outputfile系统控制#
处理信号#
使用 trap 命令指定 Shell 脚本需要侦测并拦截的 Linux 信号。
trap commands signals常用的信号对照表有:
| 信号编号 | 信号名称 | 描述 |
|---|---|---|
| 1 | SIGHUP | 挂起信号。当控制终端关闭时,该信号被发送给前台进程组 |
| 2 | SIGINT | 中断信号。当用户按下中断键(通常是Ctrl+C)时,该信号被发送给前台进程组 |
| 3 | SIGQUIT | 退出信号。当用户按下退出键(通常是Ctrl+\)时,该信号被发送给前台进程组 |
| 9 | SIGKILL | 杀死信号。用于立即结束程序的执行 |
| 11 | SIGSEGV | 段错误信号。当程序访问非法内存时产生 |
| 13 | SIGPIPE | 管道破裂信号。当进程写入没有读端的管道时产生 |
| 14 | SIGALRM | 报警信号。由alarm函数产生,用于实现定时提醒 |
| 15 | SIGTERM | 终止信号。用于请求程序正常退出 |
| 17 | SIGCHLD | 子进程状态改变信号。当子进程停止或退出时,该信号被发送给父进程 |
| 18 | SIGCONT | 继续执行信号。用于让停止的进程继续执行 |
| 19 | SIGSTOP | 停止执行信号。用于停止进程的执行,该信号不能被捕获或忽略 |
| 20 | SIGTSTP | 终端停止信号。当用户按下停止键(通常是Ctrl+Z)时,该信号被发送给前台进程组 |
| 28 | SIGWINCH | 窗口大小改变信号。当终端的大小发生变化时,该信号被发送给前台进程组 |
后台运行#
在运行的脚本名后面加上 & 会将脚本与当前 Shell 分离开来,并将脚本作为一个独立的后台进程运行。但是此时后台进程仍然可以将标准输出打印到屏幕上,此时可以在命令最开始加上 nohup,nohup 命令能阻断发给特定进程的 SIGHUP 信号,当退出终端会话时,可以避免进程退出。
调整优先级#
可以使用 nice 命令指定脚本优先级,取值为 -20 到 +19 (从最高到最低)。
nice -n 10 ./run.sh > run.out &优先级取值默认为 0,另外只有 root 用户可以使用这个命令。
指定时间运行#
参考 at 命令。
定时运行#
参考 cron 时间表以及 crontab 命令。
脚本函数#
函数定义#
Shell 中定义函数有两种。
方式一
greet () {
echo "Hello, $1!"
}
greet "World" # 输出: Hello, World!方式二,使用关键词 function。
function function_name {
# 命令序列
}函数的参数#
当然是在函数内部使用特殊变量 $1、$2 这种了。
函数的返回值#
设置函数的返回值还是有两种,一种是在函数执行结束后直接使用退出状态码 $? 来确定。另一种是在函数结尾处使用 return 关键词来实现,但是需要注意:
- 函数执行一结束就应立刻读取返回值
- 退出状态码必须介于 0 ~ 255
函数调用#
Shell 会将函数当做小型脚本来对待,这意味着你可以想普通脚本那样向函数传递参数。
compute() {
echo $(($1 + $2))
}
result=$(compute 5 3)
echo "The result is: $result"函数中的变量#
Shell 脚本中的变量可分为全局变量和局部变量。默认情况下在脚本中定义的任何变量都是全局变量,包括在函数内部,所以和现在的大部分编程语言是有区别的。如何你希望使用局部变量,需要使用 local 关键字。
my_function() {
local local_var="I am local"
echo $local_var # 输出: I am local
}
my_function
echo $local_var # 不输出任何内容,因为local_var是局部变量库函数#
source 命令,也叫点号操作符。
在命令行中使用函数#
可在 .bashrc 等初始化文件中 source 库函数文件。
图形化#
核心是 case 和 select 命令。
#!/bin/bash
echo "What is your favorite fruit?"
select fruit in Apple Banana Orange Quit
do
case $fruit in
Apple)
echo "Apples are red."
;;
Banana)
echo "Bananas are yellow."
;;
Orange)
echo "Oranges are orange."
;;
Quit)
break
;;
*)
echo "Invalid option. Please try again."
;;
esac
done数组#
Shell 提供了数组功能。
实战#
备份#
删除账户#
监控程序#
参考#
Linux 命令行与 Shell 脚本编程大全 (9.5)