Shell语言编程(炼气)

1. Shell脚本执行方式

执行方式 应用及场景
通过sh或bash 书写脚本后,最常用的方式,在其他非红帽系统中,建议使用bash运行脚本
通过.点或source 加载/生效配置文件(环境变量,别名);常用:可以用来实现include功能,把其他脚本引入到当前脚本中
通过相对或绝对路径 一般不推荐使用这种,系统脚本/服务使用脚本(加上执行权限)
输入重定向符号 不推荐使用

2. 变量

2.1 变量类型

分类 说明
普通变量(局部变量) 在shell脚本中通过name=value的方式创建的变量
环境变量(全局变量) 一般是系统创建的,如PATH, PS1...
特殊变量 shell脚本命令,如:$?

2.1.1 普通变量

普通变量赋值、引用、删除

#!/bin/bash
# 赋值
name="John"
# 引用
echo "Hello, $name"
# 删除
unset name

设置只读变量

# 将name变量设置为只读,其值将不能被修改
readonly name

2.1.2 环境变量

环境变量相关命令

  • env
  • declare
  • export

常见的系统环境变量:

环境变量名称 含义 使用场景
HOME 指向当前用户的主目录的路径 用于脚本中引用用户主目录的路径
USER/LOGNAME 当前用户的用户名 用于获取当前脚本运行的用户的用户名
PATH 指定在哪些目录中查找可执行文件 用于添加自定义脚本或程序的路径,一遍shell能够直接执行这些脚本或程序
PWD 当前工作目录的路径 在脚本中获取当前工作目录的路径
SHELL 当前用户使用的shell的路径 确定当前shell的类型以及路径
IFS 输入字段分隔符 通常用于查找当前进程的父进程
PPID 父进程的进程ID 通常用于查找当前进程的父进程
RANDOM 一个随机数 常用于生成随机数或在脚本中引入一些随机性
$# 传递给脚本的参数个数 用于获取脚本中接收到的参数个数

环境变量相关文件和目录

文件或目录 说明 使用场景
/etc/profile 包含系统范围的全局shell配置,其中可以设置系统范围的环境变量
~/.bashrc 这是用户的Bash Shell的启动文件 ,通常用于设置用户级别的Shell配置和环境变量。

2.1.3 特殊变量

变量名称 说明 使用场景
$0 表示脚本本身的名称 常用于脚本中获取脚本的名称
$1,$2,... 表示接受到的参数,$1表示第一个参数,$2表示第二个参数,以此类推 用于处理脚本中的输入参数
$# 表示传递给脚本或函数的参数个数 常用于检查传递给脚本的参数数量是否符合期望
$*和$@ $*表示所有参数作为单个字符串,$@表示所有参数分别被引用 用于处理脚本中的所有参数
$? 表示上一个命令的退出状态或返回值 通常用于检查命令是否成功执行
$$ 表示当前shell进程的进程id 常用于创建临时文件名或进行进程控制
$! 表示后台运行的最后一个进程的进程id 常用于监控后台任务的执行状态

2.2 变量命名规则

  • 大部分编程语言通用规则:只能包含字母、数字、下划线,且不能以数字开头,区分大小写,不能与shell中的关键字如(if,then,else,for,while等)同名,使用大写字母表示常量或任何可能导出到环境中的变量(如:PI=3.14)
  • shell语言特有规则:变量名和等号之间不能有空格,必须是:name=value的形式,而不是name = value
  • 函数名由全小写单词和数字构成,单词间以下划线分隔。函数名之前必须要有关键词function,函数名之后必须有圆括号且中间没有空格。如:function show_time()

2.3 变量引用

