shell学习从入门到精通

发布于:2025-07-13 ⋅ 阅读:(19) ⋅ 点赞:(0)

bShell 语法学习:从入门到精通

Shell 是一个命令解释器,它充当用户与操作系统内核之间的接口。Shell 脚本(Shell Script)则是由一系列 Shell 命令组成的文本文件,用于自动化执行任务。最常用的 Shell 是 Bash (Bourne-Again SHell),本指南将主要以 Bash 为例。

第一部分:入门基础

1. "Hello World" - 你的第一个脚本

学习任何语言的第一步都是 "Hello World"。

  • 脚本内容 (hello.sh):

    #!/bin/bash
    
    # 这是一个注释。井号(#)后面的内容会被忽略。
    echo "Hello, World!"
    
  • 关键点解释:

    • #!/bin/bash: 这叫做 "Shebang"。它告诉系统这个脚本应该使用哪个解释器来执行,这里是 /bin/bash

    • #: 这是注释符号。

    • echo: 这是最常用的命令之一,用于在终端输出文本或变量内容。

  • 如何执行脚本:

    • 方法一:作为 bash 的参数

      bash hello.sh
      
    • 方法二:赋予执行权限后直接运行(推荐)

      1. 添加执行权限: chmod +x hello.sh

      2. 执行脚本: ./hello.sh ( ./ 表示当前目录)

2. 变量 (Variables)

变量用于存储数据。Bash 中的变量不需要事先声明类型。

  • 定义和使用:

    • 规则:变量名、等号和值之间 不能有空格

    • 引用:使用 $ 符号。推荐用双引号 "" 包裹变量,以避免空格等特殊字符引起的问题。

    #!/bin/bash
    
    # 定义变量
    NAME="Alice"
    AGE=30
    
    # 使用变量
    echo "My name is $NAME, and I am $AGE years old."
    
    # 推荐使用花括号{}来明确变量边界
    echo "This is ${NAME}'s book." 
    
  • 只读变量:

    CITY="New York"
    readonly CITY
    # 下面这行会报错: CITY: readonly variable
    # CITY="London" 
    
  • 接收用户输入 (read):

    #!/bin/bash
    
    echo "What is your name?"
    read USER_NAME
    echo "Hello, $USER_NAME!"
    
    # -p 可以在同一行显示提示信息
    read -p "What is your favorite color? " COLOR
    echo "I see, your favorite color is $COLOR."
    
2.1. 变量引用
  • 语法:$变量名 或 ${变量名}(推荐后者,避免歧义)
#!/bin/bash

name="Alice"
echo "Name: $name"  # 输出:Name: Alice
echo "Name: ${name}"  # 输出:Name: Alice(推荐)

# 变量拼接(无需连接符)
greeting="Hello, ${name}!"
echo $greeting  # 输出:Hello, Alice!

# 变量作为命令参数
file="test.txt"
cat ${file}  # 等价于 cat test.txt
2.2. 特殊变量(预定义变量)

Shell 有一系列内置变量,用于获取脚本运行时的信息:

