--- title: shell学习笔记 date: 2019-07-20 15:46:09 +0800 tags: [ linux ] categories: [ tech ] weight: 50 show_comments: true draft: false --- ### 关于符号 * `$` 美元符号, 一般用于取变量的值, 不过总觉得和命令代换有很多相似之处, 具体可见样例, 另外美元符号也会和其他符号产生有意思的组合. ```shell $ var="echo hello" $ $var hello ``` * `[]` 相当于test命令, 用于测试条件是否成立, 若成立则退出值为0(真), 由于if, for, case等结构语句通过上一条命令的退出值来决定运行流程, 因此方括号多用于这些结构语句中. * `$()` , `` ` ` `` 两种用法相同, 均是命令代换, 即取所包含的命令的输出作为文本值参与运行, 文本值甚至可以是命令, 如直接运行`` `echo pwd` ``则相当于直接运行`pwd`. * `$[]` 对于此命令需要提前了解双反引号(上一条), 以及`expr`命令, `expr`官方描述为*Print the value of EXPRESSION to standard output*, 所以可以把`expr`命令做命令代换, 于是`$[]`与`` `expr expression` ``效果相同, 样例如下. ```shell $ var1=`expr 1 + 1` $ var2=$[1 + 1] $ echo var1=$var1 var2=$var2 var1=2 var2=2 ``` 不过有一点奇怪的是, 以下命令的运行结果并不能如我所愿输出`hello`, 不过至少反引号和美元符号加方括号的行为是一样的, 仍然支持我的猜想. ```shell $ alias 2="echo hello" $ alias alias 2='echo hello' $ `expr 1 + 1` bash: 2: command not found $ $[1 + 1] bash: 2: command not found ``` * `;` 可用于分隔命令, 书本上翻译为命令列表, 一般来说, 命令与命令之间通过换行符来进行分隔, 但是有时为了追求代码的紧凑会希望将几条命令放在同一行, 于是这便是分号的用途. ```shell if [ expression ] ; then command fi ``` * `()` 用于表示数组, 数组样例见下一个符号的样例, 也用于进程列表, 进程列表中的命令会被开一个子shell运行, 进程列表样例如下. ```shell $ echo $BASH_SUBSHELL # 当前子shell水平为0 0 $ (echo $BASH_SUBSHELL) # 当前子shell水平为0 1 ``` 那么之前的命令代换中的括号是否也会被开一个子shell呢, ```shell $ echo $BASH_SUBSHELL # 当前子shell水平为0 0 $ echo $(echo $BASH_SUBSHELL) # 命令代换中子shell水平为1 1 ``` 猜想得证. * `${}` 此符号用于取数组的值, 需要注意的一点是, 当数组中某一个位置的值被`unset`之后, 该位置后面的值并不会自动向前移动一个序号, 可见样例. ```shell $ arr=( 1 2 3 ) $ echo ${arr[2]} 3 $ unset arr[1] $ echo ${arr[2]} 3 $ echo ${arr[1]} #空行, 无输出 $ ``` * `[[ ]]` `(( ))` 双方括号是拓展字符运算, 双圆括号是拓展数学运算, 其中拓展数学运算可支持移位操作,自增操作等高级操作, 拓展字符运算可支持通配符匹配. * `;;` 双分号用于case的匹配中, 与单分号不同的是, 单分号只能结束当前命令, 后续仍被认为是这一块的可执行语句, 不能接下一个匹配条件, 只有以双分号结尾, 才能接下一个匹配 ```shell $ cat caseExample.sh var=2 case $var in 1 | 2 ) echo 1 ; echo 2 ;; 3 | 4 ) echo 3 ; echo 4 ;; esac $ ./caseExample.sh 1 2 $ ``` ### 关于结构化语句 * `if` 判断语句 * 判断的方法只是简单地根据上一命令的返回值来判断执行结构, 关于这一点, 其实最常用的方括号只是`test`命令, 这一点在上面符号的部分有提及. 通常用法是 ```shell if command1 ; then command2 fi ``` 以上用法中, `command1`是任意一个可执行的shell命令, `command2`是条件为真则执行的语句体, 最后`if`语句使用`fi`进行结尾. 需要注意的是, **`command1`的返回值若是`0`则`if`判断为真, 非`0`则判断为假**, 这一点与许多类C语言恰恰相反. 另外, `command1`是按照通常的命令执行方式执行的, 所以如果`command1`命令有输出, 则会直接输出在控制台中. * `if`语句也可以在条件为假的时候执行语句, 方法是在结尾`fi`之前加入一个`else`; 当然`if`也可以判断多个条件进行筛选, 方法是使用`elfi`, 样例如下 ```shell if command1 ; then command2 elif command3 ; then command4 else command5 fi ``` 为了代码紧凑, 有时候会在判断命令之后使用分号进行分隔, 并把`then`放在同一行的之后 * ​ `if`语句中也可以使用逻辑运算符, 和其他许多类C语言类似, 使用`&&`, `||`, `!`, 分别作为与,或,非. 这些逻辑运算符可以用在`test`命令和普通命令之中. 需要注意的是, 在`test`手册中并没有`&&`,`||`的表述, 不过这些仍然被支持, 因为在shell中, `&&`的行为是若该符号之前的命令运行的返回值为**真**, 则执行下一条命令, `||`的行为是若该符号之前的命令运行返回值为**假**, 则执行下一条命令, 这个被称为"短路", 在C语言中同样适用. ​ 所以如此来看的话, 使用`command1 && command2`的作为分析, 如果`command1`返回值为真, 则执行`command2`, 此时整个逻辑表达式的值就由`command2`的返回值决定, 若`command2`的返回值也为真, 则最后`$?`的值就是真, 反之则假. 若`command1`返回值为假, 则短路, 最终整个逻辑运算的结果就是`command1`的返回值--假. 原因是逻辑运算符的作用仅仅是决定是否进行短路, 而且条件判断依据仅仅是之前运行的最后一条命令的返回值, 于是整个逻辑运算十分顺利. 或运算同理. ​ 至于非运算, 叹号`!`其实也是一条命令, 这一点可以在终端不输入任何字符的情况下敲入双tab, 使其打印所有的可执行命令, 其中第一条就是`!`. 样例如下 ```shell $ ! echo 'hello' && echo 'true' ; echo $? hello 1 $ echo 'hello' && echo 'true' ; echo $? hello true 0 $ ! echo 'hello' || echo 'true' ; echo $? 'hello' 'true' 0 ``` ## 特殊变量 | 变量 | 含义 | | ---- | ------------------------------------------------- | | $$ | 当前shell进程的PID | | $? | 上一命令运行的返回值 | | $# | 命令行脚本传递参数的个数 | | $@ | 作为数组(雾)获取全部命令行参数 | | $* | 作为一整条字符串获取全部命令行参数 | | $0 | 当前shell脚本的运行名(脚本的绝对路径或者相对路径) | | $n | shell脚本的第n个参数 |