系统管理 - Linux重定向与管道

1. 为何要使用重定向

  1. 当屏幕输出的信息很重要,而且希望保存重要的信息时
  2. 后台执行中的程序,不希望他干扰屏幕正常的输出结果时
  3. 系统的例行命令, 例如定时任务的执行结果,希望他可以存下来时
  4. 一些执行命令,我们已经知道他可能出现错误信息, 想将他直接丢弃时
  5. 错误日志与标准正确日志需要分别输出至不同的文件

1.1 标准输入与输出

执行一个shell程序时通常会自动打开三个标准文件

1.1.1 标准文件描述符

文件描述符 名称 说明 默认设备
0 STDIN 标准输入 键盘
1 STDOUT 标准输出 屏幕
2 STDERR 错误输出 屏幕
3+ filename 其他文件 文件

说明:

  • 标准输入(STDIN,文件描述符为0): 通常对应终端的键盘,也可从其他文件或命令或者文件内容中输入
  • 标准输出(STDOUT,文件描述符为1): 默认输出到屏幕
  • 错误输出(STDERR,文件描述符为2): 默认输出到屏幕
  • 文件名称(filename,文件描述符为3+): 其他打开的文件

进程将从标准输入中得到数据,将正常输出打印至屏幕终端,将错误的输出信息也打印至屏幕终端。

进程使用文件描述符(file descriptors)来管理打开的文件

1.1.2 cat命令示例

以cat命令为例, cat命令的功能是从命令行给出的文件中读取数据,并将这些数据直接送到标准输出。

1
2
3
4
5
6
7
8
9
10
# 从文件读取并输出到屏幕
[root@liyanzhao ~]# cat /etc/passwd
# 将会把文件/etc/passwd的内容依次显示到屏幕上

# 如果没有参数, 从标准输入中读取数据
[root@liyanzhao ~]# cat
hello
hello
^C
# 用户输入的每一行都立刻被cat命令输出到屏幕上

1.1.3 输入输出过程检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 持续追踪查看文件内容
[root@liyanzhao ~]# tail -f /etc/passwd

# ctrl+z 将进程转到后台

# 查看运行的进程
[root@liyanzhao ~]# ps
PID TTY TIME CMD
5848 pts/1 00:00:00 bash
6885 pts/1 00:00:00 tail
6888 pts/1 00:00:00 ps

# 查看6885进程下的文件描述符
[root@liyanzhao ~]# ls -l /proc/6885/fd
total 0
lrwx------ 1 root root 64 Dec 3 06:57 0 -> /dev/pts/1
lrwx------ 1 root root 64 Dec 3 06:57 1 -> /dev/pts/1
lrwx------ 1 root root 64 Dec 3 06:56 2 -> /dev/pts/1
lr-x------ 1 root root 64 Dec 3 06:57 3 -> /etc/passwd
lr-x------ 1 root root 64 Dec 3 06:57 4 -> inotify

1.1.4 Linux查看标准输入输出设备

1
2
3
4
5
# Linux查看标准输入输出设备
[root@liyanzhao ~]# ls -l /dev/std*
lrwxrwxrwx 1 root root 15 Dec 2 22:30 /dev/stderr -> /proc/self/fd/2
lrwxrwxrwx 1 root root 15 Dec 2 22:30 /dev/stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root 15 Dec 2 22:30 /dev/stdout -> /proc/self/fd/1

1.2 输出重定向

重定向: 改变标准输入、标准输出的方向的就是重定向

1.2.1 重定向符号

符号 说明 示例
> 标准覆盖输出重定向 command > file
>> 标准追加输出重定向 command >> file
2> 错误覆盖输出重定向 command 2> file
2>> 错误追加输出重定向 command 2>> file
< 输入重定向 command < file
&> 正确和错误都重定向到同一文件 command &> file
2>&1 错误输出重定向到标准输出 command > file 2>&1

1.2.2 案例1: 标准输出重定向(覆盖)

1
2
3
4
5
# 标准输出重定向, 先清空,后写入, 如果文件不存在则创建
[root@liyanzhao ~]# ifconfig eth0 > abc

# 查看文件内容
[root@liyanzhao ~]# cat abc

1.2.3 案例2: 标准输出重定向(追加)

1
2
# 标准追加输出重定向, 向配置文件末尾追加内容
[liyanzhao@liyanzhao ~]$ echo "This is network conf" >> if

1.2.4 案例3: 错误输出重定向

1
2
3
4
5
6
7
8
9
10
# 创建用户
[root@liyanzhao ~]# useradd liyanzhao
[root@liyanzhao ~]# su - liyanzhao

