Linux命令行与脚本-03-Shell脚本编程基础:从零开始编写自动化脚本

全文摘要

本文将带你从零开始掌握Shell脚本编程,帮助你理解脚本的基本结构和编程范式。你将学到脚本的创建与执行方式、变量的定义与使用、条件判断与循环控制、函数的定义与调用、以及如何处理用户输入。通过阅读本文,你将能够编写实用的Shell脚本来自动化日常系统管理任务。

全书总结

Shell脚本编程是Linux系统自动化的核心技能,它将命令行工具组合成可重复使用的程序。本文系统梳理了Shebang与脚本执行、变量与环境变量、条件测试与分支控制、循环结构与函数定义、输入输出处理、以及脚本调试的最佳实践。从简单的命令序列到完整的程序结构,涵盖了Bash脚本编程的核心概念。适合运维工程师、DevOps工程师、系统管理员、以及对Linux自动化感兴趣的技术人员阅读。


一、第一个Shell脚本

理解脚本的最佳方式是动手编写一个。

#!/bin/bash
# 这是一个简单的Shell脚本示例
# 文件名: hello.sh
 
echo "Hello, World!"
echo "Today is $(date)"
echo "Current user is $USER"
echo "Current directory is $(pwd)"
flowchart TB
    subgraph Exec[脚本执行流程]
        direction TB
        Create[创建脚本文件<br/>hello.sh] --> Perm[添加执行权限<br/>chmod +x hello.sh]
        Perm --> Run[执行脚本<br/>./hello.sh 或<br/>bash hello.sh]
        Run --> Output[输出结果]
    end

    subgraph Shebang[Shebang的作用]
        S1[#!/bin/bash<br/>指定解释器]
        S2[系统查找bash<br/>并执行脚本]
    end

    Exec --> Shebang

    style Create fill:#e3f2fd
    style Perm fill:#fff9c4
    style Run fill:#c8e6c9
    style Shebang fill:#ba68c8

图表讲解:这张图展示了Shell脚本的创建和执行流程——理解这个流程是编写可执行脚本的第一步。

Shebang#!)是脚本的第一行,告诉系统用哪个解释器执行该脚本。#!/bin/bash表示用/bin/bash解释器。如果没有shebang,执行脚本时会使用当前Shell(可能是zsh、sh等),可能导致兼容性问题。Shebang必须是第一行,前面不能有空格。

执行脚本的几种方式

  1. bash hello.sh:明确指定用bash解释器执行,不需要执行权限。
  2. ./hello.sh:需要脚本有执行权限(chmod +x hello.sh),会使用shebang指定的解释器。
  3. source hello.sh. hello.sh:在当前Shell中执行脚本,脚本中的变量会保留在当前Shell环境中(source方式)。

最佳实践

  • 脚本文件名以.sh结尾,便于识别
  • 脚本开头添加注释说明用途、作者、创建日期
  • 使用set -e让脚本在命令失败时退出(错误检查)
  • 使用set -u让脚本在使用未定义变量时退出

二、变量与字符串操作

变量是存储数据的容器,Bash中的变量不需要声明类型。

flowchart TB
    subgraph VarTypes[变量类型]
        direction TB
        Local[局部变量<br/>name=value]
        Env[环境变量<br/>export KEY=value]
        ReadOnly[只读变量<br/>readonly var=value]
        Array[数组<br/>arr=(a b c)]
    end

    subgraph VarUsage[变量使用]
        direction TB
        Ref[引用变量<br/>$name 或 ${name}]
        Default[默认值<br/>${var:-default}]
        Indirect[间接引用<br/>${!varname}]
    end

    VarTypes --> Exp[表达式求值]
    VarUsage --> Exp

    style Local fill:#e3f2fd
    style Env fill:#fff9c4
    style Array fill:#c8e6c9

图表讲解:这张图展示了变量的类型和使用方式——变量是脚本编程的基础。

定义和使用变量

name="Alice"
echo $name           # Alice
echo ${name}         # Alice(花括号避免歧义)
echo "Hello $name"   # Hello Alice
echo "Hello ${name}!" # Hello Alice!(!需要花括号)

环境变量(export)会被子进程继承:

export DB_USER="admin"
export DB_PASS="secret"
# 现在子Shell和脚本都能访问这些变量

命令替换:将命令的输出赋值给变量:

current_dir=$(pwd)      # 或 current_dir=`pwd`
files_count=$(ls | wc -l)
today=$(date +%Y-%m-%d)

只读变量

readonly PI=3.14
# PI=3.14159  # 会报错:只读变量不能修改

数组

fruits=(apple banana orange)
echo ${fruits[0]}     # apple(第一个元素)
echo ${fruits[@]}     # 所有元素
echo ${fruits[@]}     # 数组长度
echo ${fruits[@]: -1} # 最后一个元素

三、条件判断与分支控制

让脚本根据不同情况执行不同操作,需要条件判断。

flowchart TB
    Start[开始] --> Condition{条件测试}
    Condition -->|真| TrueBranch[执行then分支]
    Condition -->|假| FalseBranch[执行else分支]
    TrueBranch --> End[继续]
    FalseBranch --> End

    subgraph Tests[常用测试]
        Eq[字符串相等<br/>[ "$a" = "$b" ]]
        Ne[字符串不等<br/>[ "$a" != "$b" ]]
        Lt[小于<br/>[ $a -lt $b ]]
        Gt[大于<br/>[ $a -gt $b ]]
        File[文件存在<br/>[ -f file ]]
    end

    End --> Tests

    style Condition fill:#e3f2fd
    style TrueBranch fill:#c8e6c9
    style Tests fill:#fff9c4

图表讲解:这张图展示了条件判断的逻辑和常用的测试条件——条件控制让脚本有了决策能力。

if语句

#!/bin/bash
count=$(ls | wc -l)
 
if [ $count -gt 10 ]; then
    echo "There are more than 10 files."
elif [ $count -eq 10 ]; then
    echo "Exactly 10 files."
else
    echo "Less than 10 files."
fi

条件测试

  • 字符串比较:[ "$str1" = "$str2" ](相等)、[ "$str1" != "$str2" ](不等)
  • 数值比较:-eq(等于)、-ne(不等于)、-gt(大于)、-lt(小于)、-ge(大于等于)、-le(小于等于)
  • 文件测试:-f file(文件存在)、-d dir(目录存在)、-r file(可读)、-w file(可写)、-x file(可执行)
  • 逻辑操作:-a(与)、-o(或)、!(非)

case语句(多分支选择):

#!/bin/bash
echo "Enter a number:"
read num
 
case $num in
    1)
        echo "You entered one."
        ;;
    2|3)
        echo "You entered two or three."
        ;;
    *)
        echo "You entered a different number."
        ;;
