1、Shell
1.1、打印方法
bash
# 简单打印
echo "Hello World"
echo "a\nb"
# 打印变量
name="Tom"
echo "Hello, ${name}"
# 打印转义字符
echo -e "a\nb"
# 打印linux命令输出
date
ls -l
# 多行打印
cat << EOF
aaa bbb ccc ddd eee fff
111 222 333 444 555 666
变量值:${name}
EOF
# 重定向到文件
echo "hello" > out.txt # 覆盖
echo "world" >> out.txt # 追加
bash
# 将 多行内容 定向到文件 abcd.log 中
name="Tom"
# 多行打印
cat << EOF >> abcd.log
aaa bbb ccc ddd eee fff
111 222 333 444 555 666
变量值:${name}
EOF
1.2、变量
bash
####################### 变量定义 #######################
# 等号两边绝对不能有空格!
name="Tom"
age=18
path="/home/work"
# 如果变量内容包含空格,必须用引号括起来
msg="Hello World"
# 打印变量 强烈建议使用 ${变量名} 的格式
echo ${name}
echo ${age}
echo ${path}
echo ${msg}
####################### 修改变量 #######################
# 重新复制即可
name="Jerry"
echo ${name}
####################### 删除变量 #######################
unset name
echo ${name} # 打印为空
####################### 只读变量 #######################
readonly PI=3.14
####################### 全局变量(环境变量) #######################
export MY_VAR="hello word"
bash
####################### 坑1 等号两边不能有空格 #######################
name = "Tom" # Shell 会以为你在执行一个叫 name 的命令,参数是 = 和 "Tom"
####################### 坑2 单引号 '' vs 双引号 "" #######################
# 双引号 "" 弱引用,会解析里面的变量和命令。
name="Tom"
echo "Hello $name" # 输出:Hello Tom
# 单引号 '' 强引用,里面写什么就输出什么,绝对不解析。
name="Tom"
echo 'Hello $name' # 输出:Hello $name
# 反引号 ` 或 $() 用于执行命令并获取结果。
now=$(date) # 推荐写法
echo "现在时间是 $now"
####################### 坑3 变量默认是字符串,不能直接算数 #######################
a=1
b=2
echo $a+$b # 错误运用,输出 1+2 (当成字符串拼接了)
# 正确的数学运算方式:
echo $((a + b)) # 输出:3
# 或者
echo $(expr $a + $b) # 输出:3 (注意 expr 的加号两边必须有空格)
####################### 坑4 变量引用遇到特殊字符 #######################
# 如果变量的值包含特殊字符(如 *、空格),在引用时加上双引号,否则会被 Shell 解析成通配符或被截断。
file="my file.txt"
cat $file # 会报错,认为你要打开 "my" 和 "file.txt" 两个文件
cat "$file" # 正确
####################### 坑5 Here Document (<<EOF) 中的变量解析 #######################
# 使用 <<EOF 时,里面的 $变量 会被解析。
# 使用 <<'EOF'(给 EOF 加上单引号)时,里面的 $变量 不会被解析,原样输出。
name="Tom"
cat << 'EOF'
aaa bbb ccc ddd eee fff
111 222 333 444 555 666
变量值:${name}
EOF
####################### 坑6 未定义变量的处理 #######################
# 如果你引用了一个没定义过的变量,默认会输出空(不会报错),这可能导致灾难性后果。
# 推荐在脚本开头加上 set -u,这样一旦用到未定义的变量,脚本会立刻报错退出,防止误删数据。
# 或者使用默认值语法:${name:-默认值}(如果 name 没定义,则返回"默认值")。
set -u
cat << EOF
aaa bbb ccc ddd eee fff
111 222 333 444 555 666
变量值:${name}
EOF
1.3、循环
bash
####################### for循环 #######################
# 形式一 遍历列表
for 变量 in 列表
do
命令
done
# 形式二 C 语言风格(常用于精确控制次数)
for ((初始值; 条件; 步长))
do
命令
done
####################### for循环举例 #######################
for file in *.log
do
echo "正在备份文件: $file"
cp "$file" "${file}.bak"
done
aaa=("1111" "2222" "3333" "4444")
for st1 in "${aaa[@]}"
do
echo ${st1}
done
for ((i=0;i<10;i++))
do
echo ${i}
done
# "${aaa[@]}" 展开数组所有元素,加双引号可以应对元素带空格的场景,是最佳实践;
# 如果写 ${aaa[*]} 会把所有元素合并成一整个字符串;
bash
####################### while循环 #######################
# 只要条件成立(真),就一直循环执行。适合不知道要循环多少次,只知道达成某个条件就停止的场景。
while [ 条件判断 ]
do
命令
done
####################### while循环举例 #######################
# 逐行读取out.txt文件
while IFS= read -r line
do
echo "读取到内容: $line"
done < "out.txt"
i=10
while [ ${i} -lt 11 ] # 判断 i 小于 11
do
echo "aaaaaa"
i=$((i + 1))
done
bash
####################### until循环 #######################
# 和 while 刚好反过来。只要条件不成立(假),就一直循环;一旦条件成立(真),立刻停止。适合"直到...才停止"的逻辑。
until [ 条件判断 ]
do
命令
done
####################### until循环举例 #######################
# 实例 等待 MySQL 服务完全启动
until systemctl is-active --quiet mysqld
do
echo "MySQL 还没启动,等 2 秒再试..."
sleep 2
done
echo "MySQL 已启动!"
bash
####################### select循环 #######################
select 变量 in 选项1 选项2
do
命令
done
####################### select循环举例 #######################
PS3="请选择要执行的操作: "
select action in "启动服务" "停止服务" "重启服务" "退出"
do
case $action in
"启动服务") echo "正在启动..."; break ;;
"停止服务") echo "正在停止..."; break ;;
"退出") exit 0 ;;
*) echo "无效选择,请重新输入" ;;
esac
done
# 坑点: 没有退出机制; 如果没有写 break 或 exit,Ctrl+C 退出后如果不小心按了回车,还会继续循环。通常需要配合 case 语句使用
1.4、函数
bash
####################### 定义 #######################
# 方式一 推荐
函数名() {
命令
}
# 方式二 使用 function 关键字(更适合习惯其他语言的人)
function 函数名 {
命令
}
####################### 调用 #######################
# 实例演示
say_hello() {
echo "Hello, 欢迎学习 Shell!"
}
# 调用函数
say_hello
bash
####################### 函数传参 #######################
is_even() {
# 检查参数个数是否为 1 个
if [ $# -ne 1 ]; then
echo "错误:需要提供一个参数!"
return 1 # 返回错误状态码
fi
if (( $1 % 2 == 0 )); then
return 0 # 成功/代表真
else
return 1 # 失败/代表假
fi
}
is_even 4
if [ $? -eq 0 ]; then
echo "4 是偶数"
fi
1.5、想在一个shell脚本中,同时启动多个字进程
bash
# Shell 中的进程主要分为以下几种情况
# 前台进程 (Foreground):默认情况,你敲下命令,终端被这个进程霸占,直到它执行完,你才能敲下一个命令。
# 后台进程 (Background):在命令末尾加 &,进程会在后台偷偷跑,终端立刻还给你,你可以继续敲别的命令。
# 子 Shell (Subshell):使用 (命令) 或 | 管道符,或者执行另一个脚本 ./test.sh 时,会 fork 出一个子进程(子 Shell)。子 Shell 会继承父 Shell 的变量,但子 Shell 里的修改不会影响父 Shell。(这就是前面提到的"管道陷阱"的根源)。
常用进程控制命令:
& 放到后台执行。
jobs 查看当前终端后台的任务。
fg %1 把后台第 1 个任务调回前台。
bg %1 把暂停的任务放到后台继续跑。
wait 等待所有后台子进程结束。
$! 获取最后一个后台进程的 PID(进程号)。
kill -9 PID 强杀进程。
bash
#!/bin/bash
# =============================================================================
# process_demo.sh
# 并行跑两个循环,打印各自 PID;两个循环没跑完,主进程就不退出
# =============================================================================
# 循环1:每 10 秒 +1,从 1 加到 100
loop1() {
local n=1 # 局部变量 n,初始值 1(local 保证只在函数内有效)
while [ "$n" -le 100 ]; do # 当 n <= 100 时循环(-le 表示小于等于)
echo "[循环1 pid=$$] n = $n" # 打印当前值;$$ 是脚本进程号
n=$((n + 1)) # n 加 1,$(( )) 是算术运算
sleep 10 # 暂停 10 秒再进入下一轮
done
}
# 循环2:每 20 秒 +10,从 1000 加到 2000
loop2() {
local m=1000 # 局部变量 m,初始值 1000
while [ "$m" -le 2000 ]; do # 当 m <= 2000 时循环
echo "[循环2 pid=$$] m = $m" # 打印当前值
m=$((m + 10)) # m 加 10
sleep 20 # 暂停 20 秒再进入下一轮
done
}
# 后台并行启动两个循环,用 $! 分别捕获 PID
loop1 & # & 把 loop1 丢到后台执行,不阻塞往下走
PID1=$! # $! = 最近一个后台进程的 PID,立刻存进 PID1
loop2 & # loop2 也丢到后台,与 loop1 并行
PID2=$! # 此刻 $! 变成 loop2 的 PID,存进 PID2
echo "循环1 PID = $PID1" # 打印两个后台进程各自的 PID
echo "循环2 PID = $PID2"
# 主进程等待两个后台循环,期间不退出;两者都结束后主进程才退出
wait # 不带参数:阻塞直到所有后台子进程结束