# 将标准输出和标准错误输出重定向到不同文件
[liyanzhao@liyanzhao ~]$ find /etc -name "*.conf" 1>a 2>b

# 查看结果
[liyanzhao@liyanzhao ~]$ cat a # 标准输出
[liyanzhao@liyanzhao ~]$ cat b # 错误输出

1.2.5 案例4: 正确和错误都输入到相同位置

1
2
3
4
5
# 将标准输出和标准错误输出重定向到同一个文件, 混合输出
[liyanzhao@liyanzhao ~]$ find /etc -name "*.conf" &>ab

# 合并两个文件内容至一个文件
[liyanzhao@liyanzhao ~]$ cat a b > c

1.2.6 案例5: 重定向到相同的位置

1
2
3
4
# 重定向到相同的位置(先重定向标准输出,再重定向错误输出)
[root@liyanzhao ~]# ls /root /error >ab 2>&1

# 注意顺序:2>&1 必须放在最后

1.2.7 案例6: 重定向到空设备/dev/null

1
2
3
4
5
6
7
8
9
# 空设备,即将产生的输出丢掉
[root@liyanzhao ~]# ls /root /error >ab 2>/dev/null

# 或使用
[root@liyanzhao ~]# ls /root /error >ab &>/dev/null

# 思考
[root@liyanzhao ~]# cp /etc/passwd /dev/null
[root@liyanzhao ~]# cp /etc/passwd /etc/passwd1 2>/dev/null

如果/dev/null设备被删除:

1
2
3
4
5
6
7
# 删除/dev/null
[root@liyanzhao ~]# rm -f /dev/null

# 1.手动创建
[root@liyanzhao ~]# mknod -m 666 /dev/null c 1 3

# 2.重启自动创建

设备号说明:

  • MAJOR主设备号: 相同主设备号表示为同一种设备类型,也可以认为 kernel 使用的是相同的驱动
  • MINOR从设备号: 在同一类型设备中的一个序号

1.2.8 案例7: 脚本中使用重定向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 原始版本
[root@liyanzhao ~]# vim ping.sh
ping -c1 192.168.69.113
if [ $? -eq 0 ];then
echo "192.168.69.113 is up."
else
echo "192.168.69.113 is down."
fi

[root@liyanzhao ~]# chmod +x ping.sh
[root@liyanzhao ~]# ./ping.sh

# 改进后版本(隐藏ping的输出)
[root@liyanzhao ~]# vim ping.sh
ping -c1 192.168.69.113 &>/dev/null
if [ $? -eq 0 ];then
echo "192.168.69.113 is up."
else
echo "192.168.69.113 is down."
fi

1.2.9 案例8: 脚本中使用重定向(分别记录)

1
2
3
4
5
6
7
8
9
10
[root@liyanzhao ~]# vim ping2.sh 
ping -c1 192.168.69.113 &>/dev/null
if [ $? -eq 0 ];then
echo "192.168.69.113 is up." >>up.txt
else
echo "192.168.69.113 is down." >>down.txt
fi

[root@liyanzhao ~]# chmod +x ping2.sh
[root@liyanzhao ~]# ./ping2.sh

1.3 输入重定向

标准输入: < 等价 0<

1.3.1 案例1: mail命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 没有改变输入的方向,默认键盘
[root@liyanzhao ~]# mail alice
Subject: hello
1111
2222
3333
. # 结束
EOT

# 检查是否收到邮件
[root@liyanzhao ~]# su - alice
[root@liyanzhao ~]# mail

# 输入重定向,来自于文件
[root@liyanzhao ~]# mail -s "test01" alice < /etc/hosts

1.3.2 案例2: grep命令

1
2
3
4
5
6
7
8
# 没有改变输入的方向,默认键盘,此时等待输入
[root@liyanzhao ~]# grep 'root'
xxx
xxx

# 输入重定向,来自于文件
[root@liyanzhao ~]# grep 'root' < /etc/passwd
root:x:0:0:root:/root:/bin/bash

1.3.3 案例3: dd命令

1
2
3
4
5
# 使用if和of参数
[root@liyanzhao ~]# dd if=/dev/zero of=/file1.txt bs=1M count=20

# 使用重定向
[root@liyanzhao ~]# dd </dev/zero >/file2.txt bs=1M count=20

1.3.4 案例4: MySQL导入

1
2
# mysql 表结构导入
[root@liyanzhao ~]# mysql -uroot -p123 < bbs.sql

