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 视作字符串
  • 变量名 作用范围:指定变量是普通变量还是环境变量

exec 函数族

子进程可以通过下面三种方式获取继承的环境变量:

  • 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 是用户尚未输入完整命令但是有回车的时候的提示字符
  • 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 命令解析过程

  1. 拆分命令为空格分割的 token
  2. shell 关键字:if then else for …
  3. alias 展开
  4. ~ 元字符展开
  5. 变量替换
  6. 命令替换
  7. 算数表达式替换
  8. 替换后展开根本的分割(IFS)
  9. 路径通配符(wildcard)展开
  10. 命令查找:特殊内置命令 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