引用方式 说明
变量前使用美元符号 示例:$var,适用于大部分情况下获取变量的值
变量前使用美元符号+大括号 示例:${var},用以明确变量的边界,适用于在变量名后面直接接非变量字符时,以明确变量名的结束
变量前使用``(反引号) 示例:`command`,用于执行命令,并将命令的输出赋值给变量
变量前使用$() 示例:$(command),与反引号类似,用于执行命令并将输出复制给变量,适用于执行命令并获取其输出的现代方式,优于反引号的使用
变量使用双引号"" 示例:"$var",用于保留变量的原始值,防止变量值中的空格或特殊字符被解释
变量使用单引号'' 示例:'$var',用于保持引号内文本的字面值,不对其中的变量进行替换
变量前使用@符号 示例:${array[@]},用于引用数组中的所有元素。适用于处理数组中的多个元素

3. 文档与注释

3.1 Here文档

Here文档(Here Document)是一种在Shell脚本中用来传递多行输入的方法。通过Here文档,您可以在Shell脚本中直接嵌入多行文本、命令或代码块,而无需使用临时文件或多行字符串拼接的方式。Here文档通常使用<<操作符来定义开始标识符和结束标识符。

Here文档的语法如下:

#!/bin/bash

command <<DELIMITER
    Text or commands
    to be passed
    as input
DELIMITER

Here文档的作用和用途包括:

  • 传递多行输入:Here文档允许您在Shell脚本中传递多行文本或命令,方便处理需要大段输入的情况。
  • 嵌入代码块:您可以将需要执行的代码块直接嵌入到脚本中,避免在脚本中大量使用echo或拼接字符串。
  • 生成配置文件:通过Here文档,您可以方便地生成配置文件或其他文本文件,将需要的内容直接嵌入到Shell脚本中。
  • 传递输入给命令:Here文档也可以用于将输入传递给需要交互式输入的命令或程序。
  • 注释:在Shell脚本中,您可以使用Here文档来暂时注释掉一段代码,实现类似多行注释的效果。

3.2 单行注释

shell中单行注释与python一样,以#开头

3.3 多行注释

3.2.1 使用here文档

<<COMMENT
This is a
multi-line comment block.
It can span across multiple lines.
COMMENT

注意:here文档中的标识符(即开始和结束标记)可以是用户自定义的任何合法标识符

3.2.2 使用空命令:

: '
This is a
multi-line comment block.
It can span across multiple lines.
'

4. 运算符

4.1 算术运算符(命令)

常见的几种算数运算方式(按推荐指数排序):

算术运算方式 说明
$(()) 最常用,语法简洁,执行效率高。
let 次常用,语法灵活,执行效率高。
expr 早期使用较多,语法繁琐,效率较低
bc 适用于浮点运算,语法复杂,效率较低
awk 适用于复杂计算,语法灵活,效率较低
4.1.1 通过$(())命令实现算术运算
result=$((5+3))
echo $result # 输出 8
4.1.2 通过let命令实现算术运算
let result=5+3
echo $result # 输出 8
4.1.3 通过expr命令实现算术运算
result=$(expr 5 + 3)
echo $result # 输出 8
4.1.4 通过bc命令实现浮点运算
a=5.5
b=3.2
result=$(echo "$a + $b" | bc)
echo $result # 输出 8.7
4.1.5 通过awk命令实现算术运算

awk 是一个强大的文本处理工具,但它也可以用来进行算术运算。awk 支持各种基本的算术运算符和一些高级的数学函数,如幂运算,平方根,三角函数运算等

a=5
b=3
result=$(awk "BEGIN {print $a + $b}")
echo $result # 输出 8

4.2 关系运算符

运算符 说明
-eq 等于(==),即:equal
-ne 不等于(!=),即:not equal
-gt 大于(>), 即:greater than
-lt 小于(<),即:less than
-ge 大于等于(>=),即:greater equal
-le 小于等于(<=),即:less equal

注意:关系运算符只支持数字,不支持字符串,除非字符串的值是数字。

4.3 逻辑运算符

旧版:

运算符 说明
非运算
-o 或运算(or)
-a 与运算(and)

新版:

运算符 说明
非运算
|| 或运算(or)
&& 与运算(and)

4.4 文件检测运算符

运算符 说明
-b file 检测文件是否是块设备
-c file 检测文件是否是字符设备文件
-d file 检测文件是否是目录
-f file 检测文件是否是普通文件(既不是目录,也不是块设备或字符设备文件)
-g file 检测文件是否设置了SGID位
-k file 检测文件是否设置了粘着位(Sticky Bit)
-p file 检测文件是否是有名管道
-d file 检测文件是否设置了SUID位
-r file 检测文件是否可读
-w file 检测文件是否可写
-x file 检测文件是否可执行
-s file 检测文件是否不为空(文件大小是否大于0)
-e file 检测文件(包括目录)是否存在
-S file 检测文件是否socket
-L file 检测文件是否存在并且是一个符号链接

4.5 自增和自减运算

  1. 使用$(())进行自增/自减运算
    num=0
    num=$((num + 1))
    num=$((num - 1))
    echo $num
    
  2. 使用(())进行自增/自减运算
    num=0
    ((num++))
    ((num--))
    echo $num
    
  3. 使用let命令进行自增/自减运算
    num=0
    let num++
    let num--
    echo $num
    
  4. 使用expr进行自增/自减运算
    num=0
    num=$(expr $num + 1)
    num=$(expr $num - 1)
    echo $num
    

5. Shell字符串处理

5.1 Shell子串用法总结

