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

166 lines
6.9 KiB
Markdown
Raw Normal View History

2022-04-28 06:40:02 +00:00
---
title: shell学习笔记
date: 2019-07-20 15:46:09 +0800
tags: [ linux ]
categories: [ tech ]
weight: 50
show_comments: true
draft: false
2022-04-28 06:40:02 +00:00
---
### 关于符号
* `$` 美元符号, 一般用于取变量的值, 不过总觉得和命令代换有很多相似之处, 具体可见样例, 另外美元符号也会和其他符号产生有意思的组合.
```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个参数 |