Skip to content

Shell 脚本

Shell 脚本基础

脚本结构

bash
#!/bin/bash

echo "Hello, World!"
  • #!/bin/bash - Shebang,指定解释器
  • .sh 为扩展名

执行脚本

bash
chmod +x script.sh               # 添加执行权限
./script.sh                      # 执行脚本
bash script.sh                   # 指定解释器执行
source script.sh                 # 在当前 Shell 执行
. script.sh                      # 同 source
bash -x script.sh                # 调试模式
bash -n script.sh                # 语法检查

注释

bash
#!/bin/bash

# 单行注释

: '
多行注释
第二行
第三行
'

echo "Done"

变量

变量定义与使用

bash
name="macOS"                     # 定义变量(等号两边不能有空格)
echo $name                       # 使用变量
echo ${name}                     # 推荐用法
echo "Hello, ${name}!"           # 在字符串中使用

unset name                       # 删除变量
readonly PI=3.14                 # 只读变量

变量类型

bash
string="Hello"                   # 字符串
number=42                        # 数字(实际是字符串)
array=(one two three)            # 数组
declare -i num=10                # 整数
declare -a arr                   # 数组
declare -A assoc                 # 关联数组(bash 4+)
declare -r CONST="value"         # 只读
declare -x VAR="value"           # 导出为环境变量

特殊变量

变量说明
$0脚本名称
$1-$n位置参数
$#参数个数
$@所有参数(数组)
$*所有参数(字符串)
$?上一命令退出状态
$$当前进程 PID
$!后台进程 PID
$_上一命令最后一个参数
bash
#!/bin/bash
echo "Script: $0"
echo "First arg: $1"
echo "Args count: $#"
echo "All args: $@"
echo "Exit status: $?"

环境变量

bash
export VAR="value"               # 导出环境变量
unset VAR                        # 删除环境变量
env                              # 显示所有环境变量
printenv                         # 显示环境变量
printenv PATH                    # 显示指定变量
echo $PATH                       # 使用环境变量

读取输入

bash
read name                        # 读取输入
read -p "Enter name: " name      # 带提示
read -s -p "Password: " pass     # 静默输入
read -t 10 name                  # 超时10秒
read -n 1 char                   # 读取一个字符
read -a arr                      # 读取到数组
read -d ':' line                 # 以冒号分隔
IFS=',' read -a arr <<< "a,b,c"  # 指定分隔符

字符串操作

字符串定义

bash
str1='Hello'                     # 单引号(原样输出)
str2="Hello, $name"              # 双引号(解析变量)
str3="Line 1
Line 2"                          # 多行字符串
str4=$(command)                  # 命令替换
str5=`command`                   # 旧式命令替换

字符串长度

