leafee98-blog/content/posts/shell学习笔记.md

166 lines
6.9 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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个参数 |