Appearance
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 "是数字"
ficase 语句
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]}"
donewhile 循环
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
doneuntil 循环
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.txtsay - 语音合成
bash
say "Hello, World!"
say -v Samantha "Hello"
say -o output.aiff "Hello"
say -f file.txtcaffeinate - 防止睡眠
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}')"