Linux 命令行与 shell 脚本编程大全 15 呈现数据

介绍如何将脚本的输出重定向到 Linux 的不同位置

更多精彩

导览

  1. 脚本的输入、输出都可以从 STDIN( 标准输入 )STDOUT( 标准输出 ) 重定向到任意文件中
  2. 除了 STDOUT 作为标准输出,还有 STDERR 作为标准错误输出来区分脚本中的正常消息和错误消息
  3. shell 从 0 - 8 一共提供 9 个文件描述符
    • 0 - 2 做为内置的文件描述符,分别指向 STDINSTDOUT 以及 STDERR
    • 3-8 则是支持自定义的文件描述符
  4. 使用 lsof 命令可以查看系统中的文件描述符使用情况
    • -p 可以过滤进程 ID
    • -d 可以过滤文件描述符
    • -a 可以合并前面两个条件的查询结果
  5. Linux 提供 /dev/null 作为一个特殊文件,用于重定向不需要的输出
    • 任何重定向到该文件的内容都会被直接丢弃
    • 也可以通过将该文件重定向到指定文件,来实现直接清空指定文件的内容
  6. 使用 mktemp 可以创建临时文件,并返回文件名
    • mktemp test.XXXXXX6 个 X 是系统用于生成 6 位随机字符串而需要使用的占位符
    • -t 可以实现生成的文件直接位于 /tmp 目录
    • -d 可以生成目录
  7. 使用 tee 命令可以将输出同时发送到 STDOUT 和指定的文件中
    • -a 可以实现内容的追加

15.1 理解输入和输出

15.1.1 标准文件描述符

  1. Linux 用 文件描述符( File Descriptor ) 来标识每个文件对象
    1. 文件描述符 是一个非负整数,可以唯一标识会话中打开的文件
  2. 每个会话一次最多可以有 9 个文件描述符,从 0 到 8
  3. 系统保留了 0 、1 、2 作为内置的文件描述符,分别用来标识如下三种类型
    Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.1.1.1 标准输入 STDIN

  1. STDIN 文件描述符标识 shell 的 标准输入
  2. 对于终端来说,标准输入的设备一般是键盘,但对于系统来说,例如在输入时使用 重定向符号( < ) ,从脚本获取内容并输入,也被认为是标准输入

15.1.1.2 标准输出 STDOUT

  1. STDOUT 文件描述符标识 shell 的 标准输出
  2. 对于终端来说,标准输出的目标就是显示器,一般来说,大部分命令的输出都会指向 STDOUT ,也就是显示器,只有这样,在执行完命令后,才能立即看到执行结果
  3. 使用 输出重定向( > ) 则可以改变这个效果,如下图
    • ls 命令默认输出的方式就是 STDOUT
    • 但使用输出重定向将命令的输出结果指向一个文件后,命令的输出结果就没有直接输出到显示器中
    • 使用 cat 命令查看该文件,则可以看到刚才 ls 命令的输出结果
      Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.1.1.3 标准错误 STDERR

  1. STDERR 文件描述符标识 shell 的 标准错误输出
  2. 我们再执行某个命令时,如果抛出了错误,一般也是直接显示在屏幕上,但这不属于标准输出,而是标准错误输出
  3. shell 对于错误消息的处理和普通输出是分开的 ,如下图
    • 想要查看一个不存在的目录的文件列表,并将显示结果输出到指定文件中
    • shell 直接将目录不存在的错误信息进行了显示
    • 用于接收命令结果的文件生成了,但文件中没有任何内容
      Linux 命令行与 shell 脚本编程大全 15 呈现数据
  4. 默认情况下,STDERRSTDOUT 指向相同的地方,也就是显示器
    • 但两者不会随着另一个指向的变化而发生变化,从上一个例子中就可以看到

15.1.2 重定向错误

15.1.2.1 只重定向错误

  1. STDERR 的文件描述符是 2 ,所以在执行命令时,可以通过输出重定向将错误输出指向特定的文件,如下图
    • 需要注意的是,2 后面必须紧跟输出重定向符号,两者直接不能有空格
      Linux 命令行与 shell 脚本编程大全 15 呈现数据
  2. 那么如果输出内容中既有 STDOUT ,也有 STDERR ,会是什么结果呢?如下图
    • 可以看到,使用 ls 命令一次性查看了三个目录的内容列表
    • 第一个目录是不存在的,但因为使用 2>STDERR 输出到了指定文件,所以错误消息没有直接显示
    • 第二和第三个目录是真实存在,所以都正常输出了显示结果
      Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.1.2.2 重定向错误和数据

  1. 要在一条语句中同时重定向错误和数据,只需要分别使用两个重定向符号即可,如下图
    • 只是在上一个例子中的语句里多加了一个 1> ,就可以让标准输出重定向到指定文件
      Linux 命令行与 shell 脚本编程大全 15 呈现数据
  2. 如果想要让 STDOUTSTDERR 输出到同一个文件中,只需要使用 &> 即可,如下图
    • 可以看到,虽然三个目录中,不存在的目录分别是第一个和第三个
    • 但是在输出文件中两个错误信息出现的位置是最前端
    • 这是因为,为了避免错误信息散落在输出文件中,相对于 STDOUT ,shell 会自动赋予 STDERR 更高的优先级
      Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.2 在脚本中重定向输出