esac

四、循环结构

循环让脚本能够重复执行任务。

flowchart TB
    subgraph Loops[循环类型]
        direction TB
        ForIn[for...in<br/>遍历列表]
        ForC[for ((;;))<br/>计数循环]
        While[while<br/>条件循环]
        Until[until<br/>直到满足条件]
    end

    subgraph Control[循环控制]
        direction TB
        Break[break<br/>跳出循环]
        Continue[continue<br/>跳过本次迭代]
    end

    Loops --> Use[使用场景]
    Control --> Use

    style ForIn fill:#e3f2fd
    style ForC fill:#fff9c4
    style While fill:#c8e6c9
    style Break fill:#ef5350

图表讲解:这张图展示了四种循环类型和控制语句——循环是自动化的基础。

for循环(遍历列表):

#!/bin/bash
# 遍历文件
for file in *.txt; do
    echo "Processing: $file"
    # 对文件进行处理
done
 
# 遍历数字序列
for i in {1..10}; do
    echo "Number: $i"
done

while循环(条件循环):

#!/bin/bash
counter=1
while [ $counter -le 5 ]; do
    echo "Counter: $counter"
    ((counter++))
done

until循环(直到条件为真):

#!/bin/bash
counter=1
until [ $counter -gt 5 ]; do
    echo "Counter: $counter"
    ((counter++))
done

C风格的for循环

#!/bin/bash
for ((i=0; i<10; i++)); do
    echo "Number: $i"
done

循环控制

  • break:跳出整个循环
  • continue:跳过本次迭代,继续下一次
for i in {1..10}; do
    if [ $i -eq 5 ]; then
        continue  # 跳过5
    fi
    if [ $i -eq 8 ]; then
        break     # 8时跳出
    fi
    echo "Number: $i"
done

五、函数与模块化编程

