shell 变量
普通变量
按作用域分类
- 普通变量:当前 shell 全局有效的变量
- 环境变量:(比普通变量大)当前 shell 全局有效,并且能够传递给子进程
- 局部变量:在函数内部定义,仅在函数内部有效的变量
按使用方式分类
- 普通变量
- 标准变量(posix 标准):对 shell 行为有影响或者记录 shell 运行状态的特殊变量
shell 变量只有一种类型:字符串
设置变量
name=value
- 变量名未定义则定义,已经定义则重新赋值
- 变量名大小写敏感
- 等号两侧不能有空格(不然的话就理解成外部命令了)
- 若变量值字符串中包含空白字符,需要用单引号或者双引号括起来
引用变量
$name
${name}
两种方式都可以
引用的变量均被解释成字符串,在命令执行前完成变量替换。如果变量不存在,则返回空串
查看变量
set
列出当前 shell 进程中所有已经定义的变量
删除变量
unset name
读入变量
read name1 name2 ...
从标准输入中读入一行字符串,对字符串进行分割,然后将分割后的子串存入变量中
- 如果变量数量太多,后续设置为空
- 最后一个变量存储剩余所有子串,空格隔开
环境变量
export name[=value]
- 可以将已经有的普通变量设置为环境变量
export exited_name
- 环境变量会被子进程继承(子进程拿到父进程 shell 环境变量的副本)
查看环境变量
export
env
printenv
shell 变量原理
每个 shell 进程维护一张变量表,记录映射:
- 变量名 →值:变量名和变量值均被 shell 视作字符串
- 变量名 →作用范围:指定变量是普通变量还是环境变量
子进程可以通过下面三种方式获取继承的环境变量:
- main 函数的第三个参数 envp
- c 的全局变量 environ
- getenv 函数
变量变换
${VAR:-word}
如果 VAR 不存在则返回 word(:
可以省略)${VAR:=word}
如果 VAR 不存在则返回 word 并赋值给 VAR${VAR:?message}
如果 VAR 不存在则打印 message 并退出(如果是交互式则不会退出)${VAR:start:length}
截取${VAR/old[/new]}
替换
标准变量
在 POSIX 标准中定义和使用的变量
HOME
指定当前用户的主目录。该标准变量会影响到 cd 命令和~
元字符的行为PATH
指定 shell 外部命令的搜索路径和搜索顺序- 该标准变量影响 shell 对外部命令的执行
- 也会影响部分 shell 命令的行为:
type
which
whereis
PS1
PS2
指定命令提示符的字符串(Prompt String)- PS1
%(?:%{%}%1{➜%} :%{%}%1{➜%} ) %{%}%c%{%} $(git_prompt_info)
- PS2 是用户尚未输入完整命令但是有回车的时候的提示字符
- PS1
SHELL
用户登录的 shell 程序TERM
保存当前用户终端的类型USER
当前用户名LOGNAME
当前用户登录名IFS
该变量指定对替换后展开的文本进一步单词分割时的分隔符- 修改 read 命令的行为
$
当前进程的 pid?
上一条命令的退出状态码(char)-1 变成 255_
保存上一条命令的输出结果
shell 元字符
转义和换行
\<meta>
用于转义后面的元字符,使其失去原有意义\<enter>
用于转义换行,到下一行继续输入
目录
~<username>
指定用户的家目录~+
当前工作目录的全路径~-
前一个工作目录的全路径
命令分组和批量操作
;
按顺序执行多条命令(cmd;cmd;...[;])
将一组命令作为单个命令,在一个独立的 子 shell 中执行(date;pwd;ls) > outfile
- 也可以用回车分隔
{ cmd;cmd;...; }
将一组命令作为单个命令,在当前的 shell 中执行- 这里的空格不能忽略,末尾的分号不能忽略
- 这里新定义的变量和函数在批量操作之后仍然有效
命令替换
`cmd`
执行命令,将命令行中的该元字符原有位置替换为执行结果$(cmd)
同上
算数表达式
$((3+6))
$((x=8**3))
$((1<<3+(++x)?0:1))
$(( $(echo "2+3+$((2+3))") ))
字符串
'str'
单引号内所有字符都被视为普通字符"str"
双引号内进行转义、变量替换、命令替换和算数表达式替换
路径通配符
出现在路径中,本质是展开
*
匹配任意子串?
匹配任意单个字符[list]
匹配 list 中任一字符,出现一次[1-9]
匹配区间
[!list]
[^list]
匹配不在 list 中任一字符,出现一次
shell 在真正执行命令之前,会将通配符展开能匹配的所有文件名
生成数列
{start..end}
{start..end..step}
也是一个展开
本身是命令的元字符
:
空命令(())
算数表达式计算(非 POSIX 标准)。不进行命令行替换,表达式的结果直接丢弃。若合法返回 0,否则返回 1!
将命令返回状态取反&&
所有命令执行成功才返回成功,有短路||
只要有命令成功就返回成功,有短路
0 代表成功,非 0 代表失败
true
返回 0,表示执行成功false
返回 1,表示执行失败
shell 命令解析过程
- 拆分命令为空格分割的 token
- shell 关键字:if then else for …
- alias 展开
- ~ 元字符展开
- 变量替换
- 命令替换
- 算数表达式替换
- 替换后展开根本的分割(IFS)
- 路径通配符(wildcard)展开
- 命令查找:特殊内置命令 →shell 函数 →普通内部命令 →外部命令 →报错
普通内置命令可以使用 shell 函数进行重载
eval
shell 替换只做一次,可以用 eval 驱使 shell 做第二次替换和拓展
xx=yy
yy=zz
echo \$$xx
> $yy
eval echo \$$xx
> zz
shell 其他重要命令
printf
和 C 语言一致let
和(())
等价,最好用双引号将算数表达式保护起来expr
POSIX 标准,外部命令,只能进行整数运算,算数表达式之间必须有空格,大量运算符都需要在 shell 中转移test
[]
是一个外部命令,测试条件真假,方括号两端必须加空格 (因为[
这玩意是一个命令)(就是 test)- 字符串测试(相同、空)(要用双引号扩起来保护,否则空字符串可能直接被丢弃了)
- 文件测试(存在)
- 括号需要转义
[[]]
是单方括号的扩展,支持更加多指令(非 POSIX 标准)- 支持通配符、正则表达式匹配
history
显示之前执行过的命令,当用户退出的时候,将内存中保存的命令追加到历史文件中tee
从标准输入读数据,同时输出到指定文件和标准输出xarg
将标准输入的数据作为参数传递给其他命令,常与 find 和 grep 合作{}
作为默认占位符,不采用则放在末尾- 可以有拆分
whereis
which
基于 PATH 查找
shell 脚本初步
运行方式
bash 运行
bash script.sh
./script.sh
新建一个 shell 进程(当前的子进程)来执行脚本,仅在新的 shell 中有效,不会对父 shell 产生影响
source 运行
source script.sh
. script.sh
在当前 shell 环境中执行脚本
脚本的返回值
默认为脚本运行的最后一条命令或语句的返回值
可以使用内置命令 exit
显式的给脚本一个返回值,不带参数的 exit 返回值与上一条命令或语句的返回值相同
shell 配置
登录交互式 shell
/etc/profile
→ ~/.bash_profile
非登录式的交互式 shell
/etc/bash.bashrc
→ ~/.bashrc
交互式 shell 退出
~/.bash_logout
非交互式 shell 退出
if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi