1. Shell 重定向

1.1 Shell 重定向是什么

我们在之前章节有学习 echo/printf 来将我们的需求输出,此时就是我们将系统的返回输出到我们标准终端,使得我们能够看到正常的输出的结果,Unix 命令默认的输入设备即 stdin 为键盘,标准和错误设备即 stdout 为显示器,我们利用重定向可以将输入改为文件,或者将输出重新定向到其他设备或文件中。

1.2 为什么要用重定向

我们知道了系统默认的输入为键盘,标准输出与错误输出为显示器,当我们在编写 Shell 的时候有一些非交互的操作,不能通过键盘输入,或显示的结果我们不希望在显示器显示的时候,此场景就需要利用输入输出重定向了。

2. Shell 输入输出重定向

Linux Shell 重定向分为两种,顾名思义,输入重定向即改变标准的默认系统键盘输入,输出重定向即改变默认的系统显示器输出。

2.1 文件描述符

在 Linux 中一切皆文件,包括标准输入设备(键盘)和标准输出设备(显示器)在内的所有计算机硬件都是文件。为了表示和区分已经打开的文件,Linux 会给每个文件分配一个 ID,这个 ID 就是一个整数,被称为文件描述符(File Descriptor)。

如下是文件描述符的类型及其对应的设备。

文件描述符 文件名 类型 硬件
0 stdin 标准输入文件 键盘
1 stdout 标准输出文件 显示器
2 stderr 标准错误输出文件 显示器

Linux 程序在你执行任何形式的 I/O 操作时,其实都是在对一个文件描述符进行读取或写入,一个文件描述符只是一个打开的文件相关联的整数,在其背后就是硬盘上一个普通文件或管道,键盘,显示器,或是一个网络链接等。

如图更为形象的展示键盘是 Linux 系统默认标准输入设备,当然可以重定向为 file,对应的命令执行的标准输出与标准错误输出设备为屏幕,也可以根据需求重定向到文件。

2.2 输入重定向

输入方向为数据从那流入程序,输入重定向即改变默认的系统键盘输入,改变其从其他对方流入程序。

2.2.1 <

command <file,将 file 文件中的内容作为 command 的输入。

格式:

 [n]< word 

注意 [n] 与 < 之间没有空格,其中将文件描述符 n 重定向到 word 指代的文件(以只读方式打开), 如果不显示指明 n,默认就为 0,标准输入,例如:

[root@xuel-terraform-cvm-0 ~]# cat testfile.txt
test content
[root@xuel-terraform-cvm-0 ~]# cat 0< testfile.txt
test content
[root@xuel-terraform-cvm-0 ~]# cat < testfile.txt
test content

我们可以看到 testfile.txt 文件内容为 test content,在输入重定向时,我们将文件描述符 0 重定向到 testfile.txt,所以利用命令 cat 查看,结果就为文件的内容,默认就是标准输入,所以可以不写 0。

[root@xuel-terraform-cvm-0 ~]# 0< testfile.txt cat
test content
[root@xuel-terraform-cvm-0 ~]# < testfile.txt cat
test content

解析器解析到 “<” 以后会先处理重定向,将标准输入重定向到 file,之后 cat 再从标准输入读取指令的时候,由于标准输入已经重定向到了 file ,于是 cat 就从 file 中读取指令了。

2.2.2 <<EOF

command <<END,从标准输入(键盘)中读取数据,直到遇见分界符 END 才停止,分界符可以是自定义的任意字符,在此建议使用 EOF。

该输入重定向可以很方便用于批量文件的输入,可以用此来创建文件,例如:

[root@xuel-terraform-cvm-0 ~]# cat > file1.txt <<EOF
> hello shell
> hello go
> test file
> EOF
[root@xuel-terraform-cvm-0 ~]# cat file1.txt
hello shell
hello go
test file

在此利用了将 cat 的输出重定向到文件 file1.txt 中,之后利用 <<EOF 来从标准输入中读取数据,直到遇到结束标示 EOF 停止。

例如我们在学习流程控制中的 while 循环读取文件就利用了输入重定向,例如:

[root@xuel-terraform-cvm-0 ~]# cat while.sh
#!/bin/bash

FILE=file1.txt
while read str; do
    echo $str
done <$FILE

[root@xuel-terraform-cvm-0 ~]# bash while.sh
hello shell
hello go
test file

在此将文件绑定到输入重定向上,利用 while 来逐行读取文件中的内容。

2.3 输出重定向

