shell脚本学习六(呈现数据)

shell脚本学习六(呈现数据)

Scroll Down

呈现数据:

           如果你有浏览过我之前的关于 shell 脚本的博客,就会发现到目前为止都是通过将数据打印在屏幕上或将数据重定向到文件中来显示信息,本篇博客将会讲解如何将脚本的输出重定向到 linux 系统的不同位置。

  1. 理解输入和输出
    ==========

            两种显示脚本输出的方法:

**                     ① 在显示器屏幕上显示输出**

**                     ② 将输出重定向到文件中**

**              这两种方法要么将数据输出全部显示,要么什么都不显示,但有时将一部分数据在显示器上显示,另一部分数据保存到文件中也是不错的**

 2、标准文件描述符

                  linux 系统将每个对象当做文件来处理,包括输入和输出过程,linux 用文件描述符( file  descriptor )来标识每个文件对象,文件描述符是一个非负整数,可以唯一标识会话中打开的文件。每个进程一次最多可以有九个文件描述符。出于特殊目的,bash shell 保留了前三个文件描述符(0、1、2)

linux 的标准文件描述符

文件描述符

缩写

描述

0

STDIN

标准输入

1

STDOUT

标准输出

2

STDERR

标准错误

           这三个特殊文件描述符会处理脚本的输入和输出,shell 用它们将 shell 默认的输入和输出导向到相应的位置

   2.1  STDIN

        STDIN 文件描述符代表 shell 的标准输入,对终端界来说,标准输入是键盘。shell 从 STDIN 文件描述符对应的键盘获得输入,在用户输入时处理每个字符。

        在使用输入重定向符号( < )时,linux 会用重定向指定的文件来替换标准输入文件描述符,它会读取文件并提取数据,就如同它是键盘上键入的。

          许多 bash 命令能接受 STDIN 的输入,尤其是没有在命令行上指定文件的话。

              

           当在命令行只输入 cat 命令时,它会从 STDIN 接受输入,输入一行,cat 命令就会显示出一行,但你也可以通过 STDIN 重定向符号强制 cat 命令接受来自另一个非 STDIN 文件的输入。

             

           现在 cat 命令会用 testfile 文件中的行作为输入,你可以使用这种技术将数据输入到任何 STDIN 接受数据局的 shell 命令中。

     2.2  STDOUT

            STDOUT 文件描述符代表 shell 的标准输出,载终端界面上,标准输出就是终端显示器,shell 的所有输出(包括 shell 中运行的程序和脚本)会被定向到标准输出中,也就是显示器

            默认情况下,大多数 bash 命令会将输出导向 STDOUT 文件描述符( 即,将输出显示到屏幕上 ),你可以用输出重定向来改变

               

               通过输出重定向符号,通常会显示到显示器的所有输出会被 shell 重定向到指定的重定向文件,你也可以将数据追加到某个文件,这可以用 >> 符号来完成。

               

          who 命令生成的输出会被追加到 test2 文件中已有数据的后面,但是如果你对脚本使用了标准输出重定向,你会遇到一个问题,来看下面的例子

              

         当命令生成错误消息时,shell 并未将错误消息重定向到输出重定向文件,shell 创建了输出重定向文件,但错误消息却显示在了显示器屏幕上,注意,在显示 test3 文件的内容时并没有任何错误,test3 文件创建成功了,只是里面是空的。

         shlell 对于错误消息的处理是跟普通输出分开的,如果你创建了在后台模式下运行的 shell 脚本,通常你必须依赖发送到日志文件的输出消息,用这种方法的话,如果出现了错误消息,这些信息是不会出现在日志文件中的,你需要换种方法来处理

      2.3  STDERR

         shell 通过特殊的 STDERR 文件描述符来处理错误消息,STDERR 文件描述符代表 shell 的标准错误输出,shell 或者 shell 中运行的程序和脚本出错时生成的错误消息都会发送到这个位置。默认情况下, STDERR 文件描述符会和 STDOUT 文件描述符指向同样的地方(尽管分配给它们的文件描述符值不同),也就是说,默认情况下错误消息也会输出到显示器输出中。

        但从上面的例子可以看出,STDERR 并不会随着 STDOUT 的重定向而发生改变。使用脚本时,你常常会想改变这种行为,尤其是当你希望将错误消息保存到日志文件中的时候。