15.2.1 临时重定向

  1. 只需要在脚本中使用 >&2 就可以将输出重定向到 STDERR 指向的位置,如下图
    • 在脚本中,使用了一个简单的 if 结构判断传入的数值是不是 1 ,如果不是,就输出一句话,并使用 >&2 将其指向 STDERR
    • 因为在默认情况下,STDERRSTDOUT 都是指向显示器的,所以如果直接运行脚本,看不出有什么变化
    • 但是当运行脚本时,使用 2> 将错误信息指向特定文件,那么在运行脚本时,就不会直接输出错误信息,而是将错误信息输出到特定的文件中
      Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.2.2 永久重定向

  1. 如果不想在每次执行脚本时都指定各种输出需要的目标文件,则可以再脚本中使用 exec 命令提前告知对应的内容应该输出到什么文件中,如下图
    • 脚本开始就使用 exec 2>STDERR 指向了特定文件
    • 然后输出了两个语句,第一个语句没有指定文件标识符,会被默认作为 STDOUT
    • 第二个语句使用 >&2 指向了 STDERR ,表示这条语句是错误信息
    • 然后再使用 exec 1>STDOUT 指向了特定文件
    • 再次重复的输出两个语句
    • 执行脚本后发现第一个标准输出被直接输出了,这是因为 exec 1> 是在这条语句之后才定义的
    • 两个错误语句都成功的出现在了接收错误信息的文件中
      Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.3 在脚本中重定向输入

  1. 使用 exec 0< 可以实现将特定的文件作为标准输入传递给脚本,如下图
    • read 命令本来是用于接收用户的键盘输入的
    • 但是在使用 exec 0<STDIN 进行重定向后,read 命令就可以接收特定文件的内容作为标准输入进行读取
      Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.4 创建自己的重定向

  1. 前文中就有提到,shell 一共提供了 9 个文件描述符,除了 0 - 2 是内置的,其他的 6 个,3-8 都是支持自定义的文件描述符

15.4.1 创建输出文件描述符

15.4.1.1 普通创建

  1. 操作方式和之前一样,只需要使用 3-8 其中之一的数字作为文件描述符,并使用 3> 语法即可,如下图
    Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.4.1.1 内容追加创建

  1. 如果想要在输出时不覆盖之前的内容,可以使用 exec 3>> 即可,如下图
    • 这基本上就是之前 第 11 章 构建基础脚本 中对 重定向 这个知识点的结合运用
      Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.4.2 重定向文件描述符

  1. 如果想要临时修改一下文件描述符的指向,但是又不想丢失该文件描述符的默认位置,则可以使用类似于临时变量的方式中转一下,如下图
    • 这个操作看上去有点绕,但仔细阅读后不难理解
      Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.4.3 创建输入文件描述符

  1. 可以使用和重定向文件描述符一样的方式来让脚本在一次执行中既能接收文件输入,也能接收键盘输入,如下图
    Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.4.4 创建读写文件描述符

  1. 使用 exec 3<> targetFile 可以创建自定义的读写文件描述符
  2. 但是对同一个文件进行数据读写时,shell 内部维护的位置指针会导致文件中的数据混乱
  3. 不推荐使用

15.4.5 关闭文件描述符

  1. 一般情况下,在脚本中创建的文件描述符会在脚本运行结束后自动关闭
  2. 但如果想要在脚本执行中手动关闭文件描述符,可以使用 exec 3>&-
  3. 如果在脚本执行中手动关闭了文件描述符,那么在该脚本的后续执行过程中,就无法再次直接使用该文件描述符,如下图
    Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.4.5.1 关闭后再次打开文件描述符

  1. 从上一个例子可以看出来,如果在关闭文件描述符后,想要再次使用该文件描述符,则必须再次打开才行,不能直接使用
  2. 但如果再次打开文件描述符,这个文件描述符 会被 shell 识别为一个新的文件描述符 ,之前输出到目标文件中的内容也会被后续输出覆盖,如下图
    • 可以看到, 输出文件中只保留了最后一次输出的内容
    • 这是因为当文件描述符再次打开时,之前的输出内容被覆盖了
      Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.4.5.2 关闭后再次打开并实现内容追加

  1. 其实在第二次打开时使用 输出重定向追加符号( >> ) 即可,如下图
    Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.5 lsof 列出打开的文件描述符

  1. lsof 命令会列出整个 Linux 中打开的所有文件描述符
  2. 如果直接使用该命令,会输出大量的系统信息,所以一般会配合指令对输出进行过滤