输出方向为数据输出到那个终端,输出重定向即改变默认的显示器输出,改变其从其他设备输出。

一般输出重定向的应用场景多为将标准输出或标准错误输出分别保持到不同的文件,或者是我们不关心输出等情况等。

如下整理的标准输出重定向与标准错误输出重定向:

2.3.1 标准输出重定向

  • 覆盖方式

语法:command >file

标准输入重定向覆盖方式,直接将 command 命令的标准输出,以覆盖方式输出到文件中,例如:

[root@xuel-terraform-cvm-0 ~]# cat file1.txt
hello shell
hello go
test file
[root@xuel-terraform-cvm-0 ~]# echo "test" > file1.txt
[root@xuel-terraform-cvm-0 ~]# cat file1.txt
test

可以看到将文件的原始内容已经覆盖掉了,也可以用来清空文件内容,例如:

[root@xuel-terraform-cvm-0 ~]# cat file1.txt
test
[root@xuel-terraform-cvm-0 ~]# >file1.txt
[root@xuel-terraform-cvm-0 ~]# cat file1.txt
  • 追加方式

语法:command >>file

将标准的输出追加到文件中,注意追加为不覆盖原始文件内容,例如:

[root@xuel-terraform-cvm-0 ~]# cat file1.txt
test
[root@xuel-terraform-cvm-0 ~]# echo "test222" >> file1.txt
[root@xuel-terraform-cvm-0 ~]# cat file1.txt
test
test222

2.3.2 错误输出重定向

  • 覆盖方式:

语法:command 2>file

与标准输出重定向一样,只是绑定标准错误输出文件描述符 2,例如:

[root@xuel-terraform-cvm-0 ~]# ls /none
ls: 无法访问/none: 没有那个文件或目录
[root@xuel-terraform-cvm-0 ~]# ls /none 2> error.txt
[root@xuel-terraform-cvm-0 ~]# cat error.txt
ls: 无法访问/none: 没有那个文件或目录

我们可以使用 ls 查看一个不存在的文件或目录,会输出标准错误输出,将其重定向到 error.txt 中。

  • 追加方式:

语法:command 2>>file

与标准输出追加方式一样,只是绑定标准错误输出文件描述符,例如:

[root@xuel-terraform-cvm-0 ~]# abc 2>>error.txt
[root@xuel-terraform-cvm-0 ~]# cat error.txt
ls: 无法访问/none: 没有那个文件或目录
-bash: abc: command not found

我们使用命令 abc,Shell 提示我们没有这个命令,在此就将标准错误输出以追加形式重定向到文件中。

2.3.3 全部重定向

在我们使用输出重定向分为标准输出与错误输出,当我们希望将两者都重定向到某文件使用可以使用 &>,例如:

[root@xuel-terraform-cvm-0 ~]# cat totle.txt
ls: 无法访问/none: 没有那个文件或目录
/tmp:
cpulimit-0.2
cvm_init.log
net_affinity.log
nohup.out
nv_driver_install.log
nv_gpu_conf.log
setRps.log
v0.2.tar.gz
virtio_blk_affinity.log

我们可以看出无论标准输出或错误输出都重定向到了 totle.txt 文件中。

2.4 组合使用

输入和输出也是可以组合使用的,那么这个组合主要应用于在 Shell 脚本当中产生新的配置文件的场景下,例如:

[root@xuel-terraform-cvm-0 ~]# cat > file1.txt <<EOF
> hello shell
> hello go
> test file
> EOF
[root@xuel-terraform-cvm-0 ~]# cat file1.txt
hello shell
hello go
test file

在此就组合标准输出重定向与输入重定向组合使用。

2.5 空设备

在 Linux 系统中存在一个空设备,也称为黑洞设备,其为 /dev/null。当我们将内容重定向到它时会被丢弃,对其也无法进行读取内容操作,利用它,可以在我们编写 Shell 中能够起到禁止异常输出的功效,例如:

[root@xuel-terraform-cvm-0 ~]# ls / /none
ls: 无法访问/none: 没有那个文件或目录
/:
bin  boot  data  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  selinux  srv  sys  tmp  usr  var
[root@xuel-terraform-cvm-0 ~]# ls / /none >/dev/null 2>&1
[root@xuel-terraform-cvm-0 ~]# ls / /none &>/dev/null

可以看到我们先将标准输出重定向到 /dev/null,对于错误标准输出全部又重定向到标准输出,从而达到了将全部输出禁止掉。

