美文网首页我的专题诗翔的Linux学习之路生物信息学与算法
【shell笔记>脚本】重定向、标准输入输出与错误

【shell笔记>脚本】重定向、标准输入输出与错误

作者: 王诗翔 | 来源:发表于2017-08-22 00:08 被阅读101次

    本章内容:

    • 再探重定向
    • 标准输入和输出
    • 报告错误
    • 丢弃数据
    • 创建日志文件

    理解输入和输出

    显示输出的方法有:

    • 在显示器屏幕上输出
    • 将输出重定向到文件中
    • 有时将一部分数据显示在显示器上;一部分保存到文件中。

    之前涉及的脚本都是以第一种方式输出。现在我们来具体了解下输入和输出。

    标准文件描述符

    Linux系统将每个对象当作文件处理。着包括输入和输出进程。而标识文件对象是通过文件描述符完成的。文件描述符是一个非负整数,可以唯一标识会话中打开的文件。每个进程一次最多有九个文件描述符,bash shell保留勒前三个(0,1,2),见下表。

    文件描述符 缩写 描述
    0 STDIN 标准输入
    1 STDOUT 标准输出
    2 STDERR 标准错误

    shell用他们将shell默认的输入和输出导向到相应的位置。

    STDIN

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

    wangsx@SC-201708020022:~/tmp$ cat
    this is a test
    this is a test
    this is a second test
    this is a second test
    

    输入cat命令时,它从STDIN接受输入。输入一行,cat命令会显示一行。

    当然也可以通过<符号强制cat命令接受来自另一个非STDIN文件的输入。

    wangsx@SC-201708020022:~$ cat < testfile
    This is the first line.
    This is the second line.
    This is the third line.
    

    STDOUT

    标准输出就是终端显示器。我们可以使用<输出重定向符号来改变它。

    wangsx@SC-201708020022:~$ ls -l > test2
    wangsx@SC-201708020022:~$ cat test2
    总用量 28
    drwxrwxrwx 0 wangsx wangsx 4096 8月   2 11:48 biosoft
    -rw-rw-rw- 1 wangsx wangsx 2156 8月   4 00:12 biostar.yml
    drwxrwxrwx 0 wangsx wangsx 4096 8月   3 22:24 miniconda2
    drwxrwxrwx 0 wangsx wangsx 4096 8月   2 11:50 ncbi
    -rw-rw-rw- 1 wangsx wangsx 5230 8月  14 00:14 spf13-vim.sh
    drwxrwxrwx 0 wangsx wangsx 4096 8月  13 23:51 src
    -rw-rw-rw- 1 wangsx wangsx    0 8月  21 22:43 test2
    -rw-rw-rw- 1 wangsx wangsx   73 8月  21 22:39 testfile
    drwxrwxrwx 0 wangsx wangsx 4096 8月  19 17:15 tmp
    -rw-rw-rw- 1 wangsx wangsx 2156 8月  14 12:20 wsx_biostar.yml
    

    如果文件存在,>符号会将导向的文件全部重写。如果想要以追加的形式,则使用>>

    STDERR

    STDERR文件描述符代表shell的标准错误输出。运行脚本或命令的错误信息都会发送到这个位置。

    默认,STDERR会和STDOUT指向同样的地方(屏幕终端)。使用脚本时,我们常常会希望将错误信息保存到日志文件中。

    重定向错误

    几种实现方法:

    1. 只重定向错误

      将文件描述符值(2)放在重定向符号前。

      wangsx@SC-201708020022:~$ ls -al badfile 2> test4
      wangsx@SC-201708020022:~$ cat test4
      ls: 无法访问'badfile': 没有那个文件或目录
      

      命令生成的任何错误信息都会保存在输出文件中。这个方法只重定向错误信息。

    2. 重定向错误和数据

      这必须用两个重定向符号。需要在符号前面放上待重定向数据所对应的文件描述符,然后指向用于保存数据的输出文件。

      wangsx@SC-201708020022:~$ ls -al test test2 test3 bad test 2> test6 1> test7
      wangsx@SC-201708020022:~$ cat test6
      ls: 无法访问'test': 没有那个文件或目录
      ls: 无法访问'test3': 没有那个文件或目录
      ls: 无法访问'bad': 没有那个文件或目录
      ls: 无法访问'test': 没有那个文件或目录
      wangsx@SC-201708020022:~$ cat test7
      -rw-rw-rw- 1 wangsx wangsx 571 8月  21 22:43 test2
      

      可以看到正常输出重定向到了test7,错误重定向到了test6。另外,也可以将STDERR和STDOUT的输出重定向到同一个输出文件,bash shell提供了符号&>

      wangsx@SC-201708020022:~$ ls -al test test2 test3 bad &> test7
      wangsx@SC-201708020022:~$ cat test7
      ls: 无法访问'test': 没有那个文件或目录
      ls: 无法访问'test3': 没有那个文件或目录
      ls: 无法访问'bad': 没有那个文件或目录
      -rw-rw-rw- 1 wangsx wangsx 571 8月  21 22:43 test2
      

      使用这个符号的话,bash shell会自动赋予错误消息更高的优先级。这样能够集中浏览错误信息。

    在脚本中重定向输出

    两种方法在脚本中重定向输出:

    • 临时重定向输出
    • 永久重定向脚本中的所有命令

    临时重定向

    如果有意在脚本中生成错误信息,可以将单独的一行输出重定向到STDERR。

    使用时需要在文件描述符数字前加&:

    echo "This is an error message" >&2
    

    下面看个例子:

    wangsx@SC-201708020022:~$ cat test8
    #!/bin/bash
    # testing STDERR message
    
    echo "This is an error" >&2
    echo "This is normal output"
    

    像平常一样运行的话,看出不会有什么差别。

    wangsx@SC-201708020022:~$ sh test8
    This is an error
    This is normal output
    

    但是如果重定向STDERR的话,所有导向STDERR的文本都会被重定向

    wangsx@SC-201708020022:~$ sh test8 2> test9
    This is normal output
    wangsx@SC-201708020022:~$ cat test9
    This is an error
    

    这个方法非常适合在脚本中生成错误信息

    永久重定向

    如果脚本中涉及大量重定向,上述的方法就会非常繁琐。我们可以用exec命令告诉shell在脚本执行期间重定向某个特定文件描述符。

    wangsx@SC-201708020022:~$ cat test10
    #!/bin/bash
    # redirecting all output to a file
    exec 1>testout
    
    echo "This is a test of redirecting all output"
    echo "from a script to another file"
    echo "without having to redirect every individual line"
    wangsx@SC-201708020022:~$ sh test10
    wangsx@SC-201708020022:~$ cat testout
    This is a test of redirecting all output
    from a script to another file
    without having to redirect every individual line
    

    再结合STDERR看看

    wangsx@SC-201708020022:~$ cat test11
    #!/bin/bash
    # redirecting output to different locations
    
    exec 2>testerror
    
    echo "This is the start of the script"
    echo "now redirecting all output to another location"
    
    exec 1>testout
    echo "This output should go to the testout file"
    echo "but this should go to the testerror file" >&2
    wangsx@SC-201708020022:~$ sh test11
    This is the start of the script
    now redirecting all output to another location
    wangsx@SC-201708020022:~$ cat testout
    This output should go to the testout file
    wangsx@SC-201708020022:~$ cat testerror
    but this should go to the testerror file
    

    这个脚本用exec命令将STDERR的输出重定向到文件testerror。接着echo语句向STDOUT显示几行文本。随后使用exec命令将STDOUT重定向到testout文件。最后,虽然STDOUT被重定向了,但依然可以将echo语句发给STDERR。

    这里存在一个问题,一旦重定向了STDOUT或STDERR,就很难将他们重定向回原来的位置。

    这个问题可以用以下方式解决。

    前面提到shell只用了3个文件描述符,而总共有9个,我们可以利用其他6个来操作。

    这里只需要另外使用一个,就可以重定向文件描述符:

    wangsx@SC-201708020022:~$ cat test14
    #!/bin/bash
    # storing STDOUT, the coming back to it
    
    exec 3>&1
    exec 1>test14out
    
    echo "This should store in the output file"
    echo "along with this line"
    
    exec 1>&3
    echo "Now things should be back to normal"
    wangsx@SC-201708020022:~$ sh test14
    Now things should be back to normal
    wangsx@SC-201708020022:~$ cat test14out
    This should store in the output file
    along with this line
    

    这里有意思的是把重定向当程序变量在玩,类似

    a=b # 把b的内容存到a
    b=c # 修改b的内容
    # 使用完后
    b=a # 将b原来的内容还原
    

    输入文件描述符也可以进行类似的操作。

    阻止命令输出

    有时候不想显示脚本的输出就要这么干。

    一种通用的方法是将STDERR重定向到null的特殊文件(里面什么都没有)。shell输出到null文件的任何数据都不会保存,全部被丢掉了。

    null文件在Linux中的标准位置是/dev/null

    wangsx@SC-201708020022:~$ ls -al > /dev/null
    wangsx@SC-201708020022:~$ cat /dev/null
    wangsx@SC-201708020022:~$
    

    这是避免出现错误消息,也无需保存它们的一个常用方法。

    wangsx@SC-201708020022:~$ ls -al badfile test2 2> /dev/null
    -rw-rw-rw- 1 wangsx wangsx 571 8月  21 22:43 test2
    

    由于null文件不含有任何内容,程序员通常用它来快速清除现有文件中的数据,而不用先删除文件再重新创建。

    wangsx@SC-201708020022:~$ cat testfile
    This is the first line.
    This is the second line.
    This is the third line.
    wangsx@SC-201708020022:~$ cat /dev/null > testfile
    wangsx@SC-201708020022:~$ cat testfile
    wangsx@SC-201708020022:~$
    

    创建临时文件

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

    创建本地临时文件

    默认mktemp会在本地目录创建一个文件。你只需要指定文件名模板,可以是任意文本名,后面加六个X即可。

    wangsx@SC-201708020022:~/tmp$ mktemp testing.XXXXXX
    wangsx@SC-201708020022:~/tmp$ mktemp testing.XXXXXX
    wangsx@SC-201708020022:~/tmp$ ll
    总用量 12
    drwxrwxrwx 0 wangsx wangsx 4096 8月  22 21:34 ./
    drwxr-xr-x 0 wangsx wangsx 4096 8月  22 21:31 ../
    -rw------- 1 wangsx wangsx    0 8月  22 21:33 testing.R6dAku
    -rw------- 1 wangsx wangsx    0 8月  22 21:32 testing.V5psXP
    

    mktemp命令会用6个字符码替换这6个X,从而保证文件名在目录中是唯一的。

    在脚本中使用mktemp命令时,可能要将文件名保存到变量中,这样就可以在脚本后面引用了。

    wangsx@SC-201708020022:~/tmp$ cat test19
    #!/bin/bash t nomore" \
    # creating and using a temp file
    tempfile=$(mktemp test19.XXXXXX)
    
    exec 3>$tempfile
    echo "This script writes to temp file $tempfile
    echo "This is the first line." >&3
    echo "This is the last line." >&3
    exec 3>&-
    
    echo "Done creating temp file. The contents are:"
    cat $tempfile 
    rm -f $tempfile 2> /dev/null
    wangsx@SC-201708020022:~/tmp$ sh test19
    This script writes to temp file test19.fVVEwn
    Done creating temp file. The contents are:
    This is the first line.
    This is the last line.
    

    显示的内容大致如上,我的ubuntu子系统有点怪怪的,不知道为毛。

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

    wangsx@SC-201708020022:~/tmp$ mktemp -t test20.XXXX
    /tmp/test20.bY3Q
    wangsx@SC-201708020022:~/tmp$ mktemp -t test20.XXXXXX
    /tmp/test20.WrkAia
    

    -d选项告诉mktemp创建一个临时目录而不是临时文件。

    记录消息

    有时候想将消息同时发送到显示器和日志文件,用tee命令可以搞定。

    tee命令的功能就像一个T,它将从STDIN过来的数据同时发往两处。一处是STDOUT,一处是tee命令行所指定的文件名。

    wangsx@SC-201708020022:~/tmp$ date | tee testfile
    2017年 08月 22日 星期二 21:49:07 DST
    wangsx@SC-201708020022:~/tmp$ cat testfile
    2017年 08月 22日 星期二 21:49:07 DST
    

    如果要追加文件,请使用-a选项。

    实例

    文件重定向常见于脚本需要读入文件和输出文件时。

    下面是一个具体的实例:shell脚本使用命令行参数指定待读取的.csv文件。.csv格式用于从电子表格中导出数据,所以我们可以把数据库数据放入电子表格,把电子表格保存成.csv格式,读取文件,然后创建INSERT语句将数据插入MySQL数据库。

    #!/bin/bash
    # read file and create INSERT statements for MySQL
    
    outfile='members.sql'
    IFS=","
    while read lname fname address city state zip # read 使用IFS字符解析读入的文本
    do
        cat >> $outfile << EOF
        # >> 将cat的输出追加到$outfile指定的文件中
        # cat的输入不再取自于标准输入,而是被重定向到脚本中存储的数据,EOF符号标记了追加到文件中的数据的起止(两个)
        INSERT INTO members (lname,fname,address,city,state,zip) VALUES 
        ('$lname', '$fname', '$address', '$city', '$state', '$zip');
    EOF
        # 上面是标准的SQL语句
    done < $1 # 将命令行第一个参数指明的数据文件导入while
    

    造一个符合的csv文件

    wangsx@SC-201708020022:~/tmp$ cat members.csv
    Blum,Richard,123 Main St.,Chicago,IL,60601
    Blum,Barbara,123 Main St.,Chicago,IL,60601
    Bresnahan,Timothy,456, Oak Ave.,Columbus,OH,43201
    

    运行脚本

    wangsx@SC-201708020022:~/tmp$ ./test23 members.csv
    wangsx@SC-201708020022:~/tmp$ cat members.sql
        INSERT INTO members (lname,fname,address,city,state,zip) VALUES
        ('Blum', 'Richard', '123 Main St.', 'Chicago', 'IL', '60601');
        INSERT INTO members (lname,fname,address,city,state,zip) VALUES
        ('Blum', 'Barbara', '123 Main St.', 'Chicago', 'IL', '60601');
        INSERT INTO members (lname,fname,address,city,state,zip) VALUES
        ('Bresnahan', 'Timothy', '456', ' Oak Ave.', 'Columbus', 'OH,43201');
    

    相关文章

      网友评论

        本文标题:【shell笔记>脚本】重定向、标准输入输出与错误

        本文链接:https://www.haomeiwen.com/subject/anhkdxtx.html