记录Linux
1、Shell
1.1、打印方法
# 简单打印 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 # 追加# 将 多行内容 定向到文件 abcd.log 中 name="Tom" # 多行打印 cat << EOF >> abcd.log aaa bbb ccc ddd eee fff 111 222 333 444 555 666 变量值:${name} EOF1.2、变量
####################### 变量定义 ####################### # 等号两边绝对不能有空格! 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"####################### 坑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} EOF1.3、循环
####################### 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[*]} 会把所有元素合并成一整个字符串;####################### 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####################### until循环 ####################### # 和 while 刚好反过来。只要条件不成立(假),就一直循环;一旦条件成立(真),立刻停止。适合"直到...才停止"的逻辑。 until [ 条件判断 ] do 命令 done ####################### until循环举例 ####################### # 实例 等待 MySQL 服务完全启动 until systemctl is-active --quiet mysqld do echo "MySQL 还没启动,等 2 秒再试..." sleep 2 done echo "MySQL 已启动!"####################### 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、函数
####################### 定义 ####################### # 方式一 推荐 函数名() { 命令 } # 方式二 使用 function 关键字(更适合习惯其他语言的人) function 函数名 { 命令 } ####################### 调用 ####################### # 实例演示 say_hello() { echo "Hello, 欢迎学习 Shell!" } # 调用函数 say_hello####################### 函数传参 ####################### 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 是偶数" fi1.5、想在一个shell脚本中,同时启动多个字进程
# Shell 中的进程主要分为以下几种情况 # 前台进程 (Foreground):默认情况,你敲下命令,终端被这个进程霸占,直到它执行完,你才能敲下一个命令。 # 后台进程 (Background):在命令末尾加 &,进程会在后台偷偷跑,终端立刻还给你,你可以继续敲别的命令。 # 子 Shell (Subshell):使用 (命令) 或 | 管道符,或者执行另一个脚本 ./test.sh 时,会 fork 出一个子进程(子 Shell)。子 Shell 会继承父 Shell 的变量,但子 Shell 里的修改不会影响父 Shell。(这就是前面提到的“管道陷阱”的根源)。 常用进程控制命令: & 放到后台执行。 jobs 查看当前终端后台的任务。 fg %1 把后台第 1 个任务调回前台。 bg %1 把暂停的任务放到后台继续跑。 wait 等待所有后台子进程结束。 $! 获取最后一个后台进程的 PID(进程号)。 kill -9 PID 强杀进程。#!/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 # 不带参数:阻塞直到所有后台子进程结束