或者我们使用 & 将标准输出与标准错误输出全部重定向到 /dev/null,同样能达到禁止输出的效果。

3. 实例

3.1 需求

编写一个脚本,获取 Linux 系统的 CPU 和内存信息,然后输出到文件中。

3.2 思路

可以利用函数来分别编写 CPU 和内存信息,最后利用重定向将信息输出到文件中。

3.3 实现

#!/bin/bash
# Description: sys check
# Auth: kaliarch
# Email: kaliarch@163.com
# function: sys check
# Date: 2020-03-29 14:00
# Version: 1.0

[ $(id -u) -gt 0 ] && echo "请用root用户执行此脚本!" && exit 1
sysversion=$(rpm -q centos-release|cut -d- -f3)
line="-------------------------------------------------"


[ -d logs ] || mkdir logs

sys_check_file="logs/$(ip a show dev eth0|grep -w inet|awk '{print $2}'|awk -F '/' '{print $1}')-`date +%Y%m%d`.txt"

# 获取系统cpu信息
function get_cpu_info() {
    Physical_CPUs=$(grep "physical id" /proc/cpuinfo| sort | uniq | wc -l)
    Virt_CPUs=$(grep "processor" /proc/cpuinfo | wc -l)
    CPU_Kernels=$(grep "cores" /proc/cpuinfo|uniq| awk -F ': ' '{print $2}')
    CPU_Type=$(grep "model name" /proc/cpuinfo | awk -F ': ' '{print $2}' | sort | uniq)
    CPU_Arch=$(uname -m)
cat <<EOF | column -t
CPU信息:
物理CPU个数: $Physical_CPUs
逻辑CPU个数: $Virt_CPUs
每CPU核心数: $CPU_Kernels
CPU型号: $CPU_Type
CPU架构: $CPU_Arch
EOF
}

# 获取系统内存信息
function get_mem_info() {
    check_mem=$(free -m)
    MemTotal=$(grep MemTotal /proc/meminfo| awk '{print $2}')  #KB
    MemFree=$(grep MemFree /proc/meminfo| awk '{print $2}')    #KB
    let MemUsed=MemTotal-MemFree
    MemPercent=$(awk "BEGIN {if($MemTotal==0){printf 100}else{printf "%.2f",$MemUsed*100/$MemTotal}}")
    report_MemTotal="$((MemTotal/1024))""MB"        #内存总容量(MB)
    report_MemFree="$((MemFree/1024))""MB"          #内存剩余(MB)
    report_MemUsedPercent="$(awk "BEGIN {if($MemTotal==0){printf 100}else{printf "%.2f",$MemUsed*100/$MemTotal}}")""%"   #内存使用率%

cat <<EOF
内存信息:
${check_mem}
EOF
}

# 定义主函数
function sys_check() {
    get_cpu_info
    echo ${line}
    get_mem_info
    echo ${line}
}

# 执行主函数将输出重定向到文件中
sys_check > ${sys_check_file}

# 执行测试
[root@xuel-terraform-cvm-0 ~]# bash sys_check.sh
[root@xuel-terraform-cvm-0 ~]# cat logs/10.0.1.15-20200329.txt
CPU信息:
物理CPU个数:  1
逻辑CPU个数:  1
每CPU核心数:  1
CPU型号:      Intel(R)  Xeon(R)  CPU  E5-26xx  v4
CPU架构:      x86_64
-------------------------------------------------
内存信息:
             total       used       free     shared    buffers     cached
Mem:           996        920         76          0        191        600
-/+ buffers/cache:        127        868
Swap:            0          0          0
-------------------------------------------------

可以看到利用了两个函数来获取系统的信息,将其利用重定向方式输出到文件中。

4. 注意事项

  • 一条 shell 命令,都会继承其父进程的文件描述符,因此所有的 shell 命令,都会默认有三个文件描述符;
  • 文件所有输入输出都是由该进程所有打开的文件描述符控制的,Linux 一切皆文件,因此他们的输入输出也是由文件描述符控制;
  • <中分界符可以是自定义的任意字符,在此建议使用 EOF;
  • 在进行文件追加的时候可以使用追加方式重定向,切记操作文件前备份老文件,以免文件内容异常。

5. 小结

对于重定向特别适用于文件的操作或者某些不关注输出结果的场景,在本章节需要注意覆盖模式与追加模式的区别,灵活配合空设备对特定场景进行应用,理解三个文件描述符,百变不离其中灵活组合使用。