1.3.5 案例5: 利用重定向建立多行的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 手动执行 shell 命令
[root@liyanzhao ~]# echo "111" > file1.txt
[root@liyanzhao ~]# cat file1.txt
111

# 使用cat创建多行文件(覆盖)
[root@liyanzhao ~]# cat >file2.txt
111
222
333
^D

# 使用cat追加多行文件
[root@liyanzhao ~]# cat >>file3.txt
aaa
bbb
ccc
^D

1.3.6 案例6: 利用重定向建立多行的文件(脚本)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 脚本 script 创建多行文件
[root@liyanzhao ~]# vim create_file.sh
cat >file200.txt <<EOF
111
222
333
yyy
ccc
EOF

# 创建菜单脚本
[root@liyanzhao ~]# vim vm.sh
cat <<-EOF
+------------------- --- ---- --- ---- --- --- ---- --- --+ ||
| ====================== |
| 虚拟机基本管理 v5.0 |
| by liyanzhao |
| ====================== |
| 1. 安装 KVM |
| 2. 安装或重置 CentOS-6.9 |
| 3. 安装或重置 CentOS-7.4 |
| 5. 安装或重置 Windows-7 |
| 6. 删除所有虚拟机 |
| q. 退出管理程序 |
+------------------- --- ---- --- ---- --- --- ---- --- --+
EOF

1.3.7 案例7: 两条命令同时重定向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 分别重定向
[root@liyanzhao ~]# ls; date &>/dev/null

[root@liyanzhao ~]# ls &>/dev/null; date &>/dev/null

# 使用subshell同时重定向
[root@liyanzhao ~]# (ls; date) &>/dev/null

# 后台执行
[root@liyanzhao ~]# (while :; do date; sleep 2; done) &
[1] 6378

[root@liyanzhao ~]# (while :; do date; sleep 2; done) &>date.txt &

[root@liyanzhao ~]# jobs
[1]+ 运行中 ( while :; do date; sleep 2; done ) &>/date.txt &

扩展点: subshell

1
2
3
4
5
6
7
# 当前shell执行(会改变当前目录)
[root@liyanzhao ~]# cd /boot; ls

# subshell 中执行(不会改变当前目录)
[root@liyanzhao ~]# (cd /boot; ls)

# 如果不希望某些命令的执行对当前 shell 环境产生影响,请在subshell中执行

1.4 进程管道技术

管道操作符号 | 连接左右两个命令, 将左侧的命令的标准输出, 交给右侧命令的标准输入

格式: cmd1 | cmd2 […|cmdn]

1.4.1 案例1: 将/etc/passwd中的用户按UID大小排序

1
2
3
4
5
6
7
8
# 按UID升序排序
[root@liyanzhao ~]# sort -t":" -k3 -n /etc/passwd

# 按UID降序排序
[root@liyanzhao ~]# sort -t":" -k3 -n /etc/passwd -r

# 排序后只显示前10行
[root@liyanzhao ~]# sort -t":" -k3 -n /etc/passwd |head

1.4.2 案例2: 统计当前/etc/passwd中用户使用的shell类型

1
2
3
4
5
6
7
8
9
10
11
12
13
# 思路:取出第七列(shell) | 排序(把相同归类)| 去重

# 取出第七列
[root@liyanzhao ~]# awk -F: '{print $7}' /etc/passwd

# 排序
[root@liyanzhao ~]# awk -F: '{print $7}' /etc/passwd |sort

# 去重
[root@liyanzhao ~]# awk -F: '{print $7}' /etc/passwd |sort |uniq

# 统计数量
[root@liyanzhao ~]# awk -F: '{print $7}' /etc/passwd |sort |uniq -c

1.4.3 案例3: 统计出最占CPU的5个进程

1
2
# 按CPU使用率降序排序,显示前6行(包含标题行)
[root@liyanzhao ~]# ps aux --sort=-%cpu |head -6

1.4.4 案例4: 统计网站的访问情况 top 20

1
2
3
4
5
6
7
8
9
10
11
12
# 思路: 打印所有访问的连接 | 过滤访问网站的连接 | 打印用户的 IP | 排序 | 去重

# 安装httpd
[root@liyanzhao ~]# yum -y install httpd
[root@liyanzhao ~]# systemctl start httpd
[root@liyanzhao ~]# systemctl stop firewalld

# 统计访问IP
[root@liyanzhao ~]# ss -an |grep :80 |awk -F":" '{print $8}' |sort |uniq -c

# 按访问次数排序,显示前20
[root@liyanzhao ~]# ss -an |grep :80 |awk -F":" '{print $8}' |sort |uniq -c |sort -k1 -rn |head -n 20

