Presenting-Data
本章内容:
- Redirecting Output in Scripts
- Redirecting Input in Scripts
- Creating Your Own Redirection
- Suppressing Command Output
- Using Temporary Files
- Logging Messages
# Redirecting Output in Scripts
先来回顾一下重定向:
- 分别将标准错误和标准输出重定向到不同的地方
$ ls
test1 test2
$ ls -al test1 badtest test2 badtest2
ls: cannot access 'badtest': No such file or directory
ls: cannot access 'badtest2': No such file or directory
-rw-rw-r-- 1 tartarus tartarus 0 May 28 23:50 test1
-rw-rw-r-- 1 tartarus tartarus 0 May 28 23:50 test2
$ ls -al test1 badtest test2 badtest2 2> error.log 1> normal.log
$ ls
error.log normal.log test1 test2
$ cat error.log
ls: cannot access 'badtest': No such file or directory
ls: cannot access 'badtest2': No such file or directory
$ cat normal.log
-rw-rw-r-- 1 tartarus tartarus 0 May 28 23:50 test1
-rw-rw-r-- 1 tartarus tartarus 0 May 28 23:50 test2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 将标准错误和标准输出重定向到同一个地方 (标准错误有比标准输出更高的优先级,所以先打印标准错误)
$ ls -al test1 badtest test2 badtest2 &> all.log
$ cat all.log
ls: cannot access 'badtest': No such file or directory
ls: cannot access 'badtest2': No such file or directory
-rw-rw-r-- 1 tartarus tartarus 0 May 28 23:50 test1
-rw-rw-r-- 1 tartarus tartarus 0 May 28 23:50 test2
2
3
4
5
6
重定向到顺序不同,输出的结果也不同 (在 man bash 的 1290 行能看到):
(重定向到某种流时文件描述符前需要加上 & )
将标准错误重定向到标准输出,然后将标准输出重定向到文件中
$ ls test1 test2 $ ls -al test1 badtest1 test2 badtest2 2>&1 1> log ls: cannot access 'badtest1': No such file or directory ls: cannot access 'badtest2': No such file or directory $ cat log -rw-rw-r-- 1 tartarus tartarus 0 May 28 23:50 test1 -rw-rw-r-- 1 tartarus tartarus 0 May 28 23:50 test21
2
3
4
5
6
7
8
9
10注意
这说明
2>&1会将标准错误流重定向到标准输出流然后进行输出到显示器上,然后标准输出流被重定向才输出到文件中。相当于每个流重定向操作都会有输出,然后才进行下一个流的重定向。将标准输出重定向到文件中输出,然后将标准错误重定向到标准
$ ls test1 test2 $ ls -al test1 badtest1 test2 badtest2 >log 2>&1 $ cat log ls: cannot access 'badtest1': No such file or directory ls: cannot access 'badtest2': No such file or directory -rw-rw-r-- 1 tartarus tartarus 0 May 28 23:50 test1 -rw-rw-r-- 1 tartarus tartarus 0 May 28 23:50 test21
2
3
4
5
6
7
8
9
10注意
这说明
>log会将标准输出流重定向到文件 log 中, 然后标准错误流被重定向到了标准输出流中,而此时标准输出流已经被重定向到了文件中。所以标准输出流和标准错误流都被重定向到了 log 文件中。等价于:ls -al test1 badtest1 test2 badtest2 &>log
在脚本中的输出重定向有两种方法:
- Temporarily redirecting each line
- Permanently redirecting all commands in the script
# Temporarily redirecting each line
如果需要在脚本中生成错误信息放到标准错误流中:
$ cat script.sh
#!/bin/bash
# testing STDERR messages
echo "This is an error">&2
echo "This is normal output"
$ ./script.sh
This is an error
This is normal output
$ ./script.sh > normal.log 2> error.log
$ cat normal.log
This is normal output
$ cat error.log
This is an error
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Permanent redirections
- 使用
exec将某一种流 (标准输出流或者标准错误流) 长期的重定向到文件或者另一种流中。
$ cat script.sh
#!/bin/bash
# redirecting all output to a file
exec >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"
$ ./script.sh
$ cat testout
This is a test of redirecting all output
from a script to another file.
without having to redirect every individual line
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 在脚本运行中间,也可以修改某一条命令的重定向:
$ cat script.sh
#!/bin/bash
# redirecting all output to a file
exec >testout
echo "This is a test of redirecting all output"
echo "from a script to another file.">&2 # 这一条命令的标准输出别重定向到了标准错误流,因为此时标准错误流没被重定向,所以会输出到🖥️上。
echo "without having to redirect every individual line"
$ ./script.sh
from a script to another file.
$ cat testout
This is a test of redirecting all output
without having to redirect every individual line
2
3
4
5
6
7
8
9
10
11
12
13
14
15
但是着存在一个问题是,一旦使用了 exec 进行重定向,就很难将输出复原到原本的输出流上。
解决办法:见 Creating Your Own Redirection
# Redirecting Input in Scripts
使用输入重定向从文件中读取输入,比如在 read from file (opens new window) 中,我们就使用管道从文件中读取数据到 read 的输入流中,这里还有另一种方法可以实现:
$ cat log
This is the first line.
This is the second line.
This is the third line.
$ cat script.sh
#!/bin/bash
# redirecting file input
exec 0< log
count=1
while read line
do
echo "Line #$count: $line"
count=$[ $count + 1 ]
done
echo "Line Total: $count"%
$ ./script.sh
Line #1: This is the first line.
Line #2: This is the second line.
Line #3: This is the third line.
Line Total: 4
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Creating Your Own Redirection
在 bash 中有 9 个 file descriptor (文件描述符,从 0 到 8),其中 0,1,2 为系统指定描述符,其余的描述符都可以人文指定。
# Creating output file descriptors
(用 script 记录 log 时非常有用)
使用方法:
- 重定向一个文件描述符到文件中
- 重定向命令的标准输出 / 标准错误输出到
1.中指定的文件描述符
优点:
这样做,可以让没有进行输出重定向的命令的输出打印到显示器,重定向到自定的文件描述符的命令可以将结果输出到文件 (比如 log file) 中。
举例:
$ cat script.sh
#!/bin/bash
# using an alternative file descriptor
exec 3>log # 如果想要将运行结果添加到一个已经存在的文件中 exec 3>>log
echo "This should display on the monitor"
echo "and this should be stored in the file">&3
echo "Then this should be back on the monitor"
$ ./script.sh
This should display on the monitor
Then this should be back on the monitor
$ cat log
and this should be stored in the file
2
3
4
5
6
7
8
9
10
11
12
13
14
# Redirecting file descriptors
通过引入临时文件描述符,临时的重定向标准输出 / 标准错误输出到文件,然后之后又恢复他们默认的输出位置。
举例:将标准输出重定向到文件,执行一些命令后又重定向回显示器。
$ cat script.sh
#!/bin/bash
# storing STDOUT, then coming back to it
# 将文件描述符3重定向到标准输出,所有重定向到3的流都会在显示器上打印
exec 3>&1
# 将标准输出流重定向到文件中,但是文件描述符3还是指向原来的位置,即显示器
exec 1>testout
echo "This should store in the output file"
echo "along with this line."
# 将标准输出重新重定向回文件描述符3指向的位置,即显示器
exec 1>&3
echo "Now things should be back to normal"%
$ ./script.sh
Now things should be back to normal
$ cat testout
This should store in the output file
along with this line.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(文件描述符 3 在上面 shell 中的作用类似于 C 语言中交换两个变量时使用的临时变量。)
# Creating input file descriptors
通过引入临时文件描述符,临时的将文件重定向到标准输入,然后之后又恢复标准输入从键盘输入。
$ cat script.sh
#!/bin/bash
# redirecting input file descriptors
# 将标准输入重定向到文件描述符6, 所有从键盘的输入都会到文件描述符6中
exec 6<&0
# 将文件log重定性到标准输入
exec 0< log
count=1
# read从log读取输入
while read line
do
echo "Line #$count: $line"
count=$[ $count + 1 ]
done
# 6重定向到标准输入,所有从键盘的输入又会回到标准输入中
exec 0<&6
read -p "Are you done now? " answer
case $answer in
Y|y) echo "Goodbye";;
N|n) echo "Sorry, this is the end.";;
esac
$ ./script.sh
Line #1: This is the first line.
Line #2: This is the second line.
Line #3: This is the third line.
Are you done now? y
Goodbye
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Creating a read/write file descriptor
创建一个可读可写的文件描述符:
$ cat script.sh
#!/bin/bash
# testing input/output file descriptor
# read data from testfile: 3< testfile
# write data to testfile: 3> testfile
exec 3<> testfile
read line <&3
echo "Read: $line"
# 在读或者写如一个文件时,维护了一个指针指向当前读或者写的位置。
echo "This is a test line">&3
$ cat testfile
This is the first line.
This is the second line.
This is the third line.
$ ./script.sh
Read: This is the first line.
$ cat testfile
This is the first line.
This is a test line
ine.
This is the third line.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Closing file descriptors
脚本运行结束后,文件描述符自动消除。但是,有时也需要在脚本结束前关闭文件描述符:
将文件描述符重定向到 $-
# 关闭问价描述符3
exec 3>&-
2
使用举例:
$ cat script.sh
#!/bin/bash
# testing closing file descriptors
exec 3> testfile
echo "This is a test line of data">&3
exec 3>&-
echo "This won't execute">&3
$ ./script.sh
./script.sh: line 6: 3: Bad file descriptor
$ cat testfile
This is a test line of data
2
3
4
5
6
7
8
9
10
11
12
13
# Suppressing Command Output
/dev/null
# Using Temporary Files
/tmp
在当前目录下创建一个临时文件:
# X表示产生的文件名后缀是随机的
mktemp filenameX..X
2
在 /tmp 文件夹下创建一个临时文件:
mktemp -t filenameX..X
使用举例:
$ cat script.sh
#!/bin/bash
# creating a temp file in /tmp
tempfile=$(mktemp -t tmp.XXXXXX)
echo "This is a test file."> $tempfile
$ ./script.sh
$ cat tmp.bvjnZ4
This is a test file.
2
3
4
5
6
7
8
9
10
- 在当前文件夹下创建一个临时文件夹
mktemp -d dir.XXXXXX
- 在 /tmp 文件夹下创建一个临时文件夹
mktemp -t -d dir.XXXXXX
# Logging Messages
使用 tee 命令将同时输出到文件和屏幕。
$ cat script.sh
#!/bin/bash
# using the tee command for logging
tempfile=testfile
echo "This is the start of the test" | tee $tempfile
# -a: append to the given files, do not overwrite
echo "This is the second line of the test" | tee -a $tempfile
echo "This is the end of the test" | tee -a $tempfile
$ ./script.sh
This is the start of the test
This is the second line of the test
This is the end of the test
$ cat testfile
This is the start of the test
This is the second line of the test
This is the end of the test
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# All in One Example
从命令行参数传入一个 csv 文件转化为 MySQL 格式:
❯ cat script.sh
#!/bin/bash
# read file and create INSERT statements for MySQL
echo "$1"
outfile='members.sql'
IFS=','
while read lname fname address city state zip
do
cat>> $outfile << EOF
INSERT INTO members
(lname,fname,address,city,state,zip) VALUES
('$lname', '$fname', '$address', '$city', '$state', '$zip');
EOF
# 标识符EOF要放在行首,才能作为结束符
done < ${1}%
❯ cat members.csv
Blum,Richard,123 Main St.,Chicago,IL,60601
Blum,Barbara,123 Main St.,Chicago,IL,60601
Bresnahan,Christine,456 Oak Ave.,Columbus,OH,43201
Bresnahan,Timothy,456 Oak Ave.,Columbus,OH,43201
❯ ./script.sh members.csv
members.csv
❯ 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', 'Christine', '456 Oak Ave.', 'Columbus', 'OH', '43201 ');
INSERT INTO members
(lname,fname,address,city,state,zip) VALUES
('Bresnahan', 'Timothy', '456 Oak Ave.', 'Columbus', 'OH', '43201');
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36