# 简介
之前关于标准输入,输出重定向的文章,我提到开始接触流,并且在文中讲解时使用也是以流来具体化重定向的作用,但是讲解的非常不严谨。本文相当于对重定向深入讲解,甚至可以自定义重定向。
# 标准文件描述符
Linux
系统将每个对象当作文件处理(有句话就是一切皆文件),包括输入和输出,用文件描述符标识每个文件对象。文件描述符是一个非负整数,可以唯一表示会话中打开的文件。出于特殊目的, bash shell
在每个进程,只保留了 3 个文件标识符 (0,1,2)
STDIN
-- 标准输入。对于终端界面,标准输入是键盘。shell
从STDIN
对应的键盘获得输入。使用输入重定向符号<
,Linux
会使用重定向指定的文件来替换标准文件描述符,会读取文件并提取数据,如同是在键盘上键入的。STDOUT
-- 标准输出。同标准输入类似,shell 所有输出都会被定向到标准输出中,也就是显示器。STDERR
-- 错误消息对于错误消息,也是重定向到显示器的,但是错误消息和一般标准输出是分开的。比如:ls -l badfile > test
,如果没有badfile
就会报错,但是错误信息并没有给test
。
我们之前知道如何重定向标准输入和标准输出,现在讲解如何重定向错误信息。
- 只重定向错误:将
STDERR
文件描述符的值(2)
紧紧放在重定向符号前,ls -al badfile 2> test
。但是这种方法,如果该命令的输出既包含错误信息,也包含标准输出,那么标准输出还是会输出到屏幕中。 - 重定向错误和数据:我们肯定不希望重定向的数据和错误信息在同一文件中(难道你希望错误日志里面还给你保存几句莫名其妙的打印语句吗),所以必须使用两个重定向符号,需要在符号面前放上各自文件描述符值:
ls -al test test2 test3 badtest 2> testSTDERR 1> testSTDOUT
(其中test*
文件都存在,badtest
不存在)。 当然,也可以将数据和错误重定向到同一文件:ll n_File bad_File new_File &> STD_ALL
。bash shell
自动赋予了错误消息更高的优先级,方便集中浏览错误信息,也就是说,ll A B C
中B
文件不存在,那么输出时会优先输出关于B
的错误信息。
# 脚本中的标准重定向输出和输入
对于三个标准文件描述符,一定要记住,使用 exec
命令时数字放在箭头左边。当数字出现在右边,要用 &
符号。
# 临时重定向
在脚本中生成错误消息,可以将单独的一行输出重定向到 STDERR
,不同的是,在脚本中,格式为 >&2
。 echo "This is error" >&2
,这样,该行就会指向 STDERR
的位置,也就类似于终端上的错误信息一样。
这种操作可以用于检测脚本运行时传入的选型或者参数是否正确,如果不正确就可以通过 >&2
来生成错误信息。
cat test | |
#!/bin/bash | |
echo "This is error" >&2 | |
echo "This is normal" | |
# 运行脚本,并将脚本的错误信息重定向到 result1 文件中 | |
./test 2> result1 | |
This is normal | |
cat result1 | |
This is an error |
# 永久重定向
上面的临时重定向,比如我一个脚本有很多行生成错误信息并需要重定向,而每一行都是用 >&2
太麻烦了,那么就可以使用 exec
命令来进行永久重定向。
exec
命令告诉 shell
在脚本中执行期间重定向某个特定文件描述符。
#!/bin/bash | |
exec 1> STD_output_file | |
.... | |
# 那么脚本中该命令之下的所有标准输出都重定向到了 STD_output_file 中 | |
# 同理,改变标准输入,这对于从待处理文件中读取数据有很大帮助 | |
exec 0< STD_input_file |
# 自定义重定向
Linux
系统本来每个进程都有 9 个文件描述符,3 个标准,剩下 6 个都可以用于自定义,这 6 个可以任意作为输入还是输出。
exec 6> testout | |
echo "This is a data">&6 | |
# 如果使用 >> 就是追加模式 |
如果你想恢复一个被重定向的文件描述符
exec 3>&1 #3 指向 1 | |
exec 1>testout | |
..... | |
exec 1>&3 |
创建读写文件描述符
exec 4<> testfile | |
# 输入输出时,文件指针是共享的。同时注意 >> 的追加模式与该特性的使用 |
关闭文件描述符时,一般创建了新的输入输出文件描述符,脚本退出时, shell
就会自动关闭它们。手动关闭: exec 3>&-
。一旦关闭,就不能在写入 / 读取数据,否则就会报错。
# lsof 命令
网上给的知识有些凌乱,甚至有些错误,所以我会总结几篇文章,最后给一下参考。
lsof--list open files
,列出当前系统已经打开的所有文件,一般 lsof
命令位于 /usr/bin/losf
或者是 /usr/sbin/lsof
。因为终端运行时,会有很多文件被打开使用,如果直接使用 lsof
会出现很多结果,我们在使用时一定要灵活使用相关的选项来控制输出。
选项 | 描述 |
---|---|
-a | 对给的选项进行与运算 |
-p<pid> | 列出指定进程号所打开的文件 |
-d <文件号> | 列出占用该文件号的文件 |
+d <目录> | 列出目录下被打开的文件 |
-c <进程名> | 列出指定进程所打开的文件 |
根据上面的部分选项,我们使用一下该命令
lsof -a -p $$ -d 0,1,2 | |
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME | |
bash 29075 cyan 0u CHR 136,0 0t0 3 /dev/pts/0 | |
bash 29075 cyan 1u CHR 136,0 0t0 3 /dev/pts/0 | |
bash 29075 cyan 2u CHR 136,0 0t0 3 /dev/pts/0 | |
# 变量 $$ 表示当前 shell 的 pid | |
# COMMAND: 正在运行的命令名的前 9 个字符 | |
# FD: 文件描述符号以及访问类型 (r-- 读,w-- 写,u-- 读写) | |
# TYPE: 文件的类型 (CHR-- 字符型,BLK-- 块型,DIR-- 目录,REG-- 常规文件) | |
# NAME:文件描述符所使用的文件的完整路径名 |
现在编写一个简单的脚本 test
,本文主要是讲解文件描述符。
#!/bin/bash | |
exec 4> four_data | |
while [ $var -eq 10] #死循环 | |
do | |
var=10 | |
done |
使用 &
将该脚本置入后台运行: ./test&
cyan@cyan-virtual-machine:~/Templates$ ./test& | |
[1] 88214 | |
cyan@cyan-virtual-machine:~/Templates$ ps | |
PID TTY TIME CMD | |
88084 pts/3 00:00:00 bash | |
88214 pts/3 00:00:19 test | |
88218 pts/3 00:00:00 ps |
再使用 lsof
来查看使用了哪些文件描述符
cyan@cyan-virtual-machine:~/Templates$ lsof -a -p 88214 -d 4 | |
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME | |
test 88214 cyan 4w REG 8,5 0 935362 /home/cyan/Templates/four_data |
-p 88214
是寻找进程为 pid=88214
的进程打开的文件, -d 4
找到使用 4
文件描述符的文件, -a
对 -p 88214
和 -d 4
进行与运算。
所以,你也可以尝试以下命令
lsof -p pid | |
# 查看这个进程的文件到底占用了哪些描述符 |
完成案例讲解,将 test
的进程杀死
cyan@cyan-virtual-machine:~/Templates$ kill 88214 | |
cyan@cyan-virtual-machine:~/Templates$ ps | |
PID TTY TIME CMD | |
88084 pts/3 00:00:00 bash | |
88243 pts/3 00:00:00 ps | |
[1]+ Terminated ./test |
lsof
命令很强大,这里我们只是用来查看文件描述符,如果你想要深入了解,可以参考以下文章
Linux 查看端口占用情况:https://www.runoob.com/w3cnote/linux-check-port-usage.html
lsof 命令详解:https://www.cnblogs.com/klausage/p/14995042.html (其实并不是很详细)
lsof 入门:https://www.qieseo.com/162896.html
# 阻止命令输出
Linux
有一个文件叫 null(/dev/null)
, shell
输出到 null
文件的任何数据都不会被保存,全部都被丢掉。所以,不希望后台输出错误信息时, shell
发送电子邮件给进程属主的话,就将 STDERR
重定向到 null
。 null
文件可以快速清除现有文件中的数据而不需要删除文件再重新创建 cat /dev/null > testfile
,类似于清空日志文件。哦,其实我个人更喜欢 echo "" > testfile
。
# 创建临时文件
/tmp
目录是 Linux
用来存放不永久保留的文件,大部分 Linux
发行版配置了系统在启动时自动删除 /tmp
目录的所有文件。任何用户账户都有权限在 /tmp
读写。
单独 mktemp
命令可以在 /tmp
目录中创建一个唯一的临时文件。 shell
会创建该文件,但不是使用默认的 umask
,会将文件读和写权限分给属主,并将你设为属主。但是其他人没法访问 (root 除外)。
cyan@cyan-virtual-machine:~/Templates$ mktemp | |
/tmp/tmp.thPVXNPVJp |
也可以自定义文件名,但是会在当前目录下创建该文件
# mktemp 会用 6 个字符替换 6 个 X,保证文件名唯一 | |
cyan@cyan-virtual-machine:~/Templates$ mktemp test.XXXXXX | |
test.k5CvV0 | |
# 在脚本中,一般将创建的文件名保存在变量中 | |
filename=$(mktemp test.XXXXXX) |
还可以使用选项
# 在 /tmp 下创建,返回全路径名 | |
cyan@cyan-virtual-machine:~/Templates$ mktemp -t test.XXXXXX | |
/tmp/test.LRNlpY | |
# -d 选项创建临时目录 | |
cyan@cyan-virtual-machine:~/Templates$ mktemp -d dir.XXXXXX | |
dir.Adr0Q6 | |
cyan@cyan-virtual-machine:~/Templates$ mktemp -d -t dir.XXXXXX | |
/tmp/dir.H9rIut |
# 记录消息
tee
命令:
相当于管道的 T
型接头,将从 STDIN
过来的数据同时发送到 STDOUT
和 tee
命令行所指定的文件名: tee testfile
。
cyan@cyan-virtual-machine:~/Templates$ date | tee testfile | |
2022年 08月 03日 星期三 15:51:57 CST | |
cyan@cyan-virtual-machine:~/Templates$ cat testfile | |
2022年 08月 03日 星期三 15:51:57 CST |
tee
每次都会覆盖文件内容, -a
选型是追加模式: who | tee -a testfile
。
# 实例
下面脚本涉及 sql
语句,如果你不会,可以跳过。该脚本主要是从.csv 文件中读取数据快速生成 sql
执行文件。
#!/bin/bash | |
# 文件名 create_sql_file | |
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 | |
done < ${1} # read 的标准输入重定向到 ${1} 文件。 |
直接使用 cat << file
可以追加数据到文件中,然后使用 ctrl+d
结束。也可以使用 << 结束符
,通过输入结束符来结束输入。
我们写一个 mebers.csv
数据
cyan@cyan-virtual-machine:~/Templates$ cat members.csv | |
Cyan,Jack,US,qiqo,iasui,iwah | |
Mike,Smith,Japen,isaui,uwif,ianas |
然后运行脚本生成 sql
文件。
cyan@cyan-virtual-machine:~/Templates$ ./create_sql_file members.csv | |
cyan@cyan-virtual-machine:~/Templates$ cat members.sql | |
INSERT INTO members (lname,fname,address,city,state,zip) VALUES | |
('Cyan','Jack','US','qiqo','iasui','iwah') | |
INSERT INTO members (lname,fname,address,city,state,zip) VALUES | |
('Mike','Smith','Japen','isaui','uwif','ianas') |