函数让代码可以重用,是模块化编程的基础。

flowchart TB
    subgraph FuncDef[函数定义]
        direction TB
        Name[函数名]
        Param[参数<br/>$1, $2, ...]
        Body[函数体<br/>命令序列]
        Return[返回值<br/>return数字]
    end

    subgraph FuncCall[函数调用]
        Call1[调用函数<br/>func arg1 arg2]
        Result[获取返回值<br/>return_code=$?]
    end

    subgraph Scope[作用域]
        Local[局部变量<br/>local var=value]
        Global[全局变量<br/>函数内可访问]
    end

    FuncDef --> Use[代码重用]
    FuncCall --> Use
    Scope --> Use

    style Name fill:#e3f2fd
    style Return fill:#fff9c4
    style Local fill:#c8e6c9

图表讲解:这张图展示了函数的定义、调用和作用域——函数是代码组织的重要工具。

定义函数

#!/bin/bash
# 函数定义
greet() {
    local name=$1  # local声明局部变量
    echo "Hello, $name!"
    return 0      # 返回值:0表示成功,非0表示失败
}
 
# 调用函数
greet "Alice"

带参数的函数

#!/bin/bash
backup_file() {
    local src=$1
    local dst=$2
 
    if [ -f "$src" ]; then
        cp "$src" "$dst"
        echo "Backed up $src to $dst"
        return 0
    else
        echo "Error: $src does not exist."
        return 1
    fi
}
 
backup_file /etc/hosts /tmp/hosts.backup

获取返回值

#!/bin/bash
check_file() {
    if [ -f "$1" ]; then
        return 0
    else
        return 1
    fi
}
 
check_file "/etc/passwd"
if [ $? -eq 0 ]; then
    echo "File exists."
else
    echo "File does not exist."
fi

函数库:将常用函数放在单独的文件中,然后在脚本中source:

# functions.sh
log_message() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" >> /var/log/myscript.log
}
 
# main.sh
source ./functions.sh
log_message "Script started"

六、用户输入与输出处理

与用户交互是脚本的重要功能。

flowchart TB
    subgraph Input[输入方式]
        direction TB
        Read[read<br/>读取单行输入]
        Arg[位置参数<br/>$1, $2, ...]
        Opt[选项解析<br/>getopts]
    end

    subgraph Output[输出方式]
        direction TB
        Echo[echo<br/>简单输出]
        Printf[printf<br/>格式化输出]
        Redir[重定向<br/>&gt; &gt;&gt;file.log]
        Err[错误输出<br/>&gt;&amp;2]
    end

    subgraph Dialog[交互式输入]
        direction TB
        Prompt[提示符<br/>read -p]
        Silent[静默输入<br/>read -s]
        Select[菜单选择<br/>select]
    end

    Input --> Script[脚本交互]
    Output --> Script
    Dialog --> Script

    style Read fill:#e3f2fd
    style Opt fill:#fff9c4
    style Printf fill:#c8e6c9
    style Select fill:#ba68c8

图表讲解:这张图展示了脚本的输入输出方式——交互是脚本实用性的关键。

读取用户输入

#!/bin/bash
echo "What is your name?"
read name
echo "Hello, $name!"
 
# 带提示符的读取
read -p "Enter your age: " age
echo "You are $age years old."
 
# 静默输入(不显示输入,适合密码)
read -s -p "Enter password: " password
echo  # 换行

位置参数

#!/bin/bash
# ./script.sh arg1 arg2 arg3
echo "First argument: $1"   # arg1
echo "Second argument: $2"  # arg2
echo "All arguments: $@"    # arg1 arg2 arg3
echo "Number of arguments: $#"  # 3

格式化输出

#!/bin/bash
name="Alice"
age=25
# printf比echo更灵活(类似C语言)
printf "Name: %-10s Age: %3d\n" "$name" "$age"
# %-10s 左对齐10个字符,%3d 右对齐3位数字

菜单选择

#!/bin/bash
echo "Choose an option:"
select option in "Create File" "Delete File" "Exit"; do
    case $option in
        "Create File")
            echo "Creating file..."
            break
            ;;
        "Delete File")
            echo "Deleting file..."
            break
            ;;
        "Exit")
            echo "Goodbye!"
            exit 0
            ;;
    esac
done

结语