1.4.5 案例5: 打印当前所有IP

1
2
3
[root@liyanzhao ~]# ip addr |grep 'inet ' |awk '{print $2}' |awk -F"/" '{print $1}'
127.0.0.1
192.168.69.112

1.4.6 案例6: 打印根分区已用空间的百分比(仅打印数字)

1
[root@liyanzhao ~]# df |grep '/$' |awk '{print $5}' |awk -F"%" '{print $1}'

1.4.1 tee管道技术

tee命令可以将数据同时输出到标准输出和文件。

tee命令说明

1
2
3
4
5
6
7
8
9
10
11
# tee命令:将数据同时输出到标准输出和文件
[root@liyanzhao ~]# ip addr |grep 'inet ' |tee ip.txt |awk -F"/" '{print $1}' |awk '{print $2}'
127.0.0.1
192.168.69.112
192.168.122.1

# 查看保存的文件
[root@liyanzhao ~]# cat ip.txt
inet 127.0.0.1/8 scope host lo
inet 192.168.69.112/24 brd 192.168.69.255 scope global ens32
inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0

重定向与tee区别

1
2
3
4
5
# 重定向:只保存到文件,不显示在屏幕
[root@liyanzhao ~]# date > date.txt

# tee:既保存到文件,又显示在屏幕
[root@liyanzhao ~]# date |tee date.txt

tee常用选项

选项 功能 示例
-a 追加模式 command | tee -a file
-i 忽略中断信号 command | tee -i file

tee应用场景

1
2
3
4
5
6
7
8
# 场景1: 保存日志同时查看
tail -f /var/log/messages |tee log_backup.txt

# 场景2: 保存到多个文件
command |tee file1.txt file2.txt

# 场景3: 追加模式
command |tee -a log.txt

1.4.2 参数传递xargs

xargs将参数列表转换成小块分段传递给其他命令

xargs说明

  • 读入stdin的数据转换为参数添加至命令后面
  • 让一些不支持管道的命令可以使用管道
  • 管道命令符能让大家能进一步掌握命令之间的搭配使用方法,进一步提高命令输出值的处理效率

xargs基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
# 统计包含/sbin/nologin的行数
[root@liyanzhao ~]# grep "/sbin/nologin" /etc/passwd | wc -l
33

# 查看第5行
[root@liyanzhao ~]# head -5 /etc/passwd|tail -1
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

# 使用grep过滤输出信息
[root@liyanzhao~]# ls -l /etc |grep pass
-rw-r--r-- 1 root root 4653 Dec 2 15:54 passwd
-rw-r--r--. 1 root root 4606 Dec 2 15:54 passwd-
-rw-r--r--. 1 root root 1454 Sep 23 2014 passwd.OLD

xargs示例

示例1: 管道和标准输出以及标准错误输出

1
2
3
4
5
# 使用普通用户执行如下命令
find /etc/ -name "p*"|grep passwd
find /etc/ -name "p*"|grep passwd > a
find /etc/ -name "p*"|grep passwd > b
find /etc/ -name "p*"|grep passwd &> ab

示例2: 使用xargs传递参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 有些命令不支持管道技术, 但是可以通过xargs来实现管道传递

# 示例1: which cat|xargs ls-l
[root@liyanzhao ~]# which cat |xargs ls -l
-rwxr-xr-x 1 root root 54080 Nov 5 2016 /usr/bin/cat

# 示例2: ls |xargs rm -rvf
[root@liyanzhao ~]# ls |xargs rm -rvf

# 示例3: 复制文件
# ls |xargs cp -rvft /tmp/
# 或使用-I选项
[root@liyanzhao ~]# ls |xargs -I {} cp -rvf {} /tmp

# 示例4: 移动文件
# ls |xargs mv -t /tmp/
# 或使用-I选项
[root@liyanzhao ~]# ls |xargs -I {} mv {} /tmp

xargs常用选项

选项 功能 示例
-I {} 指定替换字符串 ls | xargs -I {} cp {} /tmp
-n 每次传递的参数个数 ls | xargs -n 2
-d 指定分隔符 echo "a,b,c" | xargs -d ,
-p 交互式确认 ls | xargs -p rm
-t 显示执行的命令 ls | xargs -t rm

xargs实战案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 案例1: 查找并删除文件
find /tmp -name "*.tmp" |xargs rm -f

# 案例2: 查找并复制文件
find /etc -name "*.conf" |xargs -I {} cp {} /backup/

