Linux脚本编程——呈现数据

本章内容:

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

理解输入和输出

显示输出的方法有:

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

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

标准文件描述符

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

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

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

STDIN

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

1
2
3
4
5
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文件的输入。

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

STDOUT

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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)放在重定向符号前。

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

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

  2. 重定向错误和数据

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

    1
    2
    3
    4
    5
    6
    7
    8
    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提供了符号&>

    1
    2
    3
    4
    5
    6
    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。

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

1
echo "This is an error message" >&2

下面看个例子:

1
2
3
4
5
6
wangsx@SC-201708020022:~$ cat test8
#!/bin/bash
# testing STDERR message

echo "This is an error" >&2
echo "This is normal output"

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

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

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

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

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

永久重定向

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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个来操作。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

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

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

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

阻止命令输出

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

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

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

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

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

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

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

1
2
3
4
5
6
7
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即可。

1
2
3
4
5
6
7
8
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命令时,可能要将文件名保存到变量中,这样就可以在脚本后面引用了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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命令在系统的临时目录来创建该文件,它会返回临时文件的全路径,而不是只有文件名。

1
2
3
4
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命令行所指定的文件名。

1
2
3
4
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数据库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/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文件

1
2
3
4
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

运行脚本

1
2
3
4
5
6
7
8
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');
文章目录
  1. 1. 理解输入和输出
    1. 1.1. 标准文件描述符
    2. 1.2. 重定向错误
  2. 2. 在脚本中重定向输出
    1. 2.1. 临时重定向
    2. 2.2. 永久重定向
  3. 3. 阻止命令输出
  4. 4. 创建临时文件
    1. 4.1. 创建本地临时文件
  5. 5. 记录消息
  6. 6. 实例
|