Linux系统学习笔记:BASH编程
使用 var=(element element ...) 创建数组变量。引用数组中的元素时使用 ${var[i]} , i 为下标,下标@ 复制原数组,下标 * 也复制原数组,但加双引号时它将原数组作为一个元素。给数组中单个元素赋值时可以用 var[i]=value 。 ${#var[i]} 返回元素的长度, ${#var[*]} 返回数组中元素的个数。
$ array=(Alex Harry Nancy)
$ arr1=("${array[@]}")
$ arr2=("${array[*]}")
$ declare -a
declare -a arr1='([0]="Alex" [1]="Harry" [2]="Nancy")'
declare -a arr2='([0]="Alex Harry Nancy")'
declare -a array='([0]="Alex" [1]="Harry" [2]="Nancy")'
$ array[1]=Jerry
$ echo ${array[*]}
Alex Jerry Nancy
$ echo ${array[@]}
Alex Jerry Nancy
$ echo ${#array[*]} ${#array[1]}
3 5
变量的作用域为当前脚本,可以用 export 声明变量,它将父进程的变量变为对子进程是可用的。函数中的变量默认不是局部的,作用域也为当前脚本,为了避免冲突,可以用 typeset 将它声明为局部变量。
${#var} 返回变量的长度。
${var:-default} 使用变量的值,如果值为空或未赋值,则使用给出的默认值。 ${var:=default} 和前者类似,但它同时会在值为空或未赋值时将默认值赋给变量。 ${var:?message} 可以在值为空或未赋值时显示错误信息,如果未给出 message ,则显示默认错误信息。
$ cd ${dir:?$(date +%T) error, dir not set.}
-bash: dir: 10:15:29 error, dir not set.
: 可以给其后的变量赋值但不去执行它,常常用 : ${var:=default} 来给变量设置默认值。
BASH中有一种字符串模式匹配,形式为 ${varOPpattern} , OP 为: # 去除最小匹配前缀, ## 去除最大匹配前缀, % 去除最小匹配后缀, %% 去除最大匹配后缀。
$ file=/home/yeolar/a.sh
$ echo ${file##/*/}
a.sh
$ echo ${file%/*}
/home/yeolar
((var=base#n)) 语法可以给变量以其他基数赋值。
$ ((n=8#0101)); echo $n
65
位置参数
位置参数保存命令和命令后的参数。可以用 set 改变位置参数的内容,但不能在脚本内改变命令名。 $# 保存参数的个数, $0 保存命令名, $1 - $n 保存命令后的参数, $@ 和 $* 保存全部参数,和数组变量类似,加双引号时 $* 作为一个参数,而 $@ 作为一组参数。 shift 左移参数。 set 初始化参数( set 没有参数时显示已设置的shell变量,还可以用它来设置shell特性)。
$ cat a.sh
echo "cmd: $0, args: $*, argnum: $#"
echo "first arg: $1"
echo "shift args..."
shift
echo "cmd: $0, args: $*, argnum: $#"
echo "first arg: $1"
set "$@"
echo "first arg: $1"
set "$*"
echo "first arg: $1"
set a b c
echo $*
$ bash a.sh x y z
cmd: a.sh, args: x y z, argnum: 3
first arg: x
shift args...
cmd: a.sh, args: y z, argnum: 2
first arg: y
first arg: y
first arg: y z
a b c
还有一些特殊参数: $$ 保存当前(shell)进程的PID, $! 保存最近转入后台运行的进程的PID, $? 保存上一个命令的返回状态码。
特殊参数和位置参数不能通过赋值语句改变。
表达式
可以用 let "expr" 或 ((expr)) 对算术表达式求值,多个表达式可以分别用空格和逗号分开。
$ x=1 y=1 z=0
$ let "x = x * 10 + y" z=z+1
$ echo $x $y $z
11 1 1
$ ((x = x * 10 + y, z = z + 1))
$ echo $x $y $z
111 1 2
条件表达式用 [[expr]] 求值。有个比较特别的运算符是 = ,在条件表达式中可以用它来判断相等。
运算符
BASH支持绝大部分C语言的运算符,有些运算符增加了一些特定语法结构中的含义,如管道。
控制流
if...then
if...then 的语法如下:
if test-command
then
command
[elif test-command
then
command
...]
[else
command]
fi
为了减少缩进,常常用 ; 将 then 写到上一行。
test-command 为测试命令,可以用 test 命令进行测试,但一般使用它的同义词 [] 。
# 检查参数个数
if [ $# -eq 0 ]; then # 等价于:if test $# -eq 0; then
echo "Usage: cmd arg" 1>&2
exit 1
fi
对于数值的测试,可以使用:
-eq 等于
-ne 不等于
-gt 大于
-ge 大于等于
-lt 小于
-le 小于等于
字符串的比较可以使用 = 和 != 。
下面是一些和文件相关的检查选项:
-e 检查文件是否存在
-d 检查文件是否存在且是目录
-f 检查文件是否存在且是普通文件
-s 检查文件是否存在且大于0字节
-r 检查文件是否存在且可读
-w 检查文件是否存在且可写
-x 检查文件是否存在且可执行
for
for 的语法如下:
for var[ in list]
do
command
done
seq 为可展开为列表的表达式。可以省略 in list ,这时列表为命令的参数,即 $@ 。
# whos脚本,打印用户名和全名 Usage: whos user ...
for user; do
gawk -F: '{print $1, $5}' /etc/passwd |
grep -i "$user"
done
while和until
while 和 until 类似,区别是 until 在 do 分支执行后测试,并且是测试结果为假时循环,测试结果为真时跳出循环。
语法为:
while test-command
do
command
done
until test-command
do
command
done
# 查找拼写错误的词
while read line; do
if ! grep "^$line$" "$1" > /dev/null; then
echo $line
fi
done
# 猜名字
rightname=yeolar
until [ "$name" = "$rightname" ]; do
echo -n "Guess: "
read name
done
echo "Good, you've got it."
break和continue
break 和 continue 可以用于在 for 、 while 和 until 语句中跳出循环和继续下一循环。
case
case 用于多路选择。语法为:
case test-command in
pattern)
command
;;
[pattern)
command
;;
...]
esac
pattern 可以使用 * 匹配任意字符串, ? 匹配单个字符, [...] 给出可匹配的字符, | 分离不同的选择。
# 命令选择
echo -e "/ncmds: A for date, B for who, C for pwd./n"
echo -n "Enter A, B or C: "
read c
case "$c" in
a|A)
date
;;
b|B)
who
;;
c|C)
pwd
;;
*)
echo "Invalid choice: $c"
;;
esac
select
select 显示一个菜单,根据用户的选择给变量赋予相应的值,然后执行命令。退出 select 可以使用 break,或者 exit 退出整个脚本。
select var[ in list]
do
command
done
和 for 一样,省略 in list 会用命令参数代替。 PS3 设置 select 的提示符,一般会设置为需要的提示语句。
# 命令菜单
PS3="Choose what you want to do: "
select c in date who pwd exit; do
if [ "$c" == "" ]; then
echo -e "Invalid choice./n"
continue
elif [ $c = exit ]; then
echo "quit"
break
fi
echo "You choose: $c"
if [ $c = date ]; then
date
elif [ $c = who ]; then
who
elif [ $c = pwd ]; then
pwd
fi
echo ""
done
文件描述符
在BASH中,使用 exec 命令执行文件描述符相关的操作:
exec n> outfile 打开outfile作为输出文件,分配文件描述符n
exec n< infile 打开infile作为输入文件,分配文件描述符n
exec n<&m 打开或重定向文件描述符n,作为文件描述符m的副本
exec n<&- 关闭文件描述符n
内置命令
前面已经提到了很多BASH内置命令,这里做个总结和补充。
: 返回0或 true (置空内部命令)
. 把shell脚本当作当前进程的一部分执行
bg 挂起任务
break 跳出循环
cd 改变工作目录
continue 继续下一循环
echo 显示
eval 扫描并计算命令行
exec 执行shell脚本或程序并替换掉当前进程
exit 从当前shell退出
export 将变量放到被调用的环境中
fg 将后台任务移到前台
getopts 分析读取shell脚本的参数
jobs 列出后台任务
kill 向进程或作业发送信号
pwd 显示当前工作目录
read 从标准输入中读一行
readonly 声明变量为只读
set 列出全部变量,设置shell特性,设置命令行参数
shift 左移命令行参数
test 比较
times 显示当前shell及其子进程的运行时间
trap 捕获信号
type 显示参数给出的命令的相关信息
umask 返回文件创建的掩码,设置掩码
unset 删除变量或函数
wait 等待后台进程结束
read 支持一些选项:
-a array 输入的单词作为 array 的元素
-d delim 使用 delim 代替换行来终止输入
-e 使用Readline库来获取输入(输入来自键盘时)
-n num 读取 num 个字符后返回
-p prompt 以 prompt 作为输入的提示信息
-s 在终端上不打印字符
-un 从文件描述符为n的文件输入
有个特殊的变量 REPLY ,保存读取的输入。
getopts 的语法为 getopts optstr var[ arg ...] , optstr 给出合法的字母选项, var 保存每次接收的选项的值, arg 为将处理的参数,省略将默认处理命令行参数。 optstr 以 : 开始时由脚本负责产生错误信息,否则由 getopts 自己产生。 optstr 用字母后的 : 表示选项接受值, OPTARG 保存和选项相关的值。OPTIND 保存选项的索引,起始值为1。
exec 既可执行脚本又可执行程序,它不创建新进程,把当前(shell)进程替换为要执行的内容。
trap 的用法是 trap ['command'] [signal] ,它捕获信号,执行 command ,如果没有 command ,那么重置trap 。
trap '' 2 15 # 捕获并忽略中断
trap 'echo Interrupted.; exit 1' INT # 捕获中断,打印信息并退出
kill 给进程发送信号,如终止进程。后面会详细讲有关进程的内容。
shell脚本
根据本篇介绍的内容和前两篇的一些知识就可以编写shell脚本了。
像Python脚本一样可以用 #! 为脚本定义解释器。
#!/bin/bash
# 或者:
#!/usr/bin/env bash