# 案例3: 批量修改文件权限
find /var/log -type f |xargs chmod 644

# 案例4: 统计多个文件的行数
find /etc -name "*.conf" |xargs wc -l

# 案例5: 批量grep
find /etc -name "*.conf" |xargs grep "root"

2. 管道注意事项

2.1 注意事项

  1. 在管道后面的命令,都不应该在写文件名

    1
    2
    3
    4
    5
    # 错误
    cat file.txt |grep "test" file.txt

    # 正确
    cat file.txt |grep "test"
  2. 在管道中只有标准输出才可以传递下一个命令, 标准错误输出会直接输出终端显示, 建议在使用管道前将标准错误输出重定向

    1
    2
    3
    4
    5
    # 错误:错误信息会显示在终端
    find /etc -name "*.conf" | grep rc

    # 正确:先重定向错误输出
    find /etc -name "*.conf" 2>/dev/null | grep rc
  3. 有些命令不支持管道技术, 但是可以通过xargs来实现管道传递

    1
    2
    3
    4
    5
    # 不支持管道
    which cat | ls -l

    # 使用xargs
    which cat |xargs ls -l

2.2 管道最佳实践

1
2
3
4
5
6
7
8
9
10
11
# 实践1: 先处理错误输出
find /etc -name "*.conf" 2>/dev/null |grep rc

# 实践2: 使用xargs处理不支持管道的命令
find /tmp -name "*.log" |xargs rm -f

# 实践3: 使用tee保存中间结果
find /etc -name "*.conf" |tee conf_list.txt |wc -l

# 实践4: 组合使用多个管道
ps aux |grep nginx |awk '{print $2}' |xargs kill

3. 重定向与管道综合案例

3.1 案例1: 日志分析

1
2
# 分析访问日志,统计IP访问次数,保存结果
cat /var/log/access.log |awk '{print $1}' |sort |uniq -c |sort -rn |head -10 |tee top_ip.txt

3.2 案例2: 系统监控脚本

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
# 系统监控脚本

# 监控CPU使用率
ps aux --sort=-%cpu |head -6 |tee -a cpu_monitor.log

# 监控内存使用率
ps aux --sort=-%mem |head -6 |tee -a mem_monitor.log

# 监控磁盘使用率
df -h |grep -E '^/dev' |awk '{print $5}' |sed 's/%//' |awk '$1 > 80' |tee -a disk_alert.log

3.3 案例3: 批量文件处理

1
2
# 批量查找并处理文件
find /var/log -name "*.log" -type f |xargs -I {} sh -c 'echo "Processing: {}" && tail -100 {} > {}.backup'

3.4 案例4: 数据备份脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/bash
# 数据备份脚本

# 备份数据库
mysqldump -uroot -p123 database_name > backup.sql 2>error.log

# 压缩备份文件
gzip backup.sql 2>>error.log

# 移动到备份目录
mv backup.sql.gz /backup/ 2>>error.log

# 记录备份结果
if [ $? -eq 0 ]; then
echo "$(date): Backup successful" >> /var/log/backup.log
else
echo "$(date): Backup failed" >> /var/log/backup.log
fi

4. 命令总结

4.1 重定向命令

命令 功能 示例
command > file 标准输出重定向(覆盖) ls > file.txt
command >> file 标准输出重定向(追加) ls >> file.txt
command 2> file 错误输出重定向 command 2> error.log
command 2>> file 错误输出追加 command 2>> error.log
command &> file 所有输出重定向 command &> all.log
command 2>&1 错误输出重定向到标准输出 command > file 2>&1
command < file 输入重定向 mysql < script.sql
command <<EOF Here Document cat <<EOF

4.2 管道命令

命令 功能 示例
command1 | command2 管道 ps aux | grep nginx
command | tee file 同时输出到屏幕和文件 ls | tee file.txt
command | xargs cmd 参数传递 find . -name "*.txt" | xargs rm
command | xargs -I {} cmd {} 使用占位符 ls | xargs -I {} cp {} /tmp

4.3 常用组合

1
2
3
4
5
6
7
8
9
10
11
# 组合1: 查找、过滤、统计
find /etc -name "*.conf" 2>/dev/null |grep nginx |wc -l

# 组合2: 排序、去重、统计
cat access.log |awk '{print $1}' |sort |uniq -c |sort -rn

# 组合3: 查找、处理、保存
find /var/log -name "*.log" |xargs -I {} tail -100 {} |tee log_summary.txt

# 组合4: 监控、过滤、记录
ps aux |grep -v grep |grep nginx |tee -a nginx_monitor.log