语法 说明
${string} 获取字符串变量值
${#string} 获取字符串长度
${string:start} 提取从位置start开始到结束的子字符串
${string:start:length} 提取从位置start开始提取长度为length的子字符串
${string#pattern} 删除字符串开头最短匹配 pattern 的部分
${string##pattern} 删除字符串开头最长匹配 pattern 的部分
${string%pattern} 删除字符串结尾最短匹配 pattern 的部分
${string%%pattern} 删除字符串结尾最长匹配 pattern 的部分
${string/pattern/replacement} 将字符串中符合pattern的第一个子串替换为replacement
${string//pattern/replacement} 将字符串中符合pattern的所有子串替换为replacement

5.2 字符串基本操作

5.2.1 字符串赋值
str="Hello, World!"
5.2.2 字符串拼接
greeting="Hello, "
name="Alice"
message=$greeting$name
5.2.3 字符串比较
str1="hello"
str2="world"

if [ $str1 = $str2 ]; then
    echo "Strings are equal"
else
    echo "Strings are not equal"
fi
5.2.4 字符串空值判断
str=""

if [ -z $str ]; then
    echo "String is empty"
fi
5.2.5 字符串提取
  • 提取从位置position开始到结束的子字符串
    语法:${string:position:length}
    str="Hello, World!"
    echo ${str:7}     # 输出: "World!"
    echo ${str:7:5}   # 输出: "World"
    
  • 提取从位置position开始提取长度为length的子字符串
    语法:${string:position:length}
    str="Hello, World!"
    echo ${str:7}     # 输出: "World!"
    echo ${str:7:5}   # 输出: "World"
    
5.2.6 字符串截取
  • 从字符串开头截取
    • 最短匹配
      语法:${string#pattern}
      str="Hello, World!"
      echo ${str#Hello,}    # 输出: " World!"
      
    • 最长匹配
      语法:${string##pattern}
      str="Hello, World!"
      echo ${str##H*o}      # 输出: "rld!"
      
  • 从字符串结尾截取
    • 最短匹配
      语法:${string%pattern}
      str="Hello, World!"
      echo ${str%World!}    # 输出: "Hello, "
      
    • 最长匹配
      语法:${string%%pattern}
      str="Hello, World!"
      echo ${str%%W*ld!}    # 输出: "Hello, "
      
5.2.7 字符串替换
str="Hello, World!"
5.2.8 获取字符串长度
str="Hello, World!"
echo ${#str}    # 输出字符串长度: 13

6. 数组

6.1 数组定义

6.1.1 直接定义
#!/bin/bash

array=(value1 value2 value3)
6.1.2 使用declare定义数组
# 情形1:声明的同时进行初始化
declare -a array=(value1 value2 value3)

# 情形2:先声明,再赋值
declare -a array
array[0]=value1
array[1]=value2

使用declare进行数组定义的好处:

  • 明确性:declare -a 明确指定了变量是一个数组,增强了代码的可读性和可维护性。其他开发者阅读代码时可以立即知道这是一个数组。
  • 类型检查:虽然 Shell 脚本本身是弱类型的,但 declare -a 可以防止意外地将数组变量用作其他类型的变量。例如,尝试将字符串赋值给一个已经声明为数组的变量会引发错误。
  • 初始化:declare -a 允许在声明时初始化数组,使得代码更加简洁和清晰。
  • 范围控制:在函数中使用 declare -a 可以创建局部数组变量,避免与全局变量冲突。

6.2 访问数组元素

declare -a array=(value1 value2 value3)

# 访问单个元素
echo ${array[0]}  # 输出 value1

# 访问所有元素
echo ${array[@]}  # 输出 value1 value2 value3

# 访问数组子集
echo ${array[@]:1:2}  # 输出 value2 value3

6.3 获取数组长度

declare -a array=(value1 value2 value3)

# 获取数组长度
echo ${#array[@]}  # 输出 3

# 获取数组中单个元素的长度
echo ${#array[0]}  # 输出 value1 的字符数

6.4 修改数组

6.4.1 添加元素
array+=(value4)  # 在数组末尾添加 value4
6.4.2 删除元素
unset array[0]  # 删除索引为 0 的元素
6.4.3 修改元素
???

6.5 遍历数组

6.5.1 使用for循环遍历
#!/bin/bash

declare -a array=(value1 value2 value3)

for element in "${array[@]}"; do
    echo $element
done
6.5.2 使用索引遍历
for i in "${!array[@]}"; do
    echo "Index: $i, Value: ${array[$i]}"
done
6.6 多维数组

Shell本身并不支持多维数组,但是可以通过模拟来实现

# 模拟二维数组
array[0]="row1_col1 row1_col2"
array[1]="row2_col1 row2_col2"

# 访问元素
row1=(${array[0]})
echo ${row1[0]}  # 输出 row1_col1

6.7 字符串分割成数组

IFS=' ' read -r -a array <<< "value1 value2 value3"
echo ${array[0]}  # 输出 value1

7. 流程控制

7.1 分支

7.1.1 if分支

基本语法:

if [ condition ]; then
    # 执行的命令
elif [ another_condition ]; then
    # 另一种情况下的命令
else
    # 其他情况下的命令
fi

示例:

age=20
if [ $age -lt 18 ]; then
    echo "未成年"
elif [ $age -ge 18 ] && [ $age -lt 60 ]; then
    echo "成年"
else
    echo "老年"
fi

7.1.2 case分支

基本语法:

case $variable in
    pattern1)
        # 执行的命令
        ;;
    pattern2)
        # 执行的命令
        ;;
    *)
        # 默认情况下的命令
        ;;
esac

示例:

choice="apple"
case $choice in
    apple)
        echo "选择了苹果"
        ;;
    banana)
        echo "选择了香蕉"
        ;;
    *)
        echo "选择了其他水果"
        ;;
esac

7.2 循环

7.2.1 for循环

基本语法:

for variable in list; do
    # 执行的命令
done

示例:

# 一般遍历方式
for i in 1 2 3 4 5; do
    echo "数字: $i"
done

# C语言风格
for ((i=0; i<5; i++)); do
    echo "数字: $i"
done
7.2.2 while循环

基本语法:

while [ condition ]; do
    # 执行的命令
done

示例:

count=1
while [ $count -le 5 ]; do
    echo "计数: $count"
    ((count++))
done
7.2.3 until循环

基本语法:

until [ condition ]; do
    # 执行的命令
done

示例:

count=1
until [ $count -gt 5 ]; do
    echo "计数: $count"
    ((count++))
done

8. 函数

8.1 函数定义

  • 使用function关键字
    function my_function {
      # 函数体
    }
    
  • 直接使用函数名
    my_function() {
      # 函数体
    }
    

8.2 函数调用

# 直接通过函数名调用
my_function

8.3 参数传递

# $1, $2, $3等表示传递给函数第n个参数,$# 表示传递给函数的参数个数,$@ 和 $* 分别表示所有参数的列表
my_function() {
    echo "First argument: $1"
    echo "Second argument: $2"
    echo "Number of arguments: $#"
    echo "All arguments: $@"
}

my_function arg1 arg2 arg3

8.4 返回值

函数可以通过 return 语句返回一个整数值,通常用于表示函数的执行状态。返回值范围是 0 到 255,其中 0 通常表示成功,非零值表示失败

my_function() {
    echo "This is a function"
    return 0
}

my_function
echo "Return value: $?"

8.5 局部变量

在函数内部,可以使用 local 或 declare 关键字来定义局部变量,这些变量的作用域仅限于函数内部。

my_function() {
    local local_var="I am local"
    echo "Inside function: $local_var"
}

my_function
echo "Outside function: ${local_var:-'Not defined'}"

8.6 全局变量

如果在函数内部定义的变量没有使用 local 或 declare 关键字,那么这些变量将是全局变量。

my_function() {
    global_var="I am global"
}

my_function
echo "Global variable: $global_var"

8.7 函数嵌套

# 支持但不推荐
outer_function() {
    inner_function() {
        echo "Inner function"
    }
    inner_function
}

outer_function

8.8 默认参数

  • ${parameter:-word}: 如果 parameter 未设置或为空,则使用 word 的值作为默认值。
  • ${parameter:=word}: 如果 parameter 未设置或为空,则将 word 赋值给 parameter 并使用其值。

8.9 参数替换

  • ${parameter:+word}: 如果 parameter 已设置且非空,则使用 word 的值;否则,结果为空字符串。
  • ${parameter:?word}: 如果 parameter 未设置或为空,则输出错误信息 word 并退出脚本。

9. 重定向

大多数 UNIX 系统命令从你的终端接受输入并将所产生的输出发送回​​到您的终端。一个命令通常从一个叫标准输入的地方读取输入,默认情况下,这恰好是你的终端。同样,一个命令通常将其输出写入到标准输出,默认情况下,这也是你的终端。
标准文件描述符:

  • 标准输入 (stdin):文件描述符 0,默认从键盘输入。
  • 标准输出 (stdout):文件描述符 1,默认输出到终端。
  • 标准错误 (stderr):文件描述符 2,默认输出到终端。

9.1 输出重定向

  1. 以覆盖方式重定向标准输出到文件
# 将 command 的标准输出重定向到 file。如果 file 不存在,则创建它;如果存在,则覆盖它
command > file
  1. 以追加方式重定向标准输出到文件
# 将 command 的标准输出追加到 file。如果 file 不存在,则创建它
command >> file
  1. 以覆盖方式重定向标准错误到文件
# 将 command 的标准错误重定向到 file。如果 file 不存在,则创建它;如果存在,则覆盖它
command 2> file
  1. 以追加方式重定向标准错误到文件
# 将 command 的标准错误追加到 file。如果 file 不存在,则创建它;如果存在,则覆盖它
command 2>> file
  1. 同时重定向标准输出和标准错误到同一个文件
command &> file
或
command > file 2>&1

9.2 输入重定向

  1. 从文件读取标准输入
# 将 file 的内容作为 command 的标准输入
command < file
  1. 从单行文本读取标准输入(Here String)
command <<< "string"
  1. 从多行文本读取标准输入
command << EOF
line1
line2
...
EOF
posted @ 2024-09-23 19:56  berlin-fly  阅读(3)  评论(0编辑  收藏  举报