3、重定向错误 

        3.1 只重定向错误

            STDERR 文件描述符值为 2 ,可以以此来选择只重定向错误消息,将该文件描述符值放在重定向符号前 ( 该值必须紧紧地放在重定向符号前,否则不会工作 )

               

           现在运行该命令,错误消息就不会出现在屏幕上了,该命令生成的任何错误消息都会保存在输出文件中,用这种方法,shell 会只重定向错误消息而非普通数据,下面的例子是将 STDOUT 和 STDERR 消息混杂在同一输出中的例子

               

            ls 的正常 STDOUT 输出仍然 正常输出( 显示到了屏幕上 ),而且由于使用了文件描述符值 2 将生成的所有错误消息直接发送到指定的重定向文件中。

         3.2  重定向错误和数据

            如果想重定向错误和正常输出,必须用两个重定向符号,需要在符号前放上待重定向数据所对应的文件描述符,然后指向用于保存数据的输出文件。

                 

           shell 利用 1> 符号将 ls 命令的正常输出重定向到 outFile 文件,而这些输出本来是进入 STDOUT(即屏幕显示),所有本该输出到 STDERR 的错误消息通过 2> 符号被重定向到了 errFile,可以用这种方法将脚本的正常输出和脚本生成的错误消息分离开来,这样就可以轻松地识别出错误消息,再不用在成千上万行正常输出中来回翻找了。

           另外如果愿意,也可以将 STDERR 和 STDOUT 的输出重定向到同一个输出文件,为此 bash shell 提供了特殊的重定向符号 &>

               

            当使用 &> 符时,命令生成的所有输出都会发送到同一位置,包括数据和错误

       (  值得注意的是,这种方法下为了避免错误消息散落在输出文件中,相较于标准输出,bash shell 自动赋予了错误消息更高的优先级 ,这样你就能集中浏览错误消息了 )

 4、  在脚本中重定向输出

           可以在脚本中用 STDOUT 和 STDERR 文件描述符以在多个位置生成输出,只要简单地重定向相应的文件描述符就行了,有两种方法来在脚本中重定向

           ① 临时重定向行输出

           ② 永久重定向脚本中的所有命令

         4.1 临时重定向

              如果有意在脚本中生成错误消息,可以将单独的一行输出重定向到 STDERR,你所需要做的是使用输出重定向符来将输出信息重定向到 STDERR 文件描述符,在重定向到文件描述符时,你必须在文件描述符数字之前加一个 &:

 echo  “This  is  an  error  message”   >&2    (主动产生报错信息并传给 STDERR)

              这行会在脚本的 STDERR 文件描述符所指向的位置显示文本,而不是普通的 STDOUT,下面这个例子就利用了这个功能:

                    

              注意,默认情况下 Linux 会将 STDERR 导向 STDOUT。

                   

                  如果你想平常一样运行可能看不出来什么区别,但是,如果你在运行脚本时重定向了 STDERR,脚本中的所有导向 STDERR 的文本都会被重定向。

             

                 通过 STDOUT 显示的文本显示在了屏幕上。而发送给 STDERR 的 echo 语句的文本则被重定向到了输出文件,这个方法非常适合在脚本中生成错误消息,如果有人用了你的脚本,他们可以像上面的例子中那样轻松地通过 STDERR 文件描述符重定向错误消息。