bash
str="Hello"
echo ${#str}                     # 输出: 5

子字符串

bash
str="Hello World"
echo ${str:0:5}                  # 从位置0开始,取5个字符: Hello
echo ${str:6}                    # 从位置6开始到结尾: World
echo ${str: -5}                  # 最后5个字符: World
echo ${str:(-5)}                 # 同上

字符串替换

bash
str="Hello World World"
echo ${str/World/macOS}          # 替换第一个: Hello macOS World
echo ${str//World/macOS}         # 替换所有: Hello macOS macOS
echo ${str/#Hello/Hi}            # 替换开头: Hi World World
echo ${str/%World/End}           # 替换结尾: Hello World End

字符串删除

bash
str="/path/to/file.txt"
echo ${str#*/}                   # 删除最短前缀: path/to/file.txt
echo ${str##*/}                  # 删除最长前缀: file.txt
echo ${str%/*}                   # 删除最短后缀: /path/to
echo ${str%%/*}                  # 删除最长后缀: (空)

字符串大小写

bash
str="Hello World"
echo ${str^^}                    # 转大写: HELLO WORLD
echo ${str,,}                    # 转小写: hello world
echo ${str^}                     # 首字母大写: Hello World
echo ${str,}                     # 首字母小写: hello World

数组

数组定义

bash
arr=(one two three)              # 定义数组
arr[0]="one"                     # 逐个赋值
arr[1]="two"
arr[2]="three"
declare -a arr                   # 声明数组
arr=([0]=one [2]=three)          # 指定索引

数组访问

bash
arr=(one two three four)
echo ${arr[0]}                   # 第一个元素: one
echo ${arr[-1]}                  # 最后一个元素: four
echo ${arr[@]}                   # 所有元素
echo ${arr[*]}                   # 所有元素
echo ${#arr[@]}                  # 元素个数
echo ${#arr}                     # 第一个元素长度
echo ${!arr[@]}                  # 所有索引
echo ${arr[@]:1:2}               # 从索引1开始取2个: two three

数组操作

bash
arr=(one two three)
arr+=("four")                    # 添加元素
arr+=(five six)                  # 添加多个
unset arr[1]                     # 删除元素
arr=("${arr[@]}")                # 重建索引

for i in "${arr[@]}"; do
    echo "$i"
done

关联数组

bash
declare -A user
user[name]="John"
user[age]=30
user[city]="Beijing"

echo ${user[name]}
echo ${!user[@]}                 # 所有键
echo ${user[@]}                  # 所有值

for key in "${!user[@]}"; do
    echo "$key: ${user[$key]}"
done

运算符

算术运算

bash
a=10
b=3

echo $((a + b))                  # 加法: 13
echo $((a - b))                  # 减法: 7
echo $((a * b))                  # 乘法: 30
echo $((a / b))                  # 除法: 3
echo $((a % b))                  # 取余: 1
echo $((a ** b))                 # 幂运算: 1000

echo $((a++))                    # 自增
echo $((a--))                    # 自减

let a=10+3                       # let 命令
expr 10 + 3                      # expr 命令

比较运算

数字比较:

运算符说明
-eq等于
-ne不等于
-gt大于
-lt小于
-ge大于等于
-le小于等于
bash
[ $a -eq $b ]
(( a == b ))
(( a > b ))
(( a < b ))

字符串比较:

运算符说明
=相等
!=不相等
-z为空
-n非空
bash
[ "$str1" = "$str2" ]
[ "$str1" != "$str2" ]
[ -z "$str" ]
[ -n "$str" ]
[[ "$str" == "pattern"* ]]       # 模式匹配
[[ "$str" =~ regex ]]            # 正则匹配

文件测试

运算符说明
-e存在
-f是文件
-d是目录
-r可读
-w可写
-x可执行
-s非空
-L符号链接
-nt比...新
-ot比...旧
bash
[ -f file.txt ]
[ -d /path/dir ]
[ -x script.sh ]
[ file1 -nt file2 ]

条件判断

if 语句

bash
if [ condition ]; then
    commands
fi

if [ condition ]; then
    commands
else
    commands
fi

if [ condition1 ]; then
    commands1
elif [ condition2 ]; then
    commands2
else
    commands3
fi

示例:

bash
#!/bin/bash
age=25

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

if [ -f "/etc/passwd" ]; then
    echo "文件存在"
fi

if [[ "$str" =~ ^[0-9]+$ ]]; then
    echo "是数字"
fi

case 语句

bash
case $var in
    pattern1)
        commands
        ;;
    pattern2)
        commands
        ;;
    *)
        commands
        ;;
esac

示例:

bash
#!/bin/bash
read -p "Enter a number (1-3): " num

case $num in
    1)
        echo "One"
        ;;
    2)
        echo "Two"
        ;;
    3)
        echo "Three"
        ;;
    *)
        echo "Invalid"
        ;;
esac

case $answer in
    y|Y|yes|YES)
        echo "Yes"
        ;;
    n|N|no|NO)
        echo "No"
        ;;
    *)
        echo "Invalid"
        ;;
esac

循环

for 循环

bash
for var in list; do
    commands
done

for (( i=0; i<10; i++ )); do
    commands
done

示例:

bash
for i in 1 2 3 4 5; do
    echo $i
done

for i in {1..5}; do
    echo $i
done

for i in {1..10..2}; do          # 步长为2
    echo $i
done

for file in *.txt; do
    echo $file
done

for file in $(ls); do
    echo $file
done

for (( i=0; i<5; i++ )); do
    echo $i
done

arr=(one two three)
for item in "${arr[@]}"; do
    echo $item
done

for i in "${!arr[@]}"; do
    echo "$i: ${arr[$i]}"
done

while 循环

bash
while [ condition ]; do
    commands
done

示例:

bash
i=1
while [ $i -le 5 ]; do
    echo $i
    ((i++))
done

while read line; do
    echo $line
done < file.txt

while IFS=',' read -r col1 col2 col3; do
    echo "$col1 - $col2 - $col3"
done < data.csv

while true; do
    echo "Running..."
    sleep 1
done

until 循环

bash
until [ condition ]; do
    commands
done

示例:

bash
i=1
until [ $i -gt 5 ]; do
    echo $i
    ((i++))
done

循环控制

bash
for i in {1..10}; do
    if [ $i -eq 5 ]; then
        break                    # 跳出循环
    fi
    echo $i
done

for i in {1..10}; do
    if [ $i -eq 5 ]; then
        continue                 # 跳过本次
    fi
    echo $i
done

函数

函数定义

bash
function_name() {
    commands
    return value
}

function function_name() {
    commands
}

函数调用

bash
#!/bin/bash

greet() {
    echo "Hello, $1!"
}

greet "World"                    # 调用函数

add() {
    local sum=$(( $1 + $2 ))
    echo $sum
}

result=$(add 10 20)
echo "Result: $result"

return_value() {
    return 42
}

return_value
echo $?                           # 获取返回值

函数参数

bash
func() {
    echo "Function: $0"          # 脚本名
    echo "First arg: $1"
    echo "All args: $@"
    echo "Args count: $#"
}

func arg1 arg2 arg3

局部变量

bash
func() {
    local var="local"            # 局部变量
    echo $var
}

var="global"
func
echo $var                        # 输出: global

输入输出

输出

bash
echo "Hello"                     # 输出
echo -n "No newline"             # 不换行
echo -e "Line1\nLine2"           # 解释转义字符
printf "%-10s %5d\n" "Name" 25   # 格式化输出

重定向

bash
command > file                   # 输出到文件(覆盖)
command >> file                  # 输出到文件(追加)
command 2> file                  # 错误输出到文件
command > file 2>&1              # 合并输出
command &> file                  # 合并输出(简写)
command < file                   # 从文件输入
command << EOF                   # Here Document
line1
line2
EOF
command <<< "string"             # Here String

管道

bash
command1 | command2              # 管道
command1 | command2 | command3   # 多级管道
command1 | tee file | command2   # 同时输出到文件和管道
command1 | xargs command2        # 将输出作为参数

调试

调试选项

bash
bash -n script.sh                # 语法检查
bash -x script.sh                # 显示执行的命令
bash -v script.sh                # 显示读取的行
bash -xv script.sh               # 组合使用

脚本内调试

bash
#!/bin/bash
set -x                           # 开启调试
set +x                           # 关闭调试
set -e                           # 命令失败时退出
set -u                           # 使用未定义变量时报错
set -o pipefail                  # 管道中任一命令失败时退出
set -euxo pipefail               # 常用组合

trap 捕获信号

bash
#!/bin/bash
cleanup() {
    echo "Cleaning up..."
    rm -f /tmp/tempfile
}

trap cleanup EXIT                # 退出时执行
trap 'echo Interrupted' INT      # Ctrl+C 时执行
trap 'echo Terminated' TERM      # kill 时执行
trap - INT                       # 取消捕获

macOS 特有命令

osascript - AppleScript

bash
osascript -e 'display notification "Hello" with title "Notification"'
osascript -e 'tell application "Finder" to empty trash'
osascript -e 'tell application "Safari" to open location "https://apple.com"'
osascript -e 'get the clipboard as text'
osascript -e 'set the clipboard to "Hello"'

open - 打开文件和应用

bash
open .                           # 在 Finder 中打开当前目录
open -a "Visual Studio Code" .   # 用 VS Code 打开
open -a Safari https://apple.com
open file.txt                    # 用默认程序打开
open -e file.txt                 # 用 TextEdit 打开

pbcopy/pbpaste - 剪贴板

bash
echo "Hello" | pbcopy            # 复制到剪贴板
pbpaste                          # 从剪贴板粘贴
cat file.txt | pbcopy
pbpaste > file.txt

say - 语音合成

bash
say "Hello, World!"
say -v Samantha "Hello"
say -o output.aiff "Hello"
say -f file.txt

caffeinate - 防止睡眠

bash
caffeinate                       # 防止系统睡眠
caffeinate -d                    # 防止显示器睡眠
caffeinate -i                    # 防止系统空闲睡眠
caffeinate -s                    # 防止系统睡眠(仅 AC 电源)
caffeinate -t 3600               # 防止睡眠1小时
caffeinate -w 1234               # 等待进程结束

实用脚本示例

参数处理

bash
#!/bin/bash
while getopts "a:b:c" opt; do
    case $opt in
        a) arg_a="$OPTARG" ;;
        b) arg_b="$OPTARG" ;;
        c) flag_c=true ;;
        \?) echo "Invalid option: -$OPTARG" >&2; exit 1 ;;
        :) echo "Option -$OPTARG requires argument" >&2; exit 1 ;;
    esac
done
shift $((OPTIND-1))

echo "a: $arg_a"
echo "b: $arg_b"
echo "c: $flag_c"
echo "Remaining: $@"

文件备份

bash
#!/bin/bash
backup_dir="/backup"
date=$(date +%Y%m%d)
source_dir=$1

if [ -z "$source_dir" ]; then
    echo "Usage: $0 <source_dir>"
    exit 1
fi

tar -czf "${backup_dir}/backup_${date}.tar.gz" "$source_dir"
echo "Backup completed: ${backup_dir}/backup_${date}.tar.gz"

系统信息

bash
#!/bin/bash
echo "=== System Information ==="
echo "Hostname: $(hostname)"
echo "macOS: $(sw_vers -productVersion)"
echo "Kernel: $(uname -r)"
echo "CPU: $(sysctl -n machdep.cpu.brand_string)"
echo "Memory: $(sysctl -n hw.memsize | awk '{print $1/1024/1024/1024 " GB"}')"
echo "Uptime: $(uptime | awk -F' up ' '{print $2}')"

下一步学习