输入输出重定向是Shell脚本中非常重要的功能,它允许我们控制命令的输入来源和输出目标。通过重定向,我们可以将命令的输出保存到文件、从文件读取输入、连接多个命令等。
将文件内容作为命令的输入,而不是从键盘读取。
将命令的输出保存到文件,而不是显示在终端。
将命令的错误信息重定向到文件或其他位置。
将一个命令的输出作为另一个命令的输入。
在脚本中嵌入多行文本作为命令的输入。
管理标准输入、输出和错误流的高级操作。
在Unix/Linux系统中,每个进程都有三个标准的文件描述符: 0 - stdin(标准输入)、 1 - stdout(标准输出)、 2 - stderr(标准错误)。 重定向操作就是对这些文件描述符的操作。
学习最基本的输入输出重定向操作。
| 操作符 | 说明 | 示例 | 效果 |
|---|---|---|---|
> |
输出重定向(覆盖) | command > file |
将stdout输出到文件,覆盖原有内容 |
>> |
输出重定向(追加) | command >> file |
将stdout输出到文件,追加到文件末尾 |
< |
输入重定向 | command < file |
从文件读取stdin |
2> |
错误重定向(覆盖) | command 2> file |
将stderr输出到文件,覆盖原有内容 |
2>> |
错误重定向(追加) | command 2>> file |
将stderr输出到文件,追加到文件末尾 |
&> |
输出和错误重定向 | command &> file |
将stdout和stderr都输出到文件 |
&>> |
输出和错误重定向(追加) | command &>> file |
将stdout和stderr都追加到文件 |
#!/bin/bash
# 基本重定向示例
echo "=== 输出重定向 ==="
# 将命令输出重定向到文件(覆盖)
echo "Hello, World!" > output.txt
echo "文件内容:"
cat output.txt
# 将命令输出重定向到文件(追加)
echo "第二行内容" >> output.txt
echo "追加后的文件内容:"
cat output.txt
# 将命令输出和错误都重定向到文件
ls /nonexistent /tmp &> all_output.txt
echo "所有输出(包括错误):"
cat all_output.txt
echo -e "\n=== 输入重定向 ==="
# 创建输入文件
cat > input.txt << EOF
苹果
香蕉
橙子
葡萄
EOF
# 从文件读取输入
echo "排序后的水果:"
sort < input.txt
# 统计文件行数、单词数、字符数
echo "文件统计信息:"
wc < input.txt
echo -e "\n=== 错误重定向 ==="
# 将错误信息重定向到文件
ls /nonexistent 2> error.log
echo "错误信息:"
cat error.log
# 将错误信息重定向到/dev/null(丢弃)
ls /nonexistent 2> /dev/null
echo "错误信息已被丢弃"
echo -e "\n=== 组合重定向 ==="
# 将stdout和stderr分别重定向到不同文件
ls /nonexistent /tmp > stdout.log 2> stderr.log
echo "标准输出:"
cat stdout.log
echo "标准错误:"
cat stderr.log
# 将stderr重定向到stdout
ls /nonexistent /tmp > combined.log 2>&1
echo "合并的输出:"
cat combined.log
# 现代写法(Bash 4+)
ls /nonexistent /tmp &> modern_combined.log
echo "现代写法的合并输出:"
cat modern_combined.log
echo -e "\n=== 重定向到标准输出 ==="
# 将文件内容显示到标准输出
echo "显示文件内容:"
cat < output.txt
# 清理临时文件
rm -f output.txt input.txt all_output.txt error.log stdout.log stderr.log combined.log modern_combined.log
管道用于将一个命令的输出作为另一个命令的输入。
#!/bin/bash
# 管道使用示例
echo "=== 基本管道操作 ==="
# 统计文件数量
echo "当前目录文件数量:"
ls | wc -l
# 查找特定文件并统计
echo "文本文件数量:"
ls *.txt 2>/dev/null | wc -l
# 排序和去重
echo -e "香蕉\n苹果\n橙子\n香蕉\n葡萄" | sort | uniq
echo -e "\n=== 文本处理管道 ==="
# 创建测试文件
cat > fruits.txt << EOF
apple 5
banana 3
orange 8
grape 12
apple 2
EOF
echo "原始数据:"
cat fruits.txt
echo -e "\n按水果名排序:"
cat fruits.txt | sort
echo -e "\n按数量排序:"
cat fruits.txt | sort -k2,2n
echo -e "\n统计每种水果的总数:"
cat fruits.txt | awk '{sum[$1] += $2} END {for (fruit in sum) print fruit, sum[fruit]}' | sort
echo -e "\n=== 系统监控管道 ==="
# 查看进程信息
echo "内存使用最多的进程:"
ps aux --sort=-%mem | head -5
# 查看磁盘使用情况
echo "磁盘使用情况:"
df -h | grep -v tmpfs
# 查看网络连接
echo "ESTABLISHED连接数量:"
netstat -tun | grep ESTABLISHED | wc -l
echo -e "\n=== 复杂管道操作 ==="
# 分析日志文件(示例)
echo "创建示例日志文件..."
cat > app.log << EOF
[ERROR] 2023-01-01 10:00:00 Database connection failed
[INFO] 2023-01-01 10:00:01 Starting application
[WARN] 2023-01-01 10:00:05 High memory usage detected
[ERROR] 2023-01-01 10:00:10 File not found: config.json
[INFO] 2023-01-01 10:00:15 Application started successfully
EOF
echo "错误日志统计:"
grep "ERROR" app.log | cut -d' ' -f2 | sort | uniq -c
echo -e "\n=== 管道与重定向结合 ==="
# 将管道结果保存到文件
ls -la | grep "^-" | wc -l > regular_files_count.txt
echo "普通文件数量已保存到文件: $(cat regular_files_count.txt)"
# 将错误信息通过管道传递
ls /nonexistent *.txt 2>&1 | grep "ls:"
echo "错误信息已过滤"
echo -e "\n=== 命名管道(FIFO)==="
# 创建命名管道
mkfifo my_pipe
# 在后台向管道写入数据
echo "通过命名管道传递的数据" > my_pipe &
# 从管道读取数据
echo "从命名管道读取:"
cat my_pipe
# 清理
rm -f my_pipe fruits.txt app.log regular_files_count.txt
|&可以将stderr也通过管道传递tee命令可以在管道中保存中间结果Here文档用于在脚本中嵌入多行文本,Here字符串用于传递单行文本。
#!/bin/bash
# Here文档和Here字符串示例
echo "=== 基本Here文档 ==="
# 基本Here文档
cat << EOF
这是一个Here文档示例
可以包含多行文本
不需要担心引号或特殊字符
EOF
echo -e "\n=== 带缩进的Here文档 ==="
# 使用<<-去除前导制表符(注意:必须是制表符,不是空格)
cat <<- EOF
这是一个带缩进的Here文档
第二行也有缩进
这样可以使脚本更美观
EOF
echo -e "\n=== 带变量的Here文档 ==="
name="Alice"
age=25
cat << EOF
个人信息:
姓名: $name
年龄: $age
时间: $(date)
EOF
echo -e "\n=== 禁用变量扩展的Here文档 ==="
# 使用单引号或反斜杠禁用变量扩展
cat << 'EOF'
这里不会进行变量扩展
姓名: $name
年龄: $age
EOF
cat << EOF
这里会进行变量扩展
姓名: $name
年龄: $age
EOF
echo -e "\n=== Here文档与命令结合 ==="
# 使用Here文档作为命令输入
echo "排序后的数据:"
sort << END
orange
apple
banana
grape
END
# 使用Here文档创建配置文件
cat > config.txt << CONFIG
# 应用配置文件
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_NAME=myapp
DEBUG=true
CONFIG
echo "配置文件已创建:"
cat config.txt
echo -e "\n=== Here字符串 ==="
# 基本Here字符串
echo "小写转大写:"
tr 'a-z' 'A-Z' <<< "hello world"
# 使用变量
message="This is a test"
wc -w <<< "$message"
# 数值计算
echo "数学运算:"
bc <<< "2 + 3 * 5"
echo -e "\n=== 复杂Here文档示例 ==="
# 创建SQL脚本
cat > query.sql << SQL
SELECT
users.name,
orders.total
FROM
users
JOIN
orders ON users.id = orders.user_id
WHERE
orders.created_at > '2023-01-01'
ORDER BY
orders.total DESC
LIMIT 10;
SQL
echo "SQL脚本已创建:"
cat query.sql
echo -e "\n=== 交互式命令的Here文档 ==="
# 自动化交互式命令
ftp -n << EOF
open ftp.example.com
user myusername mypassword
binary
cd /pub
get file.txt
quit
EOF
echo "FTP操作完成"
echo -e "\n=== 多行字符串处理 ==="
# 处理多行字符串
multiline_text=$(cat << END
第一行
第二行
第三行
END
)
echo "多行文本:"
echo "$multiline_text"
echo "行数统计:"
wc -l <<< "$multiline_text"
# 清理临时文件
rm -f config.txt query.sql
<<-可以忽略前导制表符(必须是制表符)<<'EOF')可以禁用变量扩展文件描述符是Unix/Linux系统中用于访问文件或I/O资源的抽象指示器。
| 文件描述符 | 名称 | 默认用途 | 示例 |
|---|---|---|---|
0 |
stdin | 标准输入 | command 0< file |
1 |
stdout | 标准输出 | command 1> file |
2 |
stderr | 标准错误 | command 2> file |
3-9 |
自定义 | 用户定义 | command 3> file |
#!/bin/bash
# 文件描述符操作示例
echo "=== 自定义文件描述符 ==="
# 创建自定义文件描述符
exec 3> custom_output.txt
echo "这行文字会写入自定义文件描述符3" >&3
exec 3>&- # 关闭文件描述符
echo "自定义文件描述符内容:"
cat custom_output.txt
echo -e "\n=== 同时重定向stdout和stderr ==="
# 方法1:传统方式
ls /nonexistent /tmp > output.log 2>&1
echo "传统方式 - 输出和错误都重定向到同一文件:"
cat output.log
# 方法2:现代方式
ls /nonexistent /tmp &> modern_output.log
echo "现代方式 - 输出和错误都重定向到同一文件:"
cat modern_output.log
echo -e "\n=== 文件描述符复制 ==="
# 将stderr重定向到stdout的副本
ls /nonexistent /tmp 2>&1 | tee combined.log
echo "通过tee命令同时输出到屏幕和文件:"
cat combined.log
echo -e "\n=== 读取自定义文件描述符 ==="
# 创建输入文件描述符
exec 4< custom_output.txt
read line <&4
echo "从文件描述符4读取: $line"
exec 4<&-
echo -e "\n=== 高级文件描述符操作 ==="
# 同时重定向到多个位置
echo "同时输出到屏幕和文件:" | tee screen.log > file.log
echo "屏幕日志:" ; cat screen.log
echo "文件日志:" ; cat file.log
# 使用进程替换
echo "比较两个命令的输出:"
diff <(ls /bin) <(ls /usr/bin) | head -5
echo -e "\n=== 错误处理与重定向 ==="
# 将错误信息重定向到不同位置
{
echo "正常输出信息"
ls /nonexistent
echo "更多正常输出"
} > normal.log 2> error.log
echo "正常输出:"
cat normal.log
echo "错误输出:"
cat error.log
echo -e "\n=== 临时重定向 ==="
# 临时重定向stdout到stderr
echo "这是一条错误信息" 1>&2
# 临时重定向stderr到stdout
ls /nonexistent 2>&1 | grep "ls:"
echo -e "\n=== 文件描述符与函数 ==="
# 在函数中使用文件描述符
log_message() {
local message="$1"
echo "[$(date)] $message" >&3
}
# 设置日志文件描述符
exec 3>> function_log.txt
log_message "函数开始执行"
log_message "处理数据..."
log_message "函数执行完成"
echo "函数日志:"
cat function_log.txt
echo -e "\n=== 网络重定向 ==="
# 重定向到网络连接(示例)
echo "HTTP重定向示例:"
echo -e "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n" | nc example.com 80 | head -10
# 清理临时文件
rm -f custom_output.txt output.log modern_output.log combined.log screen.log file.log normal.log error.log function_log.txt
exec命令创建持久的文件描述符&>简化stdout和stderr的重定向通过实际脚本示例展示Shell输入输出重定向的综合应用。
使用各种重定向技术实现完整的日志处理系统。
#!/bin/bash
# 日志处理系统 - 重定向综合示例
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 日志级别
LOG_ERROR=1
LOG_WARN=2
LOG_INFO=3
LOG_DEBUG=4
# 配置
LOG_LEVEL=$LOG_INFO
LOG_FILE="application.log"
MAX_LOG_SIZE=1024 # 1KB for demo
# 初始化日志系统
init_logging() {
# 创建日志文件
> "$LOG_FILE"
# 设置文件描述符用于日志写入
exec 3>> "$LOG_FILE"
echo -e "${BLUE}日志系统初始化完成${NC}"
echo "日志文件: $LOG_FILE"
echo "日志级别: $LOG_LEVEL"
}
# 日志函数
log() {
local level=$1
local message=$2
local level_name=""
local color=$NC
case $level in
$LOG_ERROR)
level_name="ERROR"
color=$RED
;;
$LOG_WARN)
level_name="WARN"
color=$YELLOW
;;
$LOG_INFO)
level_name="INFO"
color=$BLUE
;;
$LOG_DEBUG)
level_name="DEBUG"
color=$GREEN
;;
esac
# 如果请求的日志级别高于当前设置,则跳过
if [ $level -gt $LOG_LEVEL ]; then
return
fi
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
local log_entry="[$timestamp] [$level_name] $message"
# 输出到屏幕(带颜色)
echo -e "${color}${log_entry}${NC}"
# 输出到日志文件(不带颜色)
echo "$log_entry" >&3
# 检查日志文件大小,如果过大则轮转
check_log_size
}
# 检查日志文件大小
check_log_size() {
local size=$(wc -c < "$LOG_FILE" 2>/dev/null || echo 0)
if [ $size -gt $MAX_LOG_SIZE ]; then
log $LOG_INFO "日志文件过大(${size}字节),执行轮转"
rotate_logs
fi
}
# 日志轮转
rotate_logs() {
local timestamp=$(date '+%Y%m%d_%H%M%S')
local backup_file="${LOG_FILE}.${timestamp}"
# 关闭当前文件描述符
exec 3>&-
# 备份当前日志文件
mv "$LOG_FILE" "$backup_file"
# 重新打开日志文件
exec 3>> "$LOG_FILE"
log $LOG_INFO "日志已轮转: $backup_file"
echo "备份文件: $backup_file" >&3
}
# 处理应用程序输出
process_application() {
local app_name="$1"
log $LOG_INFO "启动应用程序: $app_name"
# 模拟应用程序输出
{
echo "Application $app_name starting..."
echo "Config loaded successfully"
echo "WARNING: Resource usage high"
echo "Processing data..."
echo "ERROR: Database connection failed"
echo "Application completed with errors"
} | while read line; do
# 根据内容识别日志级别
case $line in
*"ERROR"*)
log $LOG_ERROR "$app_name: $line"
;;
*"WARNING"*)
log $LOG_WARN "$app_name: $line"
;;
*)
log $LOG_INFO "$app_name: $line"
;;
esac
done
log $LOG_INFO "应用程序 $app_name 处理完成"
}
# 分析日志文件
analyze_logs() {
local log_file=${1:-$LOG_FILE}
log $LOG_INFO "开始分析日志文件: $log_file"
echo -e "\n${BLUE}=== 日志分析报告 ===${NC}"
# 统计各级别日志数量
echo -e "\n${GREEN}日志级别统计:${NC}"
grep -o "\[[A-Z]*\]" "$log_file" | sort | uniq -c | sort -nr | while read count level; do
echo " $level: $count"
done
# 错误日志详情
echo -e "\n${RED}错误日志详情:${NC}"
grep "\[ERROR\]" "$log_file" | head -5 | while read line; do
echo " $(echo "$line" | cut -d']' -f3-)"
done
# 时间分布
echo -e "\n${YELLOW}时间分布(最近10条):${NC}"
grep -o "[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\} [0-9]\{2\}:[0-9]\{2\}:[0-9]\{2\}" "$log_file" | tail -10
# 使用管道进行复杂分析
echo -e "\n${BLUE}应用程序活动统计:${NC}"
awk -F']' '/INFO.*应用程序/ {print $3}' "$log_file" | sort | uniq -c
log $LOG_INFO "日志分析完成"
}
# 实时监控日志
monitor_logs() {
local log_file=${1:-$LOG_FILE}
echo -e "\n${GREEN}开始实时监控日志 (Ctrl+C 停止)${NC}"
echo -e "${BLUE}监控文件: $log_file${NC}"
# 使用tail -f实时监控
tail -f "$log_file" | while read line; do
# 根据日志级别着色显示
case $line in
*"[ERROR]"*)
echo -e "${RED}$line${NC}"
;;
*"[WARN]"*)
echo -e "${YELLOW}$line${NC}"
;;
*"[INFO]"*)
echo -e "${BLUE}$line${NC}"
;;
*"[DEBUG]"*)
echo -e "${GREEN}$line${NC}"
;;
*)
echo "$line"
;;
esac
done
}
# 清理函数
cleanup() {
log $LOG_INFO "正在清理资源..."
# 关闭文件描述符
exec 3>&-
# 删除临时文件
rm -f "$LOG_FILE"*
echo -e "${GREEN}清理完成${NC}"
}
# 信号处理
trap cleanup EXIT INT TERM
# 主函数
main() {
echo -e "${BLUE}=== Shell日志处理系统 ===${NC}"
# 初始化日志系统
init_logging
# 处理多个应用程序
process_application "WebServer" &
process_application "Database" &
process_application "CacheService" &
# 等待所有后台进程完成
wait
# 分析日志
analyze_logs
# 显示日志文件内容
echo -e "\n${BLUE}=== 日志文件内容 ===${NC}"
cat "$LOG_FILE"
# 询问是否实时监控
read -p "是否启动实时监控?(y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
monitor_logs
fi
}
# 参数处理
case "${1:-}" in
"monitor")
init_logging
monitor_logs
;;
"analyze")
analyze_logs "${2:-$LOG_FILE}"
;;
"clean")
cleanup
;;
"help"|"-h"|"--help")
echo "用法: $0 [command]"
echo "命令:"
echo " monitor 实时监控日志"
echo " analyze 分析日志文件"
echo " clean 清理日志文件"
echo " help 显示帮助信息"
;;
*)
main
;;
esac
./log_processor.sh - 运行完整的日志处理演示./log_processor.sh monitor - 实时监控日志./log_processor.sh analyze - 分析日志文件./log_processor.sh clean - 清理日志文件
&>简化stdout和stderr的重定向tee命令同时输出到屏幕和文件mktemp命令trap命令确保资源清理/dev/null丢弃所有输出command > file 2> filecommand &> file使用 set -x 开启调试模式查看重定向操作。使用 ls -l /proc/$$/fd 查看当前进程的文件描述符。在关键重定向操作前后添加 echo 语句进行调试。