变量 含义 示例(执行 ./script.sh arg1 arg2
$0 脚本文件名 echo $0 → ./script.sh
$n 第 n 个参数(n≥1) echo $1 → arg1echo $2 → arg2
$# 参数总数 echo $# → 2
$* 所有参数(作为单个字符串) echo $* → arg1 arg2
$@ 所有参数(作为独立字符串) for i in $@; do echo $i; done → 分别输出 arg1、arg2
$? 上一条命令的退出状态(0 成功,非 0 失败) ls non_exist; echo $? → 2(失败)
$$ 当前脚本的进程 ID(PID) echo $$ → 12345(当前进程 ID)
$! 上一个后台进程的 PID sleep 10 &; echo $! → 后台进程的 PID
#!/bin/bash
# 保存为 args.sh,执行:./args.sh a b c

echo "脚本名:$0"  # 输出:脚本名:./args.sh
echo "第1个参数:$1"  # 输出:第1个参数:a
echo "参数总数:$#"  # 输出:参数总数:3
echo "所有参数(\$*):$*"  # 输出:所有参数($*):a b c

# 遍历 $@(每个参数独立处理)
echo "遍历 \$@:"
for arg in "$@"; do
  echo "- $arg"
done
# 输出:
# 遍历 $@:
# - a
# - b
# - c
2.3. 环境变量

环境变量是全局变量,用于配置系统环境(如 PATHHOME),可通过 export 声明。

#!/bin/bash

# 查看环境变量(已存在的)
echo "HOME: $HOME"  # 输出:HOME: /home/user(当前用户家目录)
echo "PATH: $PATH"  # 输出:系统命令搜索路径

# 定义并导出环境变量(子进程可访问)
export MY_VAR="hello"
# 在当前终端执行脚本后,可通过 echo $MY_VAR 查看(仅当前终端有效)

# 临时设置环境变量(仅当前命令有效)
MY_VAR="test" echo $MY_VAR  # 输出:test
3. 命令替换

将一个命令的输出结果赋值给一个变量。

  • 语法: $(command) (推荐) 或 `command` (旧式)

    #!/bin/bash
    
    CURRENT_DATE=$(date "+%Y-%m-%d %H:%M:%S")
    FILES_IN_DIR=$(ls -l)
    
    echo "Current time is: $CURRENT_DATE"
    echo "---"
    echo "Files in the current directory:"
    echo "$FILES_IN_DIR"
    

4. 数据类型与扩展

Shell 没有严格的数据类型,但支持字符串、数字、数组、关联数组(字典)。

1. 字符串

Shell 字符串可用单引号 '' 或双引号 "",区别:

  • 单引号:不解析变量,不转义特殊字符(除单引号本身)。
  • 双引号:解析变量,可转义特殊字符(如 \n\t)。
#!/bin/bash

name="Alice"

# 单引号(变量不解析)
echo 'Hello, $name'  # 输出:Hello, $name

# 双引号(变量解析)
echo "Hello, $name"  # 输出:Hello, Alice

# 转义字符(需双引号)
echo "Line1\nLine2"  # 输出:Line1\nLine2(默认不转义)
echo -e "Line1\nLine2"  # 加 -e 启用转义,输出:
# Line1
# Line2

# 字符串长度
str="hello"
echo ${#str}  # 输出:5

# 字符串截取(${变量:起始位置:长度},起始位置从0开始)
echo ${str:1:3}  # 从索引1开始,取3个字符 → ell

5. 运算符与表达式

Shell 支持算术运算、字符串运算、逻辑运算,需用特定语法(如 $((...)))。

1. 算术运算符

语法:$((表达式)) 或 $[表达式](推荐前者),支持 +-*/%(取余)、**(幂)。

#!/bin/bash

a=10
b=3

# 加法
echo $((a + b))  # 输出:13

# 乘法(注意:* 无需转义,在 $((...)) 中直接用)
echo $((a * b))  # 输出:30

# 取余
echo $((a % b))  # 输出:1

# 幂运算(Bash 4.0+ 支持)
echo $((2 **3))  # 输出:8

# 赋值运算(在表达式中修改变量)
$((a += 5))  # 等价于 a = a + 5
echo $a  # 输出:15
2. 比较运算符(数字)

用于条件判断(if 语句中),语法:[ 数字1 运算符 数字2 ] 或 (( 数字1 运算符 数字2 ))

运算符 含义 示例(a=10, b=5)
-eq 等于 [ $a -eq $b ] → false
-ne 不等于 [ $a -ne $b ] → true
-gt 大于 [ $a -gt $b ] → true
-lt 小于 [ $a -lt $b ] → false
-ge 大于等于 (( a >= b )) → true
-le 小于等于 (( a <= b )) → false
#!/bin/bash

a=10
b=5

if [ $a -gt $b ]; then
  echo "$a 大于 $b"
fi
# 输出:10 大于 5

# 用 ((...)) 更简洁(支持 >、< 等符号)
if (( a > b )); then
  echo "$a 大于 $b"
fi
# 输出:10 大于 5
3. 字符串运算符

用于字符串比较,语法:[ 字符串1 运算符 字符串2 ] 或 [[ 字符串1 运算符 字符串2 ]]

运算符 含义 示例(s1="abc", s2="abd")
= 等于(== 等价) [ "$s1" = "$s2" ] → false
!= 不等于 [ "$s1" != "$s2" ] → true
-z 字符串长度为 0 [ -z "$s1" ] → false
-n 字符串长度不为 0 [ -n "$s1" ] → true
< 字典序小于(需 [[]]) [[ "$s1" < "$s2" ]] → true(abc < abd)
> 字典序大于(需 [[]]) [[ "$s1" > "$s2" ]] → false
#!/bin/bash

s1="abc"
s2="abd"

# 字符串是否相等
if [ "$s1" = "$s2" ]; then
  echo "相等"
else
  echo "不相等"
fi
# 输出:不相等

# 字符串长度是否不为0
if [ -n "$s1" ]; then
  echo "$s1 长度不为0"
fi
# 输出:abc 长度不为0

# 字典序比较(需用 [[ ]])
if [[ "$s1" < "$s2" ]]; then
  echo "$s1 小于 $s2"
fi
# 输出:abc 小于 abd
4. 逻辑运算符

用于组合条件,支持与(-a 或 &&)、或(-o 或 ||)、非(!)。

#!/bin/bash

a=10
b=5
c=15

# 与运算(两个条件都成立)
if [ $a -gt $b ] && [ $a -lt $c ]; then
  echo "$a 大于 $b 且小于 $c"
fi
# 输出:10 大于 5 且小于 15

# 或运算(至少一个条件成立)
if [ $a -gt $c ] || [ $a -gt $b ]; then
  echo "$a 大于 $c 或大于 $b"
fi
# 输出:10 大于 15 或大于 5

# 非运算(条件取反)
if ! [ $a -eq $b ]; then
  echo "$a 不等于 $b"
fi
# 输出:10 不等于 5

第二部分:核心语法

1. 条件判断 (if-elif-else)

让脚本根据不同条件执行不同操作。

  • 基本结构:

    if [ condition ]; then
        # command to execute if condition is true
    elif [ another_condition ]; then
        # command to execute if another_condition is true
    else
        # command to execute if all conditions are false
    fi
    
  • 关键点:

    • if[ 之间必须有空格。

    • [condition 之间以及 condition] 之间也必须有空格。

    • then 必须单独一行,或者用分号 ;if 语句放在同一行:if [ condition ]; then ...

    • 推荐使用 [[ ... ]],它更强大且不易出错,尤其是在处理字符串时。

  • #!/bin/bash
    
    read -p "Enter a number: " NUM
    
    if [[ $NUM -gt 100 ]]; then
        echo "$NUM is greater than 100."
    elif [[ $NUM -eq 100 ]]; then
        echo "$NUM is exactly 100."
    else
        echo "$NUM is less than 100."
    fi
    
  • 字符串比较:

    操作符

    描述

    ==

    等于

    !=

    不等于

    -z

    字符串为空

    -n

    字符串不为空

    #!/bin/bash
    
    read -p "Enter 'yes' or 'no': " ANSWER
    
    if [[ $ANSWER == "yes" ]]; then
        echo "You chose 'yes'."
    elif [[ $ANSWER != "no" ]]; then
        echo "Invalid input."
    else
        echo "You chose 'no'."
    fi
    
  • 文件系统判断:

    操作符

    描述

    -f

    文件存在且是常规文件

    -d

    文件存在且是目录

    -e

    文件存在(不区分类型)

    -r

    文件可读

    -w

    文件可写

    -x

    文件可执行

    #!/bin/bash
    
    FILE="./hello.sh"
    
    if [[ -f "$FILE" ]]; then
        echo "$FILE is a regular file."
        if [[ -x "$FILE" ]]; then
            echo "And it is executable."
        fi
    elif [[ -d "$FILE" ]]; then
        echo "$FILE is a directory."
    else
        echo "$FILE does not exist or is not a regular file/directory."
    fi
    
2. case 语句

当有多个分支选择时,case 是比 if-elif-else 更清晰的替代方案。

#!/bin/bash

read -p "Enter a character (a, b, or c): " CHAR

case $CHAR in
    a|A)
        echo "You entered 'a'."
        ;;
    b|B)
        echo "You entered 'b'."
        ;;
    c|C)
        echo "You entered 'c'."
        ;;
    *)
        echo "Invalid character."
        ;;