Shell脚本编程是Linux自动化的核心技能。通过将命令行工具组合成脚本,你可以:

  • 自动化重复性任务:如日志分析、数据备份、批量处理
  • 简化复杂操作:将多步骤操作封装成单个命令
  • 提高工作效率:一次编写,多次使用
  • 减少人为错误:脚本按固定逻辑执行,避免手动操作失误

脚本编写的最佳实践:

  1. 从简单开始:先写简单的脚本,逐步增加功能
  2. 添加注释:解释脚本的用途和关键逻辑
  3. 错误处理:使用set -e让脚本在错误时退出
  4. 测试脚本:在不同环境中测试,确保健壮性
  5. 使用函数:模块化代码,提高可重用性

接下来的文章将深入更高级的脚本主题:文本处理、高级数组、信号处理等。掌握这些技能后,你将能够编写复杂而强大的自动化脚本。


常见问题解答

Q1:[ ] 和 有什么区别,应该用哪个?

[ ]是shell内置命令,[[ ]]是bash关键字(更强大的测试命令)。推荐使用[[ ]],因为它:(1)不需要对变量加引号([ "$var" == "val" ] vs [[ $var == "val" ]]);(2)支持逻辑运算符(&&、||、>、<)而不需要转义;(3)支持模式匹配(=~正则表达式)。

[[ ]]是bash特性,不是POSIX标准,如果需要移植到sh(如#!/bin/sh),应该使用[ ]。新脚本推荐[[ ]],需要兼容性时用[ ]


Q2:$(…) 和 ... 和 ’…’ 有什么区别?

:这三种引号的作用不同。$(...)...是命令替换,执行命令并返回输出,例如echo "Current time: $(date)"

单引号'...'完全引用,所有字符按字面意思处理,不进行变量替换、命令替换、转义。双引号"..."部分引用,变量替换、命令替换会执行,但转义字符(如$\、```)仍然有特殊含义。

例子:echo '$USER'输出$USER(字面),echo "$USER"输出alice(变量值),echo "$(whoami)"输出root(命令执行结果),echo '$(whoami)'输出$(whoami)(字面)。


Q3:如何在脚本中处理命令行参数的选项?

答:使用getopts内置命令解析选项。getopts optstring varname:optstring是要识别的选项(如a:b:表示-a是一个标志,-b和:都需要参数),varname是存储当前选项的变量名。选项值存储在OPTARG变量中。

例子:

while getopts ":u:p:" opt; do
    case $opt in
        u) user=$OPTARG ;;
        p) pass=$OPTARG ;;
        :) echo "Option -$OPTARG requires an argument." >&2; exit 1 ;;
        \?) echo "Invalid option: -$OPTARG" >&2; exit 1 ;;
    esac
done

:开头的选项不报错(用于错误处理)。:后跟字母表示该选项需要参数。


Q4:如何让脚本在后台运行且不因终端关闭而终止?

:使用nohup命令让脚本忽略SIGHUP信号(挂断信号),重定向输出到文件:

nohup ./script.sh > output.log 2>&1 &

nohup让脚本忽略SIGHUP(终端关闭时发送的信号),&让脚本在后台运行。> output.log 2>&1将标准输出和错误输出重定向到output.log。

如果不关心输出,可以重定向到/dev/null:nohup ./script.sh > /dev/null 2>&1 &。要检查后台脚本的PID,可以用echo $!(最近的后台进程PID)或在脚本中保存echo $$ > script.pid


Q5:如何调试Shell脚本?

:Shell脚本调试的几种方法:

  • bash -x script.sh:执行脚本并显示每一条命令(展开后),可以看到变量的值、条件判断的结果。
  • 在脚本中添加set -x:开启调试模式,set +x关闭调试模式。可以只调试特定部分:
set -x  # 开启调试
for file in *.txt; do
    # 调试这部分代码
    echo "Processing $file"
done
set +x  # 关闭调试
  • bash -v script.sh:verbose模式,显示脚本读取的每一行。
  • strace -f bash -x script.sh:跟踪系统调用,看到脚本调用的每个系统调用(打开文件、读写等)。
  • 使用echologger输出调试信息到日志文件。
  • ShellCheck(shellcheck script.sh):静态分析工具,检查语法错误、常见问题。

更新时间:2026年3月2日 作者:Linux技术专栏 标签:#Shell脚本 Bash 自动化 函数 条件判断