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
# 条件为真时执行的命令
fi
if-then-else
if [ -e /path/to/file ]; then
echo "文件存在。"
else
echo "文件不存在。"
fi
if-then-elif-else
if [ 条件1 ]; then
# 条件1为真时执行的命令
elif [ 条件2 ]; then
# 条件2为真时执行的命令
else
# 上述条件都不为真时执行的命令
fi
if 嵌套
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"
fi
test 命令 #
在 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"
done
for
循环可以与Shell的通配符(如*
和?
)一起使用,以遍历匹配特定模式的文件名。
for file in *.txt
do
echo "Processing $file"
done
Bash 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"
done
while 语句 #
- 基本示例
下面的脚本使用 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)