esac
  • 关键点:

    • | 用于匹配多个模式。

    • *) 是一个通配符,匹配任何其他输入。

    • 每个分支以 ;; 结束。

    • 整个 case 语句以 esac (case反写) 结束。

3. 循环结构
  • for 循环:

    • 遍历列表:

      #!/bin/bash
      
      for FRUIT in apple banana orange; do
          echo "I like $FRUIT."
      done
      
    • 遍历数字序列:

      #!/bin/bash
      
      echo "Counting from 1 to 5:"
      for i in {1..5}; do
          echo $i
      done
      
    • C 语言风格的 for 循环:

      #!/bin/bash
      
      for (( i=0; i<5; i++ )); do
          echo "C-style loop, iteration: $i"
      done
      
  • while 循环: 当条件为真时持续循环。常用于逐行读取文件。

    #!/bin/bash
    
    COUNTER=0
    while [[ $COUNTER -lt 5 ]]; do
        echo "Counter is $COUNTER"
        # 必须有改变条件的语句,否则会死循环
        let COUNTER++ 
    done
    

    逐行读取文件(重要用法):

    #!/bin/bash
    
    FILENAME="hello.sh"
    while IFS= read -r line; do
        echo "Line: $line"
    done < "$FILENAME"
    
    • IFS= read -r line 是读取文件的标准、安全的方式,可以防止 read 命令意外地处理反斜杠和行首行尾的空白字符。

  • until 循环: 当条件为假时持续循环,直到条件为真。

    #!/bin/bash
    
    COUNTER=0
    until [[ $COUNTER -ge 5 ]]; do
        echo "Counter is $COUNTER"
        let COUNTER++
    done
    
