脚本参数

特殊变量引用

  • $0 当前 shell 脚本的名字
  • $NUM 位置参数,通过位置编号引用命令行上第 NUM 个参数
  • $# 参数个数
  • $* 所有的位置参数,用 $IFS 分割,作为一个单一字符串展开/替换(token 数为 1)
  • $@ 所有的位置参数,每个参数作为一个字符串分别展开/替换(token 数为位置参数个数)

位置参数可以通过 set 命令和 shift 命令进行控制

set 控制

set 参数列表

将脚本原有位置参数替换为新的位置参数

shift 控制

shift

将脚本原有位置参数整体左移,删除原有的 $1 参数

数组

数组支持是 POSIX 标准一部分,但是不同的 shell 最数组的支持和拓展不一致

如 zsh 是从 1 开始,bash 从 0 开始

array=(a b c)
array=()
array[0]=a
array[1]=b
array[100]=c

访问元素

下标从 0 开始,-1 表示最后一个元素

echo ${array[0]}
array[1]=x

获取数组所有元素

echo ${array[*]}
echo ${array[@]}

获取数组长度

echo ${#array[*]}
echo ${#array[@]}

删除元素或者整个数组

unset array[1]
unset array

*@ 的区别,和前面类似。* 用数组元素用 $IFS 分割,作为一个字符串展开/替换。@ 是将每个元素视为独立个体,独立展开(加双引号:每个元素作为独立带引号的字符串展开

最佳实践:仅使用 "${array[@]}""$@",在知道作用的前提下使用其他的

  • 没有双引号的时候会被 IFS 切开来
  • @ 本来就是分成多个 token 的,* 本来就是一个 token

分支

if 分支

if <condition>
	then <command>
[elif <condition>
	then <command>]
...
[else
	<command>]
fi

回车的地方可以用分号替代

这个 if 语句的返回值是最后一条命令的返回值

最佳实践:如果是数字相关的测试,利用 $(()) 得到真值,再用 = 比较字符串 1

数字以 0 开头会被认成 8 进制,需要注意

case 分支

case WORD in
[PATTERN [|PATTERN] ...)
	COMMANDS 
	;;]
....
esac

WORD 以字符串形式与给出的 PATTERN 进行匹配

;; 类似于 break

*) 为 defalt

PATTERN 支持路径通配符,有或 | (优先级最低)

返回值是最后一条命令的返回值

循环

for

for ITERM [in ITERM-LIST]
do
	COMMAND
done

in ITERM-LIST 如果未给出,则是 $@

ITERM-LIST 的分隔符为 $IFS 默认为空格、tab、回车

常见的形式有;

1 2 3 4
{1..9..2}
"${array[@]}"

while

while condition
do
	COMMAND
done

until

until condition
do
	COMMANDS
done

在判断条件上和 while 相反

break

break [num]

break 的循环层数

continue

continue [num]

continue 循环层数

函数

函数不能被 export,可以给出 return 语句,返回一个 0 到 255 之间的整数,也可以没有返回值,最后一条语句

# 方式 1(推荐)
my_func() {
    echo "Hello, $1"
}
 
# 方式 2
function my_func {
    echo "Hello, $1"
}

$FUNCNAME 获取函数名($0 不是函数中的局部变量,仍然是外部的东西,如 bash)

调用者可以使用 $? 获取函数的返回值

局部变量

local name=value

作用域限制在函数内,有 shadow

删除函数

unset -f funciton_name

函数的 shell 退出的时候,函数同样会失效

shell 选项

  • u nounset 将对未定义的选项的引用作为错误
  • pipefail 管道命令组执行中如果有任何一个运行出错,则整个执行失败
  • errexit 有命令执行失败立即终止
  • noexec 不真正执行脚本,而是检查语法
  • C noclobber 不允许重定向覆盖到已经存在的文件
  • v verbose 在执行命令前输出替换完成前的命令
  • x xtrace 在执行命令前输出替换完成后的命令

设置方法

使用 set 命令

set -x 	# 打开当前shell的x选项
set -o xtrace
set +x  # 关闭
set +o xtrace

启动的时候设置

bash -o nounset
bash -e script.sh #打开-e选项

查看当前 shell 打开的所有选项

echo $-

所以调试的时候尽量全打开

终端控制

终端可以认为是显示器 + 键盘,可以包括控制台 console(每台电脑只有一个,启动信息输出到这里)、物理终端 tty,虚拟终端 tty16(可以通过 <ctrl+alt+F1~F6> 切换),伪终端 ptmxpts

查看终端设置

  ~ stty -a
speed 9600 baud; 25 rows; 96 columns;
lflags: icanon isig iexten echo echoe -echok echoke -echonl echoctl
	-echoprt -altwerase -noflsh -tostop -flusho pendin -nokerninfo
	-extproc
iflags: -istrip icrnl -inlcr -igncr ixon -ixoff ixany imaxbel iutf8
	-ignbrk brkint -inpck -ignpar -parmrk
oflags: opost onlcr -oxtabs -onocr -onlret
cflags: cread cs8 -parenb -parodd hupcl -clocal -cstopb -crtscts -dsrflow
	-dtrflow -mdmbuf
cchars: discard = ^O; dsusp = ^Y; eof = ^D; eol = <undef>;
	eol2 = <undef>; erase = ^?; intr = ^C; kill = ^U; lnext = ^V;
	min = 1; quit = ^\; reprint = ^R; start = ^Q; status = ^T;
	stop = ^S; susp = ^Z; time = 0; werase = ^W;
stty -echo;read aaaa;stty echo;echo $aaaa

终端特殊字符能够美化,但是对于不同终端字符可能有区别,这个时候就有

terminfo 数据库,使用 tput 命令输出指定功能的转义控制字符

tput clear
tput bel

这些是带状态的,可以通过 tput reset 恢复默认的状态

当你执行 tput 命令时,它会执行以下操作:

  1. 确定终端类型: tput 首先会尝试从环境变量 $TERM 中获取当前终端的类型。你也可以使用 -T 选项显式指定终端类型。
  2. 查询 terminfo 数据库: tput 接着会在 terminfo 数据库中查找与该终端类型匹配的条目。terminfo 数据库通常位于 /usr/share/terminfo 目录下,并按照终端名称的首字母分级存放。
  3. 获取功能代码: 根据你提供的 tput 参数(即你想要执行的操作,比如 clearcupbold 等),tput 会在找到的终端条目中查找对应的 capability name(功能名称)。每个 capability name 都关联着一个特定的控制序列。
  4. 输出控制序列: 如果找到了对应的 capability name,tput 会将与其关联的控制序列输出到标准输出。这些控制序列是终端能够理解的指令,当你的 shell 或其他程序将这些序列发送给终端时,终端就会执行相应的操作。

常用 tput 命令示例及原理:

  • 清屏 (tput clear):
    • clear 是一个 capability name,它在 terminfo 数据库中对应着当前终端清屏的控制序列。
    • 执行 tput clear 会输出该控制序列,通常是 ANSI 转义序列 \033[2J
    • 当你的终端接收到这个序列时,它会清除屏幕上的所有内容并将光标移动到左上角。
  • 移动光标 (tput cup <row> <column>):
    • cup (cursor position) 是一个需要参数的 capability name,<row><column> 分别代表行号和列号(通常从 0 开始计数)。
    • tput cup 5 10 会在 terminfo 数据库中查找 cup 对应的控制序列,并将其中的参数替换为 510。例如,对于 xterm 终端,这可能会生成 \033[6;11H
    • 终端接收到这个序列后,会将光标移动到指定的行和列。
  • 设置文本样式 (例如,粗体 tput bold):
    • bold 是一个 capability name,对应着设置文本为粗体的控制序列。
    • tput bold 会输出该序列,例如 \033[1m
    • 终端接收到后,后续输出的文本将会以粗体显示,直到遇到关闭粗体的控制序列 (tput sgr0,对应 \033[0m)。
  • 设置下划线 (tput smul):
    • smul (start underline mode) 对应开始下划线的控制序列,例如 \033[4m
    • 使用 tput rmul (reset underline mode,对应 \033[24m) 关闭下划线。
  • 获取终端信息 (例如,列数 tput cols):
    • cols 是一个 capability name,对应着终端的列数。
    • tput cols 会直接输出一个数字,表示当前终端的宽度(列数)。