15.5.1 lsof -p 指定进程 ID

  1. 使用 lsof -p PID 命令可以只显示指定进程 ID 的相关信息
  2. 例如使用 lsof -p $$ 就可以只显示与当前进程有关的信息,如下图
    • $$特殊环境变量 ,用于标识当前的进程 ID
      Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.5.2 lsof -d 指定文件描述符编号

  1. 使用 lsof -d fileDescriptorNo 命令可以只显示指定的文件描述符相关信息

15.5.3 lsof -a 合并过滤条件

  1. -a 选项单独使用没有意义,因为该选项的作用就是合并其他选项的过滤条件
  2. 使用 lsof -a -p $$ -d 0,1,2 命令则表示只显示即属于当前进程 ID ,又是 0,1,2 文件描述符的相关信息,如下图
    Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.5.4 lsof 列表内容解析

Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.6 阻止命令输出

  1. Linux 提供了一个特殊文件,位于 /dev/null
  2. 这个文件里面什么内容都没有,而且任何输出到该文件的内容都不会被保存,全部都会被丢弃
  3. 所以如果在执行命令时,不想显示错误信息,则可以将 STDERR 指向该文件
  4. 例如在前文 15.1.2 重定向错误 中我们使用过的语句 ls no-exist-dir /opt 2> redirect-error-message
  5. 只需要稍作修改,改为 ls no-exist-dir /opt 2> /dev/null ,这样就可以实现错误信息 既不输出,也不保存 的效果,如下图
    Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.7 mktemp 创建临时文件

  1. Linux 使用 /tmp 目录来存放临时文件
  2. 大多数 Linux 都开启勒系统在启动时自动清空 /tmp 目录中的所有内容的配置

15.7.1 创建本地临时文件

  1. 使用 mktemp file.XXXXXX 命令可以在当前目录创建一个文件,如下图
    • 6 个 X 表示系统会为该文件分配一个 6 位的唯一字符串,来保证该文件不会重名
    • 同时可以看到,使用该命令创建文件后,会直接输出该文件的文件名
    • 这表示如果在脚本中使用该命令,可以在创建的同时接收到该文件的文件名,方便后续操作
      Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.7.1.1 创建本地临时文件并在脚本中使用

  1. 使用 mktemp 命令创建文件时,命令会直接返回创建的文件名,所以在脚本中使用也非常方便,如下图
    Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.7.2 mktemp -t 在 /tmp 目录创建临时文件

  1. 使用 mktemp -t temp.XXXXXX 命令可以实现在 Linux 的 /tmp 目录创建临时文件,如下图
    • 可以看到,使用该命令创建文件后,当前目录没有找到这个文件,在 /tmp 目录可以找到该文件
    • 需要注意的是,如果临时文件不是在当前目录创建的,创建后返回的文件名会携带完整路径
      Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.7.3 mktemp -d 创建临时目录

  1. 使用 mktemp -d dir.XXXXXX 命令可以创建一个临时目录,这让脚本在操作临时文件时可以更丰富,如下图
    Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.8 tee 记录消息

  1. 使用 tee 命令可以将 STDIN 的数据发往两处,一处是 STDOUT ,另一处可以是指定的文件,如下图
    • 这样就可以实现即输出内容到屏幕,也输出内容到文件
      Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.8.1 tee -a 实现记录并追加消息

  1. 使用 tee -a 命令可以实现将新的内容追加到指定文件中,而不是直接覆盖文件中的原始内容,如下图
    Linux 命令行与 shell 脚本编程大全 15 呈现数据

15.9 一个创建 SQL 语句的实例

  1. 首先展示一下作者的写法,如下图
    Linux 命令行与 shell 脚本编程大全 15 呈现数据
  2. 对于上述这个写法,有两处我目前无法理解
    • 第一处是 cat >> $outfile << EOF ,不太明白为啥要先使用 cat >> 读取文件,才能再使用 << EOF 追加内容
    • 第二处是 done < ${1} ,不太明白这个地方 while 命令是如何接收到文件数据的
  3. 所以我使用自己能理解的方式写了以下脚本,来实现相同的效果,如下图
    • cat >> $sqlFile << EOF 这块实现不知道怎么优化,不这么写就没有数据
    • 而且需要注意的是,EOF 的结尾不能有空格,一开始按照格式优化的标准加了两个空格,运行就一直报错,删除空格就好了
      Linux 命令行与 shell 脚本编程大全 15 呈现数据