5、 永久重定向

               如果脚本中有大量数据需要重定向,那重定向每个 echo 语句就会很烦琐,取而代之你可以用 exec 命令告诉 shell 在脚本执行期间重定向某个特定文件描述符

                    

                    

              exec 命令会启动一个新 shell 并将 STDOUT 文件描述符重定向到文件,脚本中发给 STDOUT 的所有输出会被重定向到文件,也可以在脚本执行过程中重定向 STDOUT.

                   

                   

            上面这个脚本使用 exec 命令来将所有发给 STDERR 输出重定向到文件 testerror,然后使用 echo 直接在屏幕显示两句话,随后又使用 exec 命令来将 STDOUT 重定向到 testout 文件,通过执行脚本可以看出,尽管 STDOUT 被重定向了,但是你仍然可以将 echo 语句的输出发给 STDERR,在本例中还是重定向到 testerror 文件。

6、在脚本中重定向输入

         你可以使用与脚本中重定向 STDOUT 和 STDERR 相同的方法来将 STDIN 从键盘重定向到其他位置。exec 命令允许你将 STDIN 重定向到 Linux 系统的文件中

            exec   0<  testfile

          此命令会告诉 shell 它应该从文件 testfile 中获得输入,而不是 STDIN,这个重定向只要在脚本需要输入时就会作用:

             

             

         将 STDIN 重定向到文件后,当 read 命令试图从 STDIN 读入数据时,它会到文件去取数据,而不是键盘,这是在脚本中从待处理的文件中读取数据的绝妙方法,Linux 系统管理员的一项日常任务就是从日志文件中读取数据并处理,这是完成该任务最简单的方法。

 7、创建自己的重定向

        在脚本中重定向输入和输出时,并不局限于这三个默认的文件描述符,前面的博客中曾提到过,在 shell 中最多可以有 9 个打开的文件描述符,其他 6 个从 3~8 的文件描述符可用作输入或输出重定向。你可以将这些文件描述符中的任意一个分配给文件,然后在脚本中使用它们。

       7.1  创建输出文件描述符

              可以用 exec 命令来给输出分配文件描述符。和标准的文件描述符一样,一旦将另一个文件描述符分配给一个文件,这个重定向就会一直有效,直到你重新分配,这里有个在脚本中使用其他文件描述符的例子。

           

           \

        这个脚本用 exec 命令将文件描述符 3 重定向到另一个文件,当脚本执行 echo 语句时,输出内容会像预想中那样出现在 STDOUT 上,但你重定向到文件描述符 3 的那行 echo 语句的输出却进入了另一个文件。这样你就可以在显示器上保持正常的输出,而将特定信息重定向到文件中(比如日志文件)。

       也可以不用创建新文件,而是使用 exec 命令来将输出追加到现有文件中

          exec  3>>test3out 

     7.2  重定向文件描述符

        这个小节介绍怎么恢复已重定向的文件描述符,你可以分配另外一个文件描述符给标准文件描述符,这意味着你可以将 STDOUT 的原来位置重定向到另一个文件描述符,然后再利用该文件描述符重定向回 STDOUT( 可通过例子进行理解 )

         

         

         我们一段一段的分析,首先,脚本将文件描述符 3 重定向到文件描述符 1 的当前位置,也就是 STDOUT。这意味着任何发送给文件描述符 3 的输出都将出现在显示器上。

         第二个 exec 命令将 STDOUT 重定向到文件,shell 现在会将发送给 STDOUT 的输出直接重定向到输出文件中。但是,文件描述符 3 仍然指向 STDOUT 原来的位置,也就是显示器。如果此时将输出数据发送给文件描述符 3,它仍然会出现在显示器上,尽管 STDOUT 已经被重定向了。

          最后在向 STDOUT( 现在指向一个文件 )发送一些输出之后,脚本使用 exec 将 STDOUT 重定向到文件描述符 3 的当前位置( 现在仍然是显示器)。这意味着现在 STDOUT 又指向了它原来的位置:显示器。

           这个方法可能会让你感到困惑,但这是一种在脚本中临时重定向输出,然后恢复默认输出设置的常用方法。(  按照我个人理解,这个方法有点像写代码时为了保持一个变量值初始不变的同时使用其值,遂找一个临时变量存储最开始的值,操作完之后再将临时变量存储的原始值返还  ) 简单点说就是下面的说法

         7.3  创建输入文件描述符

           可以用和重定向输出文件描述符同样的方法重定向输入文件描述符,在重定向到文件之前,先将 STDIN 文件描述符保存到另外一个文件描述符,然后在读取完文件之后再将 STDIN 恢复到它原来的位置。

            

                 

            在这个例子中,文件描述符 6 用来保存 STDIN 的位置,然后脚本将 STDIN 重定向到一个文件,read 命令的所有输入都来自重定向后的 STDIN( 也就是输入文件 )。

            在读取了所有行之后,脚本会将 STDIN 重定向到文件描述符 6,从而将 STDIN 恢复到原先的位置,该脚本用了另外一个 read 命令来测试 STDIN 是否恢复正常了,这次它会等待键盘的输入。

        7.4  创建读写文件描述符

             你可以打开单个文件描述符来作为输入和输出,可以用一个文件描述符对同一个文件进行读写。

             不过用这种方法时,你要特别小心。由于你是对同一个文件进行数据读写,shell 会维护一个内部指针,指明在文件中的当前位置。任何读或写都会从文件指针上次的位置开始。如果不够小心,它会产生一些令人瞠目的结果,

                  

                   

             这个例子用了 exec 命令将文件描述符 3 分配给文件 testfile 以进行文件读写。接下来,它 通过分配好的文件描述符,使用 read 命令读取文件中的第一行,然后将这一行显示在 STDOUT 上。 后,它用 echo 语句将一行数据写入由同一个文件描述符打开的文件中。 在运行脚本时,一开始还算正常。输出内容表明脚本读取了 testfile 文件中的第一行。但如果 你在脚本运行完毕后,查看 testfile 文件内容的话,你会发现写入文件中的数据覆盖了已有的数据。

             当脚本向文件中写入数据时,它会从文件指针所处的位置开始。read 命令读取了第一行数据,所以它使得文件指针指向了第二行数据的第一个字符。在 echo 语句将数据输出到文件时, 它会将数据放在文件指针的当前位置,覆盖了该位置的已有数据。 

         7.5  关闭文件描述符

               如果你创建了新的输入或输出文件描述符,shell 会在脚本退出时自动关闭它们。然而在有些情况下,你需要在脚本结束前手动关闭文件描述符。

                要关闭文件描述符,将它重定向到特殊符号 &-  

                           exec   3>&-

                该语句会关闭文件描述符 3。不再在脚本中使用它。这里有个例子来说明当你尝试使用已关闭的文件描述符时会怎样

                       

                       

             一旦关闭了文件描述符,就不能在脚本中向它写入任何数据,否则 shell 会生成错误消息。

             在关闭文件描述符时还要注意另一件事。如果随后你在脚本中打开了同一个输出文件,shell 会用一个新文件来替换已有文件。这意味着如果你输出数据,它就会覆盖已有文件。

              

                  

             在向 test17file 文件发送一个数据字符串并关闭该文件描述符之后,脚本用了 cat 命令来显示 文件的内容。到目前为止,一切都还好。下一步,脚本重新打开了该输出文件并向它发送了另一 个数据字符串。当显示该输出文件的内容时,你所能看到的只有第二个数据字符串。shell 覆盖了 原来的输出文件

 8、列出打开的文件描述符

            lsof 命令会列出整个 linux 系统打开的所有文件描述符,这是个有争议的功能,因为它会向非系统管理员提供 linux 系统的信息,鉴于此,许多 linux 系统隐藏了该命令,这样用户就不会一不小心就发现了。

            在许多 linux 系统中,lsof 命令位于 / usr/sbin 目录,要想以普通用户账户来运行它,必须通过全路径名来引用。

            $/usr/sbin/lsof

            该命令会产生大量的输出,它会显示当前 linux 系统上打开的每个文件的有关信息,包括后台运行的所有进程以及登录到系统的任何用户

            有大量的命令行选项和参数可以用来帮助过滤 lsof 的输出,最常用的有 - p 和 - d,前者允许指定进程 ID(PID),后者允许指定要显示的文件描述符编号

            要想知道进程的当前 PID,可以用特殊环境变量 $$(shell 会将它设为当前 PID)。-a 选项用来对其他两个选项的结果执行布尔 AND 运算,这会产生如下输出(本人安装的)

               

          上例显示了当前进程(bash shell )的默认文件描述符(0,1,2)。lsof 的默认输出中有 7 列信息,如下表所示

    列                           描述

