Shell 编程基础(上):变量、管道、重定向
Shell 脚本基础
Shell 脚本是把多个命令写入文件,赋予执行权限后批量运行的程序。它是 Linux 自动化的核心工具。
#!/bin/bash
# 上面这行叫 Shebang,指定用 /bin/bash 解释执行
echo "Hello, Shell Script!"
变量
定义和使用
#!/bin/bash
# 定义变量(等号两边不能有空格!)
name="John"
age=25
# 使用变量(前面加 $)
echo "Name: $name"
echo "Age: $age"
# 使用变量(推荐加花括号,避免歧义)
echo "Hello, ${name}!"
# 只读变量(readonly)
readonly PI=3.14159
# PI=3.14 # 错误!不能修改
# 删除变量(不能删除只读变量)
unset name
特殊变量
| 变量 | 含义 | 示例 |
|---|---|---|
$0 | 脚本名 | ./script.sh |
$1-$9 | 第 1-9 个参数 | $1 是第一个参数 |
$# | 参数个数 | echo $# |
$* | 所有参数(作为一个字符串) | "$*" = "$1 $2 $3" |
$@ | 所有参数(作为多个字符串) | "$@" = "$1" "$2" "$3" |
$$ | 当前进程 PID | echo $$ |
$? | 上一个命令的退出状态 | echo $? |
$_ | 上一个命令的最后一个参数 | echo $_ |
#!/bin/bash
# script.sh
echo "脚本名: $0"
echo "第 1 个参数: $1"
echo "第 2 个参数: $2"
echo "参数个数: $#"
echo "所有参数: $@"
echo "当前进程 PID: $$"
# 执行:./script.sh arg1 arg2
# 输出:
# 脚本名: ./script.sh
# 第 1 个参数: arg1
# 第 2 个参数: arg2
# 参数个数: 2
# 所有参数: arg1 arg2
# 当前进程 PID: 12345
环境变量 vs 局部变量
#!/bin/bash
# 局部变量(仅在当前 Shell/脚本有效)
local_var="I am local"
# 环境变量(子进程也能访问)
export GLOBAL_VAR="I am global"
# 查看所有环境变量
env
# 查看特定环境变量
echo $PATH
echo $HOME
echo $USER
管道(Pipe)
管道 | 把前一个命令的标准输出作为后一个命令的标准输入,是 Linux 哲学的核心。
# 基本管道
cat file.txt | grep "error"
# 多个管道串联
ps aux | grep nginx | grep -v grep | awk '{print $2}'
# 统计单词数
cat file.txt | wc -w
# 排序去重
cat file.txt | sort | uniq -c | sort -rn
管道实战
#!/bin/bash
# 场景 1:查找占用 CPU 最高的 5 个进程
ps aux | sort -k3 -rn | head -n 6 | tail -n 5
# 场景 2:统计当前目录下每种文件类型的数量
ls -l | awk '{print $1}' | sort | uniq -c | sort -rn
# 场景 3:查找最大的 10 个文件
find . -type f -exec ls -lh {} + | awk '{print $5, $9}' | sort -rh | head -n 10
# 场景 4:统计日志中每个 IP 的访问次数
cat access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -n 10
重定向
重定向改变命令的标准输入/输出/错误的目标。
标准流
| 流 | 文件描述符 | 默认目标 | 符号 |
|---|---|---|---|
| 标准输入(stdin) | 0 | 键盘 | < |
| 标准输出(stdout) | 1 | 终端 | > 或 1> |
| 标准错误(stderr) | 2 | 终端 | 2> |
输出重定向
#!/bin/bash
# 覆盖写入(>)
echo "Hello" > file.txt
# 追加写入(>>)
echo "World" >> file.txt
# 重定向标准错误(2>)
ls /notexist 2> error.log
# 同时重定向 stdout 和 stderr
ls /exist /notexist > output.log 2> error.log
# 合并 stderr 到 stdout(2>&1)
ls /exist /notexist > all.log 2>&1
# 或简写
ls /exist /notexist &> all.log
# 丢弃输出(重定向到 /dev/null)
command > /dev/null 2>&1
输入重定向
#!/bin/bash
# 从文件读取输入(<)
wc -l < file.txt
# 等价于
cat file.txt | wc -l
# Here Document(多行输入)
cat << EOF
This is line 1
This is line 2
EOF
# Here String(单行输入)
wc -w <<< "Hello World Linux"
重定向实战
#!/bin/bash
# 场景 1:运行程序,保存输出和错误
./program > output.log 2> error.log
# 场景 2:运行程序,合并输出和错误
./program > all.log 2>&1
# 场景 3:运行程序,丢弃所有输出
./program > /dev/null 2>&1
# 场景 4:同时输出到屏幕和文件(tee)
./program | tee output.log
# 场景 5:追加到日志
./program | tee -a log.txt
命令替换
命令替换把命令的输出作为另一个命令的参数。
#!/bin/bash
# 反引号方式(旧,不推荐)
echo "Today is `date +%Y-%m-%d`"
# $() 方式(推荐,可嵌套)
echo "Today is $(date +%Y-%m-%d)"
# 嵌套命令替换
echo "Files: $(ls $(pwd))"
# 把命令输出赋值给变量
current_date=$(date +%Y-%m-%d)
echo "Date: $current_date"
# 计算文件数量
file_count=$(ls | wc -l)
echo "Files in current directory: $file_count"
本篇小结
- Shell 脚本第一行是 Shebang(
#!/bin/bash),指定解释器 - 变量定义:
name="value"(等号两边不能有空格),使用:$name或${name} - 特殊变量:
$0脚本名,$1-$9参数,$#参数个数,$@所有参数,$$PID,$?退出状态 export定义环境变量,子进程可访问- 管道
|:前一个命令的 stdout 作为后一个命令的 stdin,可串联多个命令 - 重定向:
>覆盖输出,>>追加输出,2>错误输出,2>&1合并错误到输出,<输入重定向 /dev/null:黑洞设备,丢弃输出;tee:同时输出到屏幕和文件- 命令替换:
$(command)或反引号,把命令输出作为参数或赋值给变量
动手实践
创建第一个脚本:
cat > hello.sh #!/bin/bash echo "Hello, $USER!" echo "Today is $(date)" echo "Current directory: $(pwd)" # Ctrl+D chmod +x hello.sh ./hello.sh带参数的脚本:
cat > greet.sh #!/bin/bash echo "Hello, $1!" echo "You are $2 years old." echo "Total arguments: $#" echo "All arguments: $@" # Ctrl+D chmod +x greet.sh ./greet.sh Alice 25管道练习:
# 统计 /bin 下有多少命令 ls /bin | wc -l # 查找当前目录下最大的 5 个文件 ls -lh | sort -k5 -rh | head -n 6 | tail -n 5 # 统计 /etc/passwd 中每种 Shell 的用户数 awk -F: '{print $7}' /etc/passwd | sort | uniq -c | sort -rn重定向练习:
echo "Test" > test.txt echo "Append" >> test.txt cat test.txt ls /notexist 2> error.log cat error.log ls /tmp /notexist > out.log 2>&1 cat out.log ls /tmp /notexist > /dev/null 2>&1 echo "Silent execution done"命令替换练习:
echo "Current date: $(date)" files=$(ls) echo "Files: $files" count=$(ls | wc -l) echo "File count: $count"思考:为什么
name = "value"(等号两边有空格)会报错?Shell 是如何解析这个语句的?2>&1和1>&2有什么区别?