上传文件至 /

This commit is contained in:
wxin 2025-03-28 20:10:14 +08:00
parent 94429534f1
commit 3c2f1a0fa3
5 changed files with 1470 additions and 0 deletions

225
shell-括号.md Normal file

@ -0,0 +1,225 @@
<h2><center>Shell 括号</center></h2>
------
## 一:括号介绍
### 1. 圆括号
| 特性 | | (()) |
| :----------: | :-------------------------------------: | :--------------------------------------: |
| **用途** | 子 Shell 执行、命令替换、数组定义 | 整数算术运算、循环条件 |
| **执行环境** | 在子 Shell 中运行,不共享当前变量 | 在当前 Shell 中执行算术操作 |
| **变量影响** | 子 Shell 内变量修改不影响父 Shell | 直接修改当前 Shell 的变量 |
| **示例** | `(a=1; echo $a)`(退出后 `a` 恢复原值) | `((a=5+3))`(直接赋值给 `a` |
| **逻辑运算** | 不适用 | 支持 `&&`, ` |
| **返回值** | 返回子 Shell 中最后一条命令的退出码 | 返回算术表达式是否为真0为真非0为假 |
### 2. 方括号
| 特性 | `[ ]`test 命令) | `[[ ]]`Bash 增强条件) |
| :------------: | :----------------------------: | :-------------------------------------: |
| **兼容性** | POSIX 标准,所有 Shell 支持 | Bash/Ksh/Zsh 特有,非 POSIX |
| **字符串比较** | 用 `=``!=`,需严格转义空格 | 直接支持 `==``!=`,无需转义 |
| **模式匹配** | 不支持通配符(需用 `case` | 支持通配符(如 `[[ $file == *.txt ]]` |
| **正则匹配** | 不支持 | 支持 `=~`(如 `[[ "abc" =~ ^a ]]` |
| **逻辑运算符** | 必须用 `-a`AND`-o`OR | 直接使用 `&&`、` |
| **变量处理** | 变量未加引号可能导致语法错误 | 自动处理变量中的空格和特殊字符 |
| **数值比较** | 用 `-eq``-lt` 等 | 也可用 `-eq`,但推荐 `(( ))` |
| **示例** | `[ "$a" -eq 5 -a "$b" != "" ]` | `[[ $str == "yes" && $num > 10 ]]` |
### 3. 花括号
| 特性 | 用途 | 示例 |
| :------------: | :-------------------------: | :------------------------------------------: |
| **代码块** | 组合命令(当前 Shell 执行) | `{ cmd1; cmd2; } > log.txt` |
| **序列生成** | 生成数字/字母序列 | `echo {1..5}``1 2 3 4 5` |
| **文件名扩展** | 快速枚举文件或路径 | `cp file.{txt,bak}``cp file.txt file.bak` |
| **变量操作** | 字符串处理、默认值替换等 | `${var:-default}`(空值时替换) |
| **注意** | 代码块首尾必须有空格和分号 | `{ echo "Start"; ls; }` |
### 4. 总对比
| 括号类型 | 核心用途 | 关键区别 | 示例 |
| :------: | :------------------------: | :---------------------------: | :----------------------: |
| `()` | 子 Shell、数组、命令替换 | 子 Shell 中运行,不共享变量 | `(cd /tmp; ls)` |
| `(( ))` | 整数算术运算、循环条件 | 直接操作变量,支持 C 风格语法 | `for ((i=0; i<10; i++))` |
| `[ ]` | 基础条件测试(兼容 POSIX | 严格空格,需转义符号 | `[ -f file ]` |
| `[[ ]]` | 增强条件测试Bash 特有) | 支持通配符、正则、逻辑运算符 | `[[ $str =~ ^[0-9]+$ ]]` |
| `{}` | 代码块、序列生成、变量扩展 | 不创建子 Shell直接扩展内容 | `echo {A..C}``A B C` |
### 5. 常见误区
1. 空格要求:
- `[ ]``[[ ]]` 内部必须保留空格:`[ "$a" = 5 ]` ✔️,`[$a=5]` ❌。
- `(( ))``[[ ]]` 中的变量无需 `$` 符号:`((a++))` ✔️,`(($a++))` ❌。
2. 字符串比较:
- `[ ]` 中必须用引号包裹变量,避免空值错误:`[ "$str" = "hello" ]`
- `[[ ]]` 中可不加引号(自动处理):`[[ $str == hello ]]`
3. 数值比较:
- `[ ]` 必须用 `-eq``-lt` 等操作符:`[ $a -gt 10 ]`
- `[[ ]]``(( ))` 中可直接用 `>``<``[[ $a > 10 ]]``((a > 10))`
4. 逻辑运算符:
- `[ ]` 中用 `-a`AND`-o`OR`[ $a -eq 1 -o $b -eq 2 ]`
- `[[ ]]` 中用 `&&``||``[[ $a == 1 && $b == 2 ]]`
## 二:示例
### 1. 圆括号
**用途**
- **子 Shell 执行**:在独立环境中运行命令,不影响当前 Shell。
- **令替换**:捕获命令输出。
- **数组定义**:初始化数组。
**示例:**
```bash
# 子 Shell 示例:临时切换目录,不影响当前 Shell
[root@wxin ~]# (current_dir=$(pwd); cd /tmp; ls; cd "$current_dir")
# 命令替换:获取当前日期
[root@wxin ~]# today=$(date +"%Y-%m-%d")
[root@wxin ~]# echo "今天是$today"
今天是2025-03-26
# 数组定义
[root@wxin ~]# fruits=("apple" "banana" "cherry")
[root@wxin ~]# echo "第二个水果是 ${fruits[1]}"
第二个水果是 banana
```
### 2. 双括号
**用途**
- **整数算术运算**:替代 `expr`,支持 C 语言风格语法。
- **循环条件**:简化 `for`/`while` 循环。
**示例:**
```bash
# 算术运算
[root@wxin ~]# (( sum = 3+5*2 ))
[root@wxin ~]# echo "结果是 $sum"
结果是 13
# 自增操作
[root@wxin ~]# count=0
[root@wxin ~]# (( count++ ))
[root@wxin ~]# echo "计数: $count"
计数: 1
# 循环条件
[root@wxin ~]# for (( i=0;i<3;i++ ));do
> echo "循环第 $i 次"
> done
循环第 0 次
循环第 1 次
循环第 2 次
```
### 3. 方括号
**用途**
- **条件测试**:兼容 POSIX 的基础条件判断。
- **文件检查**:测试文件属性(如是否存在、是否可读)。
**示例:**
```bash
# 数值比较
[root@wxin ~]# if [ "$a" -eq 10 ];then echo "a 等于 10"; fi
a 等于 10
# 文件存在性检查
[root@wxin ~]# if [ -f "/etc/passwd" ];then
> echo "文件存在"
> fi
文件存在
# 逻辑组合(使用 -a/-o
[root@wxin ~]# if [ "$a" -gt 5 -a "$a" -lt 15 ];then
> echo "a 在 5 到 15 之间"
> fi
a 在 5 到 15 之间
```
### 4. 双括号
**用途**
- **增强条件测试**:支持通配符、正则表达式和逻辑运算符。
- **模式匹配**:直接使用 `==``=~`
**示例:**
```bash
# 字符串匹配(支持通配符)
[root@wxin ~]# file="image.jpg"
[root@wxin ~]# if [[ "$file" == *.jpg ]];then
> echo "这是一个 JPEG 文件"
> fi
这是一个 JPEG 文件
# 正则表达式匹配
[root@wxin ~]# phone="123-456-7890"
[root@wxin ~]# if [[ "$phone" =~ ^[0-9]{3}-[0-9]{3}-[0-9]{4}$ ]];then
> echo "有效的电话号码"
> fi
有效的电话号码
# 逻辑运算符(直接使用 &&/||
[root@wxin ~]# str="hello"
[root@wxin ~]# num=8
[root@wxin ~]# if [[ "$str" == "hello" && $num -gt 5 ]];then
> echo "条件满足"
> fi
条件满足
```
### 5. 花括号
**用途**
- **代码块**:组合命令(当前 Shell 执行)。
- **序列生成**:快速生成数字或字母序列。
- **文件名扩展**:批量操作文件。
- **变量操作**:字符串处理和默认值替换。
**示例:**
```bash
# 代码块(重定向所有输出)
[root@wxin ~]# {
> echo "操作开始于 $(date)"
> ls -l
> echo "操作结束"
> } > log.txt
# 序列生成
[root@wxin ~]# echo {1..5}
1 2 3 4 5
[root@wxin ~]# echo {A..C}
A B C
[root@wxin ~]# echo file{1..3}.txt
file1.txt file2.txt file3.txt
# 文件名扩展(快速复制备份)
[root@wxin ~]# cp config.txt{,.bak}
# 变量默认值替换
[root@wxin ~]# unset var
[root@wxin ~]# echo "${var:-默认值}"
默认值
# 字符串截取
[root@wxin ~]# filename="photo.jpg"
[root@wxin ~]# echo "${filename%.*}"
photo
[root@wxin ~]# echo "${filename##*.}"
jpg
```

331
shell-数组.md Normal file

@ -0,0 +1,331 @@
<h2><center>Shell 数组</center></h2>
------
## 一:数组
### 1. 介绍
Shell 的数组就是把有限个元素变量或字符内容用一个名字命名然后用编号对它们进行区分的元素集合。这个名字就称为数组名用于区分不同内容的编号就称为数组下标。组成数组的各个元素变量称为数组的元素有时也称为下标变量。有了Shell数组后就可以用相同名字引用一系列变量及变量值并通过数字索引来识别使用它们。在许多场合使用数组可以缩短和简化程序开发。数组的本质还是变量是特殊的变量形式。
### 2. 数组分类
- **索引数组:**只能使用整数作为数组索引/下标(从0开始)
- **关联数组:**可以使用字符串作为数组索引/下标
### 3. 索引数组
#### 数组定义
**方法一:**用小括号将变量值括起来赋值给数组变量,每个变量值之间要用空格分隔
```bash
[root@wxin ~]# array=(value1 value2 value3 ... )
示例:
[root@wxin ~]# array=(1 2 3) # 用小括号将数组内容赋值给数组变量,数组元素用“空格”分隔开。
[root@wxin ~]# echo ${array[*]} # 输出上面定义的数组的所有元素值,注意语法。
1 2 3
```
**方法二:**用小括号将变量值括起来,同时采用键值对的形式赋值
```bash
[root@wxin ~]# array=([0]=one [1]=two [2]=three [3]=four)
```
**方法三:**通过分别定义数组变量的方法来定义
```bash
[root@wxin ~]# array[0]=one
[root@wxin ~]# array[1]=two
[root@wxin ~]# array[2]=three
[root@wxin ~]# array[3]=four
[root@wxin ~]# echo ${array[@]} # 查看所有数组的值
one two three four
```
**方法四:**动态地定义数组变量,并使用命令的输出结果作为数组的内容
```bash
array=($(命令))
array=(`命令`)
示例:
[root@wxin ~]# array=(`ls ./`)
[root@wxin ~]# echo ${array[@]}
anaconda-ks.cfg initial-setup-ks.cfg 公共 模板 视频 图片 文档 下载 音乐 桌面
```
#### 常用变量
```shell
${ARRAY_NAME[INDEX]} # 引用数组中的元素 注意引用时只给数组名表示引用下标为0的元素
${#ARRAY_NAME[*]} # 数组中元素的个数
${#ARRAY_NAME[@]} # 数组中元素的个数
${ARRAY_NAME[*]} # 引用数组中的所有元素
${ARRAY_NAME[@]} # 引用数组中的所有元素
${#ARRAY_NAME} # 数组中下标为 0 的字符个数
```
#### 访问数组
```bash
[root@wxin ~]# array=(one two three four)
[root@wxin ~]# echo ${array} 打印单个数组元素用${数组名[下标]}当未指定数组下标时数组的下标是从0开始。
one
[root@wxin ~]# echo ${array[0]}
one
[root@wxin ~]# echo ${array[*]} # 使用*或者@可以得到整个数组内容
one two three four
[root@wxin ~]# echo ${array[@]}
one two three four
[root@wxin ~]# echo ${#array[*]} # 用${#数组名[@或*]}可以得到数组长度,这和前文讲解的变量子串知识是一样的,因为数组也是变量,只不过是特殊的变量,因此也适合变量的子串替换等知识。
4
[root@wxin ~]# echo ${#array[@]}
4
```
#### 数组赋值
直接通过`数组名[下标]`对数组进行引用赋值,如果下标不存在,自动添加新一个数组元素,如果下标存在就覆盖原来的值
数组中元素的赋值方式主要有几个方式:
```bash
# 一次只能赋值一个元素
[root@wxin ~]# array[index]=value
# 一次赋值全部元素
[root@wxin ~]# array=("value1" "value2" "value3" ...)
# 只赋值特定元素
[root@wxin ~]# array=([0]="value1" [3]="value2" ...)
# 交互式赋值
[root@wxin ~]# read -a array
```
#### 删除数组
因为数组本质上还是变量,因此可通过`unset 数组[下标]`清除相应的数组元素,如果不带下标,表示清除整个数组的所有数据
```bash
[root@wxin ~]# echo ${array[@]}
anaconda-ks.cfg initial-setup-ks.cfg 公共 模板 视频 图片 文档 下载 音乐 桌面
[root@wxin ~]# echo ${array[*]}
anaconda-ks.cfg initial-setup-ks.cfg 公共 模板 视频 图片 文档 下载 音乐 桌面
[root@wxin ~]# unset array[0]
[root@wxin ~]# echo ${array[*]}
initial-setup-ks.cfg 公共 模板 视频 图片 文档 下载 音乐 桌面
[root@wxin ~]# unset array
[root@wxin ~]# echo ${array[*]}
```
#### 数组元素删除
```bash
[root@wxin ~]# array=(one two three four five)
[root@wxin ~]# echo ${array[@]}
one two three four five
[root@wxin ~]# echo ${array[@]#*o} # 从每个元素的左边开始匹配最短的,并删除。
ne three ur five
[root@wxin ~]# echo ${array[@]##*e} # 从每个元素的左边左边开始匹配最长的,并删除。
two four
[root@wxin ~]# echo ${array[@]%f*} # 从每个元素的右边开始匹配最短的,并删除。
one two three
[root@wxin ~]# echo ${array[@]%%f*} # 从每个元素的右边开始匹配最长的,并删除。
one two three
```
#### 数组截取/切片
```bash
命令格式:
${array_name[@]:offset:number}
# offset: 要路过的元素个数
# number: 要取出的元素个数省略number时表示取偏移量之后的所有元素
[root@wxin ~]# array=(1 2 3 4 5)
[root@wxin ~]# echo ${array[@]:1:3} # 从下标为1的元素开始截取共取3个数组元素
2 3 4
[root@wxin ~]# array=({a..z})
[root@wxin ~]# echo ${array[@]}
a b c d e f g h i j k l m n o p q r s t u v w x y z
[root@wxin ~]# echo ${array[@]:3:3} # 从下标为3的元素开始截取共取3个数组元素。
d e f
[root@wxin ~]# echo ${array[@]:0:3} # 从下标为0的元素开始截取共取3个数组元素
a b c
```
#### 数组替换
```bash
[root@wxin ~]# array=(1 2 3 4)
[root@wxin ~]# echo ${array[@]}
1 2 3 4
[root@wxin ~]# echo ${array[@]/2/baby} # 把数组中的2替换成baby原数组未被修改。
1 baby 3 4
```
### 4. 遍历数组
**`for`循环**
```bash
[root@wxin ~]# array=(one two three four five)
[root@wxin ~]# for item in "${array[@]}"; do
> echo "$item"
> done
one
two
three
four
five
[root@wxin ~]# for((i=0;i<${#array[@]};i++)); do
> echo ${array[i]}
> done
one
two
three
four
five
```
**`while`循环**
```bash
[root@wxin ~]# i=0
[root@wxin ~]# while [ $i -lt ${#array[@]} ]; do
> echo ${array[$i]}
> let i++
> done
one
two
three
four
five
[root@wxin ~]# i=0
[root@wxin ~]# while [[ i -lt ${#array[@]} ]]; do
> echo ${array[i]}
> let i++
> done
```
### 4. 关联数组
Bash 支持关联数组,它可以使用字符串作为数组索引,关联数组一定要事先声明才行,不然会按照索引数组进行执行
#### 定义关联数组
**使用声明语句将一个变量声明为关联数组**。
```bash
[root@wxin ~]# declare -A assArray
```
利用内嵌索引-值列表的方法
```bash
[root@wxin ~]# assArray=([lucy]=beijing [yoona]=shanghai)
[root@wxin ~]# echo ${assArray[lucy]}
beijing
```
使用独立的索引-值进行赋值
```bash
[root@wxin ~]# assArray[lily]=shandong
[root@wxin ~]# assArray[sunny]=xian
[root@wxin ~]# echo ${assArray[sunny]}
xian
[root@wxin ~]# echo ${assArray[lily]}
shandong
```
#### 列出数组索引
每一个数组都有一个索引用于查找。使用${!数组名[@或者*]}获取数组的索引列表
```bash
[root@wxin ~]# echo ${!assArray[*]}
lily yoona sunny lucy
[root@wxin ~]# echo ${!assArray[@]}
lily yoona sunny lucy
```
#### 获取所有键值对
```bash
[root@wxin ~]# vim assArray.sh
#!/bin/bash
declare -A cityArray
cityArray=([yoona]=beijing [lucy]=shanghai [lily]=shandong)
for key in ${!cityArray[@]}; do
echo "$key come from ${cityArray[$key]}"
done
[root@wxin ~]# bash assArray.sh
lily come from shandong
yoona come from beijing
lucy come from shanghai
```
**示例1**使用循环批量输出数组的元素
```bash
#!/bin/bash
array=(1 2 3 4 5)
for i in ${array[@]}; do
echo $i
done
array1=(1 2 3 4 5)
for ((i=0;i<${#array1[@]};i++)); do
echo ${array[i]}
done
array2=(1 2 3 4 5)
i=0
while ((i<${#array2[@]})); do
echo ${array2[i]}
let i++
done
```
**示例2**通过竖向列举法定义数组元素并批量打印
```bash
#!/bin/bash
array=(one two three four five)
for ((i=0;i<${#array[@]};i++)); do
echo "This is num $i, then content is ${array[i]}"
done
```
**示例3**把命令结果作为数组元素定义并打印
```bash
#!/bin/bash
array=(`ls ./`)
for ((i=0;i<${#array[@]};i++)); do
echo "This is num $i, then content is ${array[i]}"
done
echo ==========================================
array2=(`ls ./`)
num=1
for i in ${array[@]}; do
echo "This is num $num, then content is $i"
let num++
done
```

89
shell-正则.md Normal file

@ -0,0 +1,89 @@
<h2><center>Shell 正则</center></h2>
------
## 一:正则表达式
### 1. 正则应用环境
重要的文本处理工具vim、sed、awk、grep
各种语言和应用程序: mysql、oracle、php、python、Apache、Nginx ...
### 2. 正则介绍
正则表达式Regular Expression通常简称为 regex 或 RE是一种字符表达方式可以用它来查找匹配特定准则的文本。在许多编程语言中都有用到正则表达式常用它来实现一些复杂的匹配。这里简单介绍一下 shell 中常用到的一些正则表达式。
正则表达式是对字符串进行操作的一种逻辑公式即用事先定义好的的一些特定字符以及这些特定字符的组合组成一个有一定规则的字符串Regular Expression使用这个有一定规则的字符串来表达对字符串的一种过滤逻辑。正则表达式被广泛应用于Linux和许多其他编程语言中而且不论在哪里其基本原理都是一样的。
正则表达式是由两个基本组成部分所建立:一般字符与特殊字符。一般字符是指没有任何特殊意义的字符;特殊字符,常称为元字符 metacharacter或 meta 字符,正则表达式将匹配被查找行中任何位置出现的相同模式。在正则表达式中,元字符是最重要的概念。在某些情况下,特殊字符也可被视为一般字符(使用转义符 \ 进行转义)。
POSIX 有两种风格的正则表达式基本正则表达式BRE和扩展正则表达式ERE。这两种风格的正则表达式在一些字符含义上有细微的差距。以常用的 grep 指令来说grep 指令默认支持的是 BRE若要使用 ERE 进行匹配,可以使用 -E 选项,接下来的例子中均使用 grep 指令来演示正则表达式的使用。
案例:
```shell
匹配数字: ^[0-9]+$
匹配Mail [a-z0-9_]+@[a-z0-9]+\.[a-z]+
匹配IP [0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}
[root@wxin ~]# egrep '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' /etc/sysconfig/network-scripts/ifcfg-ens33
IPADDR=192.168.159.130
GATEWAY=192.168.159.2
NETMASK=255.255.255.0
DNS1=192.168.159.2
```
### 3. 元字符
- shell 元字符(也称为通配符)
- 正则表达式元字符
案例:
```bash
[root@wxin ~]# rm -rf *.pdf
[root@wxin ~]# grep 'abc*' /etc/passwd
abrt:x:173:173::/etc/abrt:/sbin/nologin
```
### 4. 正则表达式元字符
#### 1. 基础元字符
| 元字符 | 作用 | 示例 |
| :----: | :------------------------------------------------------: | -------------------------- |
| `.` | 匹配任意单个字符 | `a.c``"abc""a1c"` |
| `^` | 匹配行开头 | `^start`→行以`"start"`开头 |
| `$` | 匹配行结尾 | `end$` → 行以 "end" 结尾 |
| `[]` | 匹配字符组中的任意一个字符 | `[aeiou]` → 任意元音字母 |
| `[^]` | 匹配不在字符组中的任意一个字符 | `[^0-9]` → 非数字字符 |
| `\` | 转义字符,将元字符转为普通字符,或将普通字符转为特殊功能 | `a\.b` → 字面量 `"a.b"` |
#### 2. 量词元字符
| 元字符 | 作用 | 示例 |
| :-----: | :------------------------: | :---------------------------: |
| `*` | 前字符出现 **0 次或多次** | `a*b` → "b", "ab", "aaab" |
| `+` | 前字符出现 **1 次或多次** | `a+b` → "ab", "aaab" |
| `?` | 前字符出现 **0 次或 1 次** | `colou?r` → "color", "colour" |
| `{n,m}` | 前字符出现 **n 到 m 次** | `a{2}` → "aa" |
#### 3. 分组与逻辑
| 元字符 | 作用 | 示例 |
| :----: | :------------------: | :-----------------------: |
| `()` | **分组**,捕获子模式 | `(ab)+` → "ab", "abab" |
| `|` | **逻辑或** | `at|dog` → "cat" 或 "dog" |
#### 4. 预定义字符类
| 元字符 | 作用 | 示例 |
| :----: | :------------------------: | :-------------------: |
| `\d` | 数字(部分工具支持) | `\d+` → "123" |
| `\w` | 单词字符(字母数字下划线) | `\w+` → "hello123" |
| `\s` | 空白字符(空格、制表符等) | `\s+` → 匹配空白 |
| `\b` | 单词边界 | `\bword\b` → 独立单词 |

536
shell-流程控制.md Normal file

@ -0,0 +1,536 @@
<h2><center>Shell 流程控制</center></h2>
------
## 一:条件测试
### 1. 测试方法
| 方法 | 描述 |
| :--------------: | :----------------------------------------------------------: |
| test 测试表达式 | 利用test命令进行条件测试表达式test命令与测试表达式之间至少有一个空格 |
| [ 测试表达式 ] | 通过[ ]中括号进行条件测试表达式,[]中括号边界与测试表达式之间至少有一个空格 |
| [[ 测试表达式 ]] | 通过[[ ]]双中括号进行条件测试表达式,[[ ]]双中括号与测试表达式之间至少有一个空格 |
| ((测试表达式)) | 通过(( ))双小括号进行条件测试表达式,( ))双小括号两端不需要空格,常用于整数对比 |
### 2. 文件测试
**语法**
```shell
# test 操作符 文件或目录
# [ 操作符 文件或目录]
```
| 操作符 | 描述 |
| ----------- | -------------------------------------------------- |
| **-d 文件** | 文件存在且为目录则为真 |
| **-f 文件** | 文件存在且为普通文件则为真 |
| **-e 文件** | 文件存在则为真,不辩别是目录还是文件 |
| **-s 文件** | 文件存在且文件大小不为0则为真 |
| **-r 文件** | 文件存在且可读则为真,与执行脚本的用户权限也有关 |
| **-w 文件** | 文件存在且可写则为真,与执行脚本的用户权限也有关 |
| **-x 文件** | 文件存在且可执行则为真,与执行脚本的用户权限也有关 |
| -L 文件 | 文件存在且为链接文件则为真 |
| f1 -nt f2 | 文件f1比文件f2新则为真根据文件的修改时间计算 |
| f1 -ot f2 | 文件f1比文件f2旧则为真根据文件的修改时间计算 |
示例:
```bash
[root@wxin ~]# test -d /home
[root@wxin ~]# echo $?
0
[root@wxin ~]# test -d /home111
[root@wxin ~]# echo $?
1
[root@wxin ~]# [ ! -d /ccc ]
[root@wxin ~]# [ ! -d /ccc ] && mkdir /ccc
[root@wxin ~]# [ -d /ccc ] || mkdir /ccc
```
### 3. 数字比较
**语法**
```shell
# test 数字 操作符 数字
# test $((变量)) 操作符 $((变量))
# [ 数字 操作符 数字 ]
# [ $((变量)) 操作符 $((变量)) ]
# [[ 数字 操作符 数字 ]]
# [[ $"变量" 操作符 $"变量" ]]
# [[ $((变量)) 操作符 $((变量)) ]]
# ((数字 操作符 数字))
# (($"变量" 操作符 $"变量"))
```
| **在[]、[[ ]]和test中使用** | **在(( ))中使用** | **说明** |
| --------------------------- | ----------------- | -------------------------------- |
| -eq | ==或= | 等于全拼为equal |
| -ne | != | 不等于全拼为not equal |
| -gt | > | 大于全拼为greater than |
| -ge | >= | 大于等于全拼为greater or equal |
| -lt | < | 小于全拼为less than |
| -le | <= | 小于等于全拼为less or equa |
**示例:**
```bash
[root@wxin ~]# test 1 -gt 0;echo $?
0
[root@wxin ~]# test $((num1)) -gt $((num2));echo $?
1
[root@wxin ~]# [ 1 -gt 0 ];echo $?
0
[root@wxin ~]# [ $((num1)) -gt $((num2)) ];echo $?
1
[root@wxin ~]# [[ 1 -lt 2 ]];echo $?
0
[root@wxin ~]# [[ $"num1" != $"num2" ]];echo $?
0
[root@wxin ~]# [[ $((num1)) -eq $((num2)) ]];echo $?
1
[root@wxin ~]# ((1 == 2));echo $?
1
[root@wxin ~]# (($"num1"<$"num2"));echo $?
0
```
### 4. 字符串比较
语法:
```shell
# test 参数 字符串
# test 字符串1 参数 字符串
# [ 参数 字符串 ]
# [ 字符串1 参数 字符串2 ]
```
| 参数 | 示例 | 功能 |
| --------- | ------ | -------------------------------------------- |
| **-z** | s1 | 如果字符串s1的长度为0则测试条件为真 |
| **-n** | s1 | 如果字符串s1的长度大于0则测试条件为真 |
| | s1 | 如果字符串s1不是空字符串则测试条件为真 |
| **=或==** | s1=s2 | 如果s1等于s2则测试条件为真,“=”前后应有空格 |
| **!=** | s1!=s2 | 如果s1不等于s2则测试条件为真 |
| < | s1<s2 | 如果按字典顺序s1在s2之前则测试条件为真 |
| > | s1>s2 | 如果按自定顺序s1在s2之后则测试条件为真 |
**示例:**
```bash
[root@wxin ~]# str="12345"
[root@wxin ~]# str2="namespace"
[root@wxin ~]# test -z "$str";echo $?
1
[root@wxin ~]# test "$str";echo $?
0
[root@wxin ~]# [ -z "$str" ];echo $?
1
[root@wxin ~]# [ "$str" != "$str2" ];echo $?
0
```
### 5. 逻辑操作符
| **在[]和test中使用** | **在[[ ]]和(( ))中使用** | **说明** |
| -------------------- | ------------------------ | ---------------------------------- |
| -a | && | and两端都为真则结果为真 |
| -o | \|\| | or两端有一个为真则结果为真 |
| ! | ! | not两端相反则结果为真 |
## 二:条件判断
### 1. 流程控制
在一个shell脚本中的命令执行顺序称作脚本的流大多数脚本会根据一个或多个条件来改变它们的流。
流控制命令:能让脚本的流根据条件而改变的命令称为条件流控制命令。
exit语句退出程序的执行并返回一个返回码返回码为0正常退出非0为非正常退出。
条件判断if代码返回0表示真非0为假。
### 2. 语法结构
**单分支结构**
```shell
if [ 条件判断式 ]; then
条件成立时,执行的程序
fi
```
**双分支结构**
```shell
if [ 条件判断式 ]; then
条件成立时,执行的程序
else
条件不成立时,执行的另一个程序
fi
```
**多分支结构**
```bash
if [ 条件判断式1 ]; then
当条件判断式1成立时执行程序1
elif [ 条件判断式2 ]; then
当条件判断式2成立时执行程序2
...省略更多条件....
else
当所有条件都不成立,最后执行此程序
fi
```
**case**
```shell
case $变量名 in
值1
如果变量的值等于值1则执行指令1
;;
值2
如果变量的值等于值2则执行指令2
;;
值3
如果变量的值等于值3则执行指令3
;;
*
如果变量的值不等于以上列出的任何值则执行默认指令
esac
```
## 三:循环结构
### 1. for 循环
**语法结构**
```bash
for 变量 in 值集合
do
执行命令
done
```
- for 每次从值集合中取一个值赋值给变量
- do - done 将赋值后的变量带入执行的命令得到执行结果
- 重复以上两个步骤,直到值集合中的值被一一获取赋值给变量的到所有结果,循环结束
### 2. while 循环
**语法结构**
```shell
while 条件测试
do
执行命令
done
```
- while 首先进行条件测试如果传回值为0条件测试为真则进入循环执行命令区域否则不进入循环。
- 满足 while 测试条件,执行命令区域,直到 while 的测试条件不满足结束执行while循环如果条件一直满足执行无穷循环
### 3. until 循环
**语法结构**
```shell
until 条件测试
do
执行命令
done
```
- until 条件测试结果为假传回值不为0就进入循环。
- 条件测试不满足,执行命令区域。直到 until 条件满足结束执行until 循环(如果条件一直不满足则执行无穷循环)。
## 四:循环控制
### 1. break
**语法结构**
```shell
break n # n 表示跳出循环的次数, 如果省略n表示跳出单个循环
```
**示例**
```bash
for i in {1..3}; do
for j in {1..3}; do
for k in {1..3}; do
if [ $k -eq 2 ]; then
break 2
fi
echo "i=$i,j=$j,k=$k"
done
done
done
输出:
i=1, j=1, k=1
i=2, j=1, k=1
i=3, j=1, k=1
break 2 # 跳出2次循环
for i in {1..3}; do
for j in {1..3}; do
for k in {1..3}; do
if [ $k -eq 2 ]; then
break
fi
echo "i=$i,j=$j,k=$k"
done
done
done
输出:
i=1, j=1, k=1
i=1, j=2, k=1
i=1, j=3, k=1
i=2, j=1, k=1
i=2, j=2, k=1
i=2, j=3, k=1
i=3, j=1, k=1
i=3, j=2, k=1
i=3, j=3, k=1
```
### 2. continue
**语法结构**
```shell
continue n # 跳转到第n层封闭循环的下一个迭代
```
**示例**
```bash
for i in {1..3}; do
for j in {1..3}; do
if [ $j -eq 2]; then
continue
fi
echo "i=$i, j=$j"
done
done
输出:
i=1, j=1
i=1, j=3
i=2, j=1
i=2, j=3
i=3, j=1
i=3, j=3
for i in {1..3}; do
for j in {1..3}; do
if [ $j -eq 2]; then
continue 2
fi
echo "i=$i, j=$j"
done
done
输出:
i=1, j=1
i=2, j=1
i=3, j=1
```
### 3. exit
**语法格式**
```shell
exit # 退出循环
```
**示例:**
```bash
[root@wxin ~]# vim exit.sh
#!/bin/bash
for i in {1..5}; do
for j in {1..5}; do
if [ $j -eq 3 ]; then
exit
fi
echo "$i,$j"
done
done
[root@wxin ~]# bash exit.sh
1,1
1,2
```
## 五shift 指令
shift 命令用于将参数列表 list 左移指定次数,最左端的那个参数就从列表中删除,其后边的参数继续进入循环。
shift[N]:用于将参量列表 list 左移指定次数,缺省为左移一次。
参量列表 list 一旦被移动,最左端的那个参数就从列表中删 除。while 循环遍历位置参量列表时,常用到 shift
**语法格式**
```shell
shift [n] # 默认 n=1即左移 1 位n 可以指定移动的位数
```
**示例**
```bash
# vim demo.sh
#!/bin/bash
while [ $# -gt 0 ]
do
echo $*
shift
done
# ./demo.sh a b c d e f g h
a b c d e f g h
b c d e f g h
c d e f g h
d e f g h
e f g h
f g h
g h
h
```
## 六:格式化打印
### 1. 语法格式
```shell
printf "格式字符串" 参数1 参数2 ...
```
- **格式字符串**:包含格式说明符(如 `%s`, `%d`, `%f`),用于定义输出样式。
- **参数**:按顺序替换格式字符串中的说明符。
- **自动换行**`printf` **不会自动换行**,需手动添加 `\n`
### 2. 格式说明符
| 说明符 | 用途 | 示例 |
| ------ | ------------- | --------------------- |
| `%s` | 字符串 | `printf "%s" "Hello"` |
| `%d` | 整数 | `printf "%d" 42` |
| `%f` | 浮点数 | `printf "%f" 3.14` |
| `%%` | 输出 `%` 符号 | `printf "%%"` |
| `\n` | 换行 | `printf "%d\n" 42` |
| `\f` | 换页 | `printf "%d\f" 42` |
| `\r` | 回车 | `printf "%d\r" 42` |
### 3. 格式控制
**字段宽度与对齐**
- **右对齐**:默认行为,指定宽度(如 `%10s`)。
- **左对齐**:用 `-` 符号(如 `%-10s`)。
- **填充字符**:默认用空格,可以用 `0` 填充数字(如 `%05d`)。
```bash
printf "右对齐: |%10s|\n" "Text" # | Text|
printf "左对齐: |%-10s|\n" "Text" # |Text |
printf "零填充: %05d\n" 7 # 00007
```
**浮点数精度**
- 控制小数位数:`%.Nf``N` 为保留的小数位数)。
```bash
printf "保留两位小数: %.2f\n" 3.1415 # 3.14
```
## 七:颜色
### 1. 语法格式
使用 `\033[``\e[` 作为转义序列的起始符,格式为:
```shell
echo -e "\033[样式代码;前景色;背景色m文本内容\033[0m"
```
- **`-e` 选项**:允许 `echo` 解析转义字符(必须加上)。
- **`\033[0m`**:重置所有样式(避免后续文本也应用颜色)。
### 2. 颜色代码表
**文本样式**
| 代码 | 样式 |
| ---- | -------------------------- |
| `0` | 重置所有样式 |
| `1` | 粗体 |
| `4` | 下划线 |
| `7` | 反显(反转前景色和背景色) |
**前景色(文字颜色)**
| 代码 | 颜色 | 代码(亮色) |
| ---- | ------ | ------------ |
| `30` | 黑色 | `90` |
| `31` | 红色 | `91` |
| `32` | 绿色 | `92` |
| `33` | 黄色 | `93` |
| `34` | 蓝色 | `94` |
| `35` | 品红色 | `95` |
| `36` | 青色 | `96` |
| `37` | 白色 | `97` |
**背景色**
| 代码 | 颜色 | 代码(亮色) |
| ---- | ------ | ------------ |
| `40` | 黑色 | `100` |
| `41` | 红色 | `101` |
| `42` | 绿色 | `102` |
| `43` | 黄色 | `103` |
| `44` | 蓝色 | `104` |
| `45` | 品红色 | `105` |
| `46` | 青色 | `106` |
| `47` | 白色 | `107` |
示例:
```bash
# 红色文字
echo -e "\033[31m这是红色文字\033[0m"
# 绿色背景 + 黄色文字
echo -e "\033[42;33m绿色背景上的黄色文字\033[0m"
# 粗体 + 蓝色文字
echo -e "\033[1;34m粗体蓝色文字\033[0m"
# 下划线 + 亮红色文字
echo -e "\033[4;91m下划线亮红色文字\033[0m"
```

289
shell-脚本.md Normal file

@ -0,0 +1,289 @@
<h2><center>Shell 脚本</center></h2>
------
## 一:脚本规范
### 1. 风格规范
开头有“蛇棒”
所谓shebang其实就是在很多脚本的第一行出现的以”#!”开头的注释,他指明了当我们没有指定解释器的时候默认的解释器,一般可能是下面这样:
```shell
#bin/sh
```
除了 bash 之外,可以用下面的命令查看本机支持的解释器:
```bash
[root@wxin ~]# cat /etc/shells
/bin/sh
/bin/bash
/usr/bin/sh
/usr/bin/bash
```
- 直接使用`./a.sh`来执行这个脚本的时候,如果没有`shebang`,就会默认用`$Shell`指定的解释器,否则就会用`shebang`指定的解释器。
- 上面这种写法可能不太具备适应性,一般我们会用下面的方式来指定:
```shell
#!/usr/bin/env bash
```
### 2. 注释
注释的意义不仅在于解释用途,而在于告诉我们注意事项,就像是一个 README。
具体的来说对于Shell脚本注释一般包括下面几个部分
- shebang
- 脚本的参数
- 脚本的用途
- 脚本的注意事项
- 脚本的写作时间,作者,版权等
- 各个函数前的说明注释
- 一些较复杂的单行命令注释
### 3. 参数规范
这一点很重要,当脚本需要接受参数的时候,一定要先判断参数是否合乎规范,并给出合适的回显,方便使用者了解参数的使用。
至少得判断下参数的个数
```shell
if [[ $# != 2 ]];then
echo "Parameter incorrect."
exit 1
fi
```
### 4. 变量
一般情况下会将一些重要的环境变量定义在开头,确保这些变量的存在。
```shell
source /etc/profile
export PATH=”/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/apps/bin/”
```
- 这种定义方式有一个很常见的用途,最典型的应用就是,当本地安装了很多`java`版本时,可能需要指定一个`java`来用。这时就会在脚本开头重新定义`JAVA_HOME`以及`PATH`变量来进行控制。
- 一段好的代码通常是不会有很多硬编码在代码里的“魔数”的。如果一定要有,通常是用一个变量的形式定义在开头,然后调用的时候直接调用这个变量,这样方便日后的修改。
### 5. 缩进
- 正确的缩进非常重要,尤其是在写函数时,否则在阅读时很容易把函数体跟直接执行的命令搞混。
- 常见的缩进方法主要有`soft tab``hard tab`两种:
1. 所谓`soft tab`就是使用n个空格进行缩进(n通常是2或4)
2. 所谓`hard tab`当然就是指真实的`\t`字符
- 对于`if``for`语句之类的,最好不要把`then``do`这些关键字单独写一行,这样看上去比较丑。
### 6. 命名标准
所谓命名规范,基本包含下面这几点:
- 文件名规范,以`.sh`结尾,方便识别
- 变量名字要有含义,不要拼错
- 统一命名风格,写`Shell`一般用小写字母加下划线
### 7. 编码统一
在写脚本的时候尽量使用`UTF-8`编码能够支持中文等一些奇奇怪怪的字符。不过虽然能写中文但是在写注释以及打log的时候还是尽量英文毕竟很多机器还是没有直接支持中文的打出来可能会有乱码。
### 8. 日志和回显
- 日志的重要性不必多说,能够方便回头纠错,在大型的项目里是非常重要的。
- 如果这个脚本是供用户直接在命令行使用的,那么最好还要能够在执行时实时回显执行过程,方便用户掌控。
- 为了提高用户体验,会在回显中添加一些特效,比如颜色啊,闪烁啊之类的。
### 9. 密码移除
不要把密码硬编写在脚本里,尤其是当脚本托管在类似 Github 这类平台中时。
### 10. 分行
在调用某些程序的时候,参数可能会很长,这时候为了保证较好的阅读体验,我们可以用反斜杠(续行符)来分行:
```shell
./configure \
prefix=/usr \
sbin-path=/usr/sbin/nginx \
conf-path=/etc/nginx/nginx.conf
```
### 11. 代码有效率
在使用命令的时候要了解命令的具体做法,尤其当数据处理量大的时候,要时刻考虑该命令是否会影响效率。
比如下面的两个sed命令
```bash
[root@wxin ~]# sed -n '1p' file
[root@wxin ~]# sed -n '1p;1q' file
```
作用一样,都是获取文件的第一行。但是第一条命令会读取整个文件,而第二条命令只读取第一行。当文件很大的时候,仅仅是这样一条命令不一样就会造成巨大的效率差异。
当然,这里只是为了举一个例子,这个例子真正正确的用法应该是使用`head -n1 file`命令
**勤用双引号**
- 几乎所有的大佬都推荐在使用”$”来获取变量的时候最好加上双引号。
- 不加上双引号在很多情况下都会造成很大的麻烦。
```shell
#!/bin/sh
#已知当前文件夹有一个a.sh的文件
var="*.sh"
echo $var
echo "$var"
```
运行结果如下:
```shell
a.sh
*.sh
```
可以解释为它执行了下面的命令
```shell
echo *.sh
echo "*.sh"
```
在很多情况下,在将变量作为参数的时候,一定要注意上面这一点,仔细体会其中的差异。上面只是一个非常小的例子,实际应用的时候由于这个细节导致的问题实在是太多了。
### 12. 学会查路径
- 很多情况下,会先获取当前脚本的路径,然后以这个路径为基准,去找其他的路径。通常我们是直接用`pwd`以期获得脚本的路径。 不过其实这样是不严谨的,`pwd`获得的是当前`Shell`的执行路径,而不是当前脚本的执行路径。
正确的做法应该是下面这两种:
```shell
script_dir=$(cd $(dirname $0) && pwd)
script_dir=$(dirname $(readlink -f $0 ))
```
应当先`cd`进当前脚本的目录然后再`pwd`,或者直接读取当前脚本的所在路径。
### 13. 代码要简短
这里的简短不单单是指代码长度,而是只用到的命令数。原则上我们应当做到,能一条命令解决的问题绝不用两条命令解决。这不仅牵涉到代码的可读性,而且也关乎代码的执行效率。
最经典的例子如下:
```bash
[root@wxin ~]# cat /etc/passwd | grep root
[root@wxin ~]# grep root /etc/passwd
```
cat 命令最为人不齿的用法就是这样,用的没有任何意义,明明一条命令可以解决,非得加根管道
### 14. 使用新写法
- 尽量使用[[ ]]来代替[ ]
- 尽量使用\$()将命令的结果赋给变量,而不是反引号。
- 在复杂的场景下尽量使用printf代替echo进行回显
### 15. 其他小技巧
- 路径尽量保持绝对路径,不容易出错,如果非要用相对路径,最好用./修饰
- 优先使用bash的变量替换代替awk sed这样更加简短
- 简单的if尽量使用&& ||,写成单行。比如[[ x > 2]] && echo x
- 当export变量时尽量加上子脚本的namespace保证变量不冲突
- 会使用trap捕获信号并在接受到终止信号时执行一些收尾工作
- 使用mktemp生成临时文件或文件夹
- 利用/dev/null过滤不友好的输出信息
- 会利用命令的返回值判断命令的执行情况
- 使用文件前要判断文件是否存在,否则做好异常处理
- 不要处理ls后的数据(比如ls -l | awk '{ print $8 }')
- ls的结果非常不确定并且平台有关
- 读取文件时不要使用for loop而要使用while read 
## 二:脚本调试
Shell脚本的语法调试使用bash的相关参数进行调试
```shell
sh [参数] 文件名.sh
```
- -n 不要执行script仅查询语法的问题
- -v 在执行script之前先将script的内容输出到屏幕上
- **-x** 将使用的脚本的内容输出到屏幕,该参数经常被使用
```bash
#-v的示例
[root@wxin ~]# sh -v demo.sh
module () { eval `/usr/bin/modulecmd bash $*`
}
#!/bin/bash
case $1 in
"one")
echo "you input number is one"
;;
"two")
echo "you input number is twp"
;;
*)
echo "you input number is other"
;;
esac
you input number is other
```
```bash
#-x的示例
[root@wxin ~]# sh -x demo.sh
+ case $1 in
+ echo 'you input number is other'
you input number is other
[root@wxin ~]# sh -vx demo.sh
```
## 三:脚本运行方式
Linux中Shell脚本的执行通常有4种方式分别为工作目录执行绝对路径执行sh执行Shell环境执行。
### 1. 工作目录执行
工作目录执行,指的是执行脚本时,先进入到脚本所在的目录(此时,称为工作目录),然后使用 ./脚本方式执行
```bash
[root@wxin ~]# ./test.sh
Hello Shell
```
### 2. 绝对路径执行
```bash
[root@wxin ~]# /home/tan/scripts/test.sh
Hello Shell
```
### 3. bash 执行
```bash
#在子shell中执行,不需要脚本的可执行权限
[root@wxin ~]# sh test.sh
Hello Shell
[root@wxin ~]# bash test.sh
Hello Shell
```
### 4. 环境执行
Shell环境执行指的是在当前的Shell环境中执行可以使用 . 接脚本 或 source 接脚本。
不需要脚本的可执行权限在当前shell中执行
```bash
[root@wxin ~]# . test.sh
Hello Shell
[root@wxin ~]# source test.sh
Hello Shell
```