COMMAND          正在运行的命令名的前 9 个字符

PID                       进程的 PID

USER                   进程属主的登录名

FD                        文件描述符号以及访问类型(r 代表读,w 代表写,)

TYPE                   文件的类型

DEVICE               设备的设备号

SIZE                     如果有的话,表示文件的大小

NODE                  本地文件的节点号

NAME                   文件名

                     与 STDIN、STDOUT 和 STDERR 关联的文件类型是字符型。因为 STDIN、STDOUT 和 STDERR 文 件描述符都指向终端,所以输出文件的名称就是终端的设备名。所有 3 种标准文件都支持读和写 (尽管向 STDIN 写数据以及从 STDOUT 读数据看起来有点奇怪)。 

                             

                             

               该脚本创建了 3 个替代性文件描述符,两个作为输出(3 和 6),一个作为输入(7)。在脚本 运行 lsof 命令时,可以在输出中看到新的文件描述符。我们去掉了输出中的第一部分,这样你 就能看到文件名的结果了。文件名显示了文件描述符所使用的文件的完整路径名。它将每个文件 都显示成 REG 类型的,这说明它们是文件系统中的常规文件。 

9、阻止命令输出

        有时候,你可能不想显示脚本的输出。这在将脚本作为后台进程运行时很常见(参见第 16 章)。如果在运行在后台的脚本出现错误消息,shell 会通过电子邮件将它们发给进程的属主。这 会很麻烦,尤其是当运行会生成很多烦琐的小错误的脚本时。

        要解决这个问题,可以将 STDERR 重定向到一个叫作 null 文件的特殊文件。null 文件跟它的名 字很像,文件里什么都没有。shell 输出到 null 文件的任何数据都不会保存,全部都被丢掉了。

       在 Linux 系统上 null 文件的标准位置是 / dev/null。你重定向到该位置的任何数据都会被丢掉, 不会显示。

                 

       也可以在输入重定向中将 / dev/null 作为输入文件。由于 / dev/null 文件不含有任何内容,程序员 通常用它来快速清除现有文件中的数据,而不用先删除文件再重新创建。

                  

         文件 testfile 仍然存在系统上,但现在它是空文件。这是清除日志文件的一个常用方法,因为 日志文件必须时刻准备等待应用程序操作。     