4. 函数 (Functions)

将代码块封装成函数,方便复用。

  • 定义和调用:

    #!/bin/bash
    
    # 定义函数
    greet() {
        echo "Hello there!"
    }
    
    # 调用函数
    echo "Calling the function..."
    greet
    echo "Function called."
    
  • 传递参数:

    • 在函数内部,$1, $2, $3, ... 分别代表第一个、第二个、第三个参数。

    • $@ 代表所有参数的列表。

    • $# 代表传递给函数的参数个数。

    #!/bin/bash
    
    print_info() {
        if [[ $# -eq 0 ]]; then
            echo "Usage: print_info <name> <age>"
            return 1 # 返回一个非零值表示错误
        fi
    
        echo "Name: $1"
        echo "Age: $2"
        echo "All arguments: $@"
    }
    
    print_info "Bob" 42
    print_info # 测试错误处理
    
  • 返回值:

    • Shell 函数的 return 语句只返回一个 0-255 的整数,称为 退出状态码0 通常表示成功,非 0 表示失败。

    • 要 "返回" 数据,通常是在函数中用 echo 输出,然后在调用处用命令替换 $(...) 来捕获输出。

      • local 关键字使变量的作用域仅限于函数内部,这是一个好习惯。

    #!/bin/bash
    
    get_full_name() {
        local first_name=$1
        local last_name=$2
        # 使用 echo "返回" 结果
        echo "$first_name $last_name"
    }
    
    # 使用命令替换捕获函数的输出
    FULL_NAME=$(get_full_name "John" "Doe")
    echo "The full name is: $FULL_NAME"
    

第三部分:高级用法

1. 数组 (Arrays)
  • 索引数组 (Indexed Arrays):

    #!/bin/bash
    
    # 定义数组
    fruits=("Apple" "Banana" "Cherry")
    
    # 访问元素(索引从0开始)
    echo "First fruit: ${fruits[0]}"
    
    # 访问所有元素
    echo "All fruits: ${fruits[@]}"
    
    # 获取数组长度
    echo "Number of fruits: ${#fruits[@]}"
    
    # 添加元素
    fruits+=("Orange")
    
    # 遍历数组
    for fruit in "${fruits[@]}"; do
        echo "Processing $fruit"
    done
    
  • 关联数组 (Associative Arrays / Hashes): 键值对数组(需要 Bash 4.0+)。

    #!/bin/bash
    
    # 声明一个关联数组
    declare -A user
    
    # 赋值
    user["name"]="Alice"
    user["id"]="101"
    user["email"]="alice@example.com"
    
    # 访问元素
    echo "User Name: ${user[name]}"
    
    # 遍历所有的键
    echo "All keys: ${!user[@]}"
    
    # 遍历所有的值
    echo "All values: ${user[@]}"
    
    # 遍历键值对
    for key in "${!user[@]}"; do
        echo "$key: ${user[$key]}"
    done
    

2. 字符串处理

操作 示例 结果
长度 ${#str} 字符串长度
截取 ${str:2:3} 从索引 2 开始取 3 个字符
替换 ${str/old/new} 替换第一个 old 为 new
全替换 ${str//old/new} 替换所有 old 为 new
删除前缀 ${str#prefix} 删除最短前缀
删除后缀 ${str%suffix} 删除最短后缀

示例

#!/bin/bash
str="hello world"
echo "长度:${#str}"  # 输出:11
echo "截取:${str:3:4}"  # 输出:lo w
echo "替换:${str/world/shell}"  # 输出:hello shell
3. 精细数学计算

Shell 内置的 $((...)) 只支持整数运算。对于浮点数或更复杂的计算,需要借助外部工具。

  • 整数计算:

    #!/bin/bash
    
    A=10
    B=3
    
    SUM=$((A + B))
    PRODUCT=$((A * B))
    REMAINDER=$((A % B)) # 取余
    
    echo "Sum: $SUM, Product: $PRODUCT, Remainder: $REMAINDER"
    
  • 浮点数计算 (bc): bc 是一个强大的计算器,-l 参数可以加载数学库,支持高精度计算。

    #!/bin/bash
    
    # 将表达式通过管道传给 bc
    RESULT=$(echo "scale=4; 10 / 3" | bc)
    echo "10 / 3 = $RESULT"
    
    # 更复杂的计算
    PI=$(echo "scale=10; 4*a(1)" | bc -l) # a() 是反正切函数,4*a(1)是计算pi的经典方法
    echo "Pi ≈ $PI"
    
    VAR1=5.5
    VAR2=2.2
    SUM=$(echo "$VAR1 + $VAR2" | bc)
    echo "$VAR1 + $VAR2 = $SUM"
    
  • 使用 awk 计算: awk 也是一个处理文本和进行计算的强大工具。

    #!/bin/bash
    
    RESULT=$(awk "BEGIN {printf \"%.4f\", 10/3}")
    echo "10 / 3 = $RESULT"
    
4. 颜色和格式化输出

通过 ANSI 转义序列设置输出颜色,格式:\033[颜色代码m文本\033[0m0m 重置颜色)

常用颜色代码:

  • 文本色:30(黑)、31(红)、32(绿)、33(黄)、34(蓝)、35(紫)、36(青)、37(白)
  • 背景色:40(黑)、41(红)、42(绿)等

    使用 ANSI escape codes 来控制终端输出的颜色和样式。

    • 语法: \e[...m\033[...m

    • 示例:

      #!/bin/bash
      
      # 为了可读性和复用,最好将颜色代码定义为变量
      COLOR_RESET='\e[0m'
      COLOR_RED='\e[31m'
      COLOR_GREEN='\e[32m'
      COLOR_YELLOW='\e[33m'
      BG_BLUE='\e[44m'
      STYLE_BOLD='\e[1m'
      
      echo -e "${COLOR_RED}This is red text.${COLOR_RESET}"
      echo -e "${COLOR_GREEN}This is green text.${COLOR_RESET}"
      echo -e "${STYLE_BOLD}${COLOR_YELLOW}This is bold yellow text.${COLOR_RESET}"
      echo -e "${BG_BLUE}This text has a blue background.${COLOR_RESET}"
      
      # 组合使用
      echo -e "${STYLE_BOLD}${COLOR_RED}${BG_BLUE}DANGER! Critical Error!${COLOR_RESET}"
      
      • echo -e 是必须的,它让 echo 能够解释转义序列。

    5. 强大的命令执行与控制
    • 命令分组:

      • ( ... ): 在一个 子 Shell 中执行命令组。子 Shell 中的变量和目录改变不会影响父 Shell。

      • { ...; }: 在 当前 Shell 中执行命令组。注意 } 前必须有分号或换行。

      # 子Shell示例
      echo "Before: PWD=$PWD"
      (cd /tmp; echo "Inside subshell: PWD=$PWD")
      echo "After: PWD=$PWD" # PWD 没变
      
      # 当前Shell示例
      echo "Before: PWD=$PWD"
      { cd /var; echo "Inside group: PWD=$PWD"; }
      echo "After: PWD=$PWD" # PWD 变了
      
    • 输入/输出重定向:

      • >: 重定向标准输出(会覆盖文件内容)。 ls > file.txt

      • >>: 重定向标准输出(追加到文件末尾)。date >> file.txt

      • <: 重定向标准输入。 read -r line < file.txt

      • 2>: 重定向标准错误。command_that_fails 2> error.log

      • &>: 重定向标准输出和标准错误。command &> all_output.log

      • /dev/null: 一个特殊的设备文件,所有写入它的数据都会被丢弃("黑洞")。常用于丢弃不想要的输出。command > /dev/null 2>&1

    • 管道 (|): 将前一个命令的标准输出作为后一个命令的标准输入。这是 Shell 的精髓之一。

      # 统计当前目录有多少个 .sh 文件
      ls -l | grep ".sh$" | wc -l
      
    • 进程替换 (<(command)): 这是一个非常高级的特性,它将一个命令的输出伪装成一个文件,然后可以被另一个需要文件作为输入的命令使用。

      # 比较两个目录下的文件列表,而无需创建临时文件
      diff <(ls /bin) <(ls /usr/bin)
      
    • Here 文档(<<

            向命令输入多行文本(无需手动输入),语法

    命令 << 分界符
      多行文本
    分界符
    

    #!/bin/bash
    
    # 向文件写入多行内容
    cat << EOF > info.txt
    Name: Alice
    Age: 25
    City: Beijing
    EOF
    # info.txt 内容为上述三行
    
    # 作为函数输入
    count_lines() {
      wc -l
    }
    
    count_lines << EOF
    line1
    line2
    line3
    EOF
    # 输出:3(行数)

    第四部分:编写健壮的脚本

    1. set 命令

    在脚本开头使用 set 命令可以使其更安全、更健壮。

    #!/bin/bash
    set -euo pipefail
    
    • set -e: 脚本中任何命令失败(返回非零退出状态码)时,立即退出。

    • set -u: 尝试使用未定义的变量时,立即退出。

    • set -o pipefail: 在管道中,只要有任何一个命令失败,整个管道的退出状态码就是失败的。

    2. 解析脚本选项 (getopts)

    用于解析传递给脚本的命令行选项(如 -f, -v)。

    #!/bin/bash
    set -euo pipefail
    
    VERBOSE=false
    FILENAME=""
    
    # f: 表示-f选项需要一个参数
    # v 表示-v选项不需要参数
    while getopts 'vf:' OPTION; do
      case "$OPTION" in
        v)
          VERBOSE=true
          ;;
        f)
          FILENAME="$OPTARG"
          ;;
        ?)
          echo "Usage: $(basename $0) [-v] [-f filename]"
          exit 1
          ;;
      esac
    done
    
    if [[ $VERBOSE == true ]]; then
        echo "Verbose mode is ON."
    fi
    
    if [[ -n "$FILENAME" ]]; then
        echo "Processing file: $FILENAME"
    else
        echo "No filename provided."
    fi
    
    • 执行示例:

      • ./myscript.sh -v -f data.txt

      • ./myscript.sh -f report.csv

    3. 信号陷阱 (trap)

    允许你在脚本接收到特定信号(如 Ctrl+C)时执行一段代码,常用于清理临时文件。

    #!/bin/bash
    set -euo pipefail
    
    # 创建一个临时文件
    TMP_FILE=$(mktemp)
    echo "Created temporary file: $TMP_FILE"
    
    # 定义清理函数
    cleanup() {
        echo "Caught signal! Cleaning up..."
        rm -f "$TMP_FILE"
        echo "Cleanup finished."
        exit 1
    }
    
    # 设置陷阱:当接收到 INT(Ctrl+C) 或 TERM 信号时,执行 cleanup 函数
    trap cleanup INT TERM
    
    # 主逻辑
    echo "Script is running, press Ctrl+C to test the trap."
    sleep 60 # 模拟长时间运行的任务
    echo "Script finished normally."
    
    # 正常退出前也要清理
    rm -f "$TMP_FILE"
    

      捕获系统信号(如 Ctrl+C 发送的 SIGINT),执行自定义操作。

    #!/bin/bash
    
    # 捕获 SIGINT 信号(Ctrl+C)
    trap 'echo " 不要按 Ctrl+C!"; exit 1' SIGINT
    
    echo "运行中(按 Ctrl+C 测试)..."
    while true; do
      sleep 1
    done

    4. 正则表达式与文本处理

    Shell 结合 grep(搜索)、sed(编辑)、awk(分析)可强大处理文本。

    1. grep:文本搜索
    • 语法:grep [选项] 模式 文件
    • 常用选项:-i(忽略大小写)、-v(反向匹配)、-n(显示行号)、-E(扩展正则)。
    #!/bin/bash
    
    # 在文件中搜索包含 "error" 的行(区分大小写)
    grep "error" log.txt
    
    # 忽略大小写搜索,显示行号
    grep -in "warning" log.txt
    
    # 反向匹配(不包含 "debug" 的行)
    grep -v "debug" log.txt
    
    # 扩展正则(-E),匹配 "apple" 或 "banana"
    grep -E "apple|banana" fruits.txt
    
    2. sed:文本替换与编辑
    • 语法:sed [选项] '命令' 文件
    • 常用命令:s/原字符串/新字符串/(替换,默认替换每行第一个匹配)、s/.../.../g(全局替换)。
    #!/bin/bash
    
    # 替换文件中 "old" 为 "new"(仅输出,不修改原文件)
    sed 's/old/new/' text.txt
    
    # 全局替换并修改原文件(-i 选项,备份用 -i.bak)
    sed -i 's/hello/HELLO/g' greet.txt  # 所有 hello 替换为 HELLO
    
    # 删除空行(d 命令删除匹配行)
    sed '/^$/d' input.txt  # ^$ 匹配空行
    
    3. awk:文本分析与处理

    擅长按列处理文本(默认空格分隔),语法:awk '模式 {动作}' 文件

    #!/bin/bash
    
    # 打印文件第2列和第4列
    awk '{print $2, $4}' data.txt
    
    # 按条件过滤(第3列数值 > 100 的行)
    awk '$3 > 100 {print $0}' data.txt  # $0 表示整行
    
    # 自定义分隔符(-F 选项),按逗号分隔,打印第1列
    awk -F ',' '{print $1}' csvfile.txt

    5. 进程管理

    Shell 可启动、查看、终止进程。

    1. 后台运行与 jobs
    • &:在命令后加 & 使其后台运行。
    • jobs:查看当前终端的后台进程。
    • fg %n:将第 n 个后台进程调回前台。
    • bg %n:将暂停的后台进程继续运行。
    #!/bin/bash
    
    # 后台运行长时间任务(输出重定向到文件)
    sleep 30 > sleep.log 2>&1 &
    echo "后台进程 ID:$!"  # $! 是后台进程 PID
    
    # 查看后台进程
    jobs
    
    # 终止进程(kill)
    pid=$!
    kill $pid  # 发送 SIGTERM 信号
    # kill -9 $pid  # 强制终止(SIGKILL,无法捕获)
    
    2. 进程替换(<(命令)>(命令)

    将命令输出作为临时文件,用于需要文件参数的命令。

    #!/bin/bash
    
    # 比较两个命令的输出(无需临时文件)
    diff <(ls dir1) <(ls dir2)  # 比较 dir1 和 dir2 的文件列表
    
    # 将输出同时发送到终端和文件(tee 命令)
    ls -l | tee >(grep ".sh" > sh_files.txt)  # 筛选 .sh 文件到 sh_files.txt,同时显示所有
    6. 调试脚本
    • bash -n 脚本:检查语法错误(不执行)。
    • bash -x 脚本:执行并输出每一行命令(调试细节)。
    • 在脚本中用 set -x(开启调试)、set +x(关闭)。
    #!/bin/bash
    
    set -x  # 开启调试
    a=5
    b=3
    echo $((a + b))
    set +x  # 关闭调试
    echo "调试结束"
    

    第五部分:综合脚本示例

    案例 1:批量重命名文件
    #!/bin/bash
    # 功能:将当前目录所有 .txt 文件重命名为 "prefix_数字.txt"(如 prefix_1.txt)
    
    count=1
    for file in *.txt; do
      # 跳过非文件(如目录)
      if [ ! -f "$file" ]; then
        continue
      fi
      # 重命名
      mv "$file" "prefix_${count}.txt"
      echo "重命名:$file → prefix_${count}.txt"
      ((count++))
    done
    
    案例 2:系统监控脚本
    #!/bin/bash
    # 功能:监控系统 CPU、内存、磁盘使用率,超过阈值则报警
    
    # 阈值(百分比)
    CPU_THRESHOLD=80
    MEM_THRESHOLD=80
    DISK_THRESHOLD=90
    
    # 获取 CPU 使用率(取整数)
    cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2 + $4}' | cut -d. -f1)
    
    # 获取内存使用率
    mem_usage=$(free | grep Mem | awk '{print $3/$2 * 100}' | cut -d. -f1)
    
    # 获取磁盘使用率(根目录)
    disk_usage=$(df -h / | grep / | awk '{print $5}' | sed 's/%//')
    
    # 检查 CPU
    if (( cpu_usage > CPU_THRESHOLD )); then
      echo "报警:CPU 使用率过高($cpu_usage%)"
    fi
    
    # 检查内存
    if (( mem_usage > MEM_THRESHOLD )); then
      echo "报警:内存使用率过高($mem_usage%)"
    fi
    
    # 检查磁盘
    if (( disk_usage > DISK_THRESHOLD )); then
      echo "报警:磁盘使用率过高($disk_usage%)"
    fi

    总结

    掌握 Shell 脚本是一个循序渐进的过程。

    1. 入门:echo、变量、read 和简单的 if 开始,学会编写和执行基础脚本。

    2. 进阶: 熟练运用循环、函数、case 语句和各种条件判断,能够编写逻辑复杂的脚本。

    3. 高级: 掌握数组、bc 计算、颜色输出、进程替换、getoptstrap 等高级特性,编写出功能强大、交互友好且非常健壮的专业脚本。

    最重要的是 多写、多练、多看。尝试用脚本去自动化你日常工作中的重复性任务,这是最好的学习方式。


    网站公告

    今日签到

    点亮在社区的每一天
    去签到