10、创建临时文件

        Linux 系统有特殊的目录,专供临时文件使用。Linux 使用 / tmp 目录来存放不需要永久保留的 文件。大多数 Linux 发行版配置了系统在启动时自动删除 / tmp 目录的所有文件。

        系统上的任何用户账户都有权限在读写 / tmp 目录中的文件。这个特性为你提供了一种创建临时文件的简单方法,而且还不用操心清理工作。

        有个特殊命令可以用来创建临时文件。mktemp 命令可以在 / tmp 目录中创建一个唯一的临时 文件。shell 会创建这个文件,但不用默认的 umask 值(参见第 7 章)。它会将文件的读和写权限分配给文件的属主,并将你设成文件的属主。一旦创建了文件,你就在脚本中有了完整的读写权限, 但其他人没法访问它(当然,root 用户除外)。 

          10.1   创建本地临时文件

                默认情况下,mktemp 会在本地目录中创建一个文件。要用 mktemp 命令在本地目录中创建一 个临时文件,你只要指定一个文件名模板就行了。模板可以包含任意文本文件名,在文件名末尾 加上 6 个 X 就行了。

                    

              mktemp 命令会用 6 个字符码替换这 6 个 X,从而保证文件名在目录中是唯一的。你可以创建多个临时文件,它可以保证每个文件都是唯一的。  

             如上例所示,mktemp 命令的输出正是它所创建的文件的名字。在脚本中使用 mktemp 命令时,可能要将文件名保存到变量中,这样就能在后面的脚本中引用了。 

              

                 

           这个脚本用 mktemp 命令来创建临时文件并将文件名赋给 $tempfile 变量。接着将这个临时 文件作为文件描述符 3 的输出重定向文件。在将临时文件名显示在 STDOUT 之后,向临时文件中写 入了几行文本,然后关闭了文件描述符。后,显示出临时文件的内容,并用 rm 命令将其删除 

        10.2   在 / tmp 目录创建临时文件 
        -t  选项会强制 mktemp 命令来在系统的临时目录来创建该文件。在用这个特性时,mktemp 命令会返回用来创建临时文件的全路径,而不是只有文件名。

              

        10.3   创建临时目录 

         -d 选项告诉 mktemp 命令来创建一个临时目录而不是临时文件。这样你就能用该目录进行任何需要的操作了,比如创建其他的临时文件。 

               

             

         10.4  记录消息

          将输出同时发送到显示器和日志文件,这种做法有时候能够派上用场。你不用将输出重定向两次,只要用特殊的 tee 命令就行。 tee 命令相当于管道的一个 T 型接头。它将从 STDIN 过来的数据同时发往两处。

         一处是 STDOUT,另一处是 tee 命令行所指定的文件名: 

                      tee filename

          由于 tee 会重定向来自 STDIN 的数据,你可以用它配合管道命令来重定向命令输出。 

         

          -a 选项将数据追加到文件中        

        

 11、小结

          在创建脚本时,理解了 bash shell 如何处理输入和输出会给你带来很多方便。你可以改变脚本 获取数据以及显示数据的方式,从而在任何环境中定制脚本。脚本的输入 / 输出都可以从标准输 入(STDIN)/ 标准输出(STDOUT)重定向到系统中的任意文件。除了 STDOUT,你可以通过重定 向 STDERR 输出来重定向由脚本产生的错误消息。这可以通过重定向与 STDERR 输出关联的文件描 述符(也就是文件描述符 2)来实现。可以将 STDERR 输出和 STDOUT 输出到同一个文件中,也可 以输出到完全不同的文件中。这样就可以将脚本的正常消息同错误消息分离开。

         bash shell 允许在脚本中创建自己的文件描述符。你可以创建文件描述符 3~9,并将它们分配 给要用到的任何输出文件。一旦创建了文件描述符,你就可以利用标准的重定向符号将任意命令 的输出重定向到那里。

         bash shell 也允许将输入重定向到一个文件描述符,这给出了一种将文件数据读入到脚本中的 简便途径。你可以用 lsof 命令来显示 shell 中在用的文件描述符。

         Linux 系统提供了一个特殊的文件(称为 / dev/null)来重定向不需要的输出。Linux 系统会删 掉任何重定向到 / dev/null 文件的东西。你也可以通过将 / dev/null 文件的内容重定向到一个文件中来 产生空文件。

         mktemp 命令是 bash shell 中一个很方便的特性,可以轻松地创建临时文件和目录。只需要给 mktemp 命令指定一个模板,它就能在每次调用时基于该文件模板的格式创建一个唯一的文件。 也可以在 Linux 系统的 / tmp 目录创建临时文件和目录,系统启动时会清空这个特殊位置中的内容。

        tee 命令便于将输出同时发送给标准输出和日志文件。这样就可以在显示器上显示脚本的消 息的同时,又能将它们保存在日志文件中。