呈现数据

本文详细介绍了Linuxshell脚本中关于输入输出重定向的概念和实践,包括标准文件描述符(STDIN、STDOUT、STDERR)、重定向错误、临时和永久重定向、创建自定义文件描述符以及使用临时文件等。通过示例展示了如何在脚本中控制命令的输出和错误处理,以实现更灵活的数据管理和日志记录。
摘要由CSDN通过智能技术生成

本章内容:

  • 理解输入和输出
  • 在脚本中重定向输出
  • 在脚本中重定向输入
  • 创建自己的重定向
  • 列出打开的文件描述符
  • 抑制命令输出
  • 使用临时文件
  • 记录消息

一、理解输入和输出

了解 Linux 如何处理输入和输出有助于将脚本输出送往所需的位置。

1.1 标准文件描述符

Linux 用文件描述符来标识每个文件对象

  • 文件描述符是一个非负整数,唯一会标识的是会话中打开的文件。
  • 每个进程一次最多可以打开 9 文件描述符。
  • 出于特殊目的,bash shell 保留了 3 文件描述符。(012
  • Linux 的标准文件描述符如下
文件描述符缩写描述
0STDIN标准输入
1STDOUT标准输出
2STDERR标准错误

1.1.1 STDIN

STDIN 文件描述符代表 shell 的标准输入

  • 对终端界面来说,标准输入就是键盘
  • 在使用输入重定向符(<)时,Linux 会用重定向指定的文件替换标准输入文件描述符。
  • 许多 bash 命令能从 STDIN 接收输入尤其是在命令行中没有指定文件的情况下。
  • 举个例子,使用 cat 命令来处理来自 STDIN 的输入
# 1.cat命令从STDIN接收输入,输入一行,显示一行。
[root@VM-8-11-centos ~]# cat 
one
one
two
two
^Z
[2]+  Stopped                 cat
  • 也可以通过输入重定向符强制 cat 命令接收来自 STDIN 之外的文件输入
# 1.cat命令现在从info.txt文件中获取输入。
[root@VM-8-11-centos testdir]# cat < info.txt 
This is the first line.
This is the second line.
This is the third line.
  • 可以使用这种技术将数据导入任何能从 STDIN 接收数据的 shell 命令中

1.1.2 STDOUT

STDOUT 文件描述符代表 shell 的标准输出

  • 在终端界面上,标准输出就是终端显示器

  • 在默认情况下,大多数 bash 命令会将输出送往 STDOUT 文件描述符,以用输出重定向来更改此设置:

# 1.将原本应该在屏幕上显示的输出内容,重定向到指定文件。
[root@VM-8-11-centos tmp]# ls -l > test.txt

# 2.查看被重定向的文件内容。
[root@VM-8-11-centos tmp]# cat test.txt 
total 8
-rw-rw-r--+ 1 root root    0 Jun 14 16:21 a.txt
-rw-rw-r--+ 1 root root    0 Jun 14 16:21 b.txt
-rw-rw-r--+ 1 root root    0 Jun 14 16:21 c.txt
drwxrwxr-x+ 2 root root 4096 Jun 14 16:21 dir1
drwxrwxr-x+ 2 root root 4096 Jun 14 16:21 dir2
-rw-rw-r--+ 1 root root    0 Jun 20 10:29 test.txt
  • 也可以使用 >> 将数据追加到某个文件。
  • 但是,如果对脚本使用标准输出重定向,就会遇到一个问题
# 1.将错误消息重定向到指定文件。(错误消息依然显示在屏幕上。)
[root@VM-8-11-centos tmp]# ls -lh badfile > test.txt 
ls: cannot access badfile: No such file or directory

# 2.但是,目标文件的内容没有记录错误消息。
[root@VM-8-11-centos tmp]# cat test.txt 
[root@VM-8-11-centos tmp]# 
  • shell 对于错误消息的处理是跟普通输出分开的

1.1.3 STDERR

STDERR 文件描述符代表 shell 的标准错误输出

  • shell 或运行在 shell 中的程序和脚本报错时,生成的错误消息都会被送往这个位置。
  • 默认情况下,STDERR 和 STDOUT 指向同一个地方(尽管二者的文件描述符索引值不同),也就是说,所有的错误消息也都默认会被送往显示器
  • STDERR 并不会随着 STDOUT 的重定向发生改变。

1.2 重定向错误

想重定向错误,只需要在使用重定向符时指定 STDERR 文件描述符就可以了。以下是两种实现方法。

1.2.1 只重定向错误

  • STDERR 的文件描述符2
  • 可以将该文件描述符索引值放在重定向符号之前只重定向错误消息
  • 注意,两者必须紧挨着,否则无法正常工作:
# 1.正常输出被送至显示器,错误消息被重定向(`2>`)到了文件。
[root@VM-8-11-centos tmp]# ls -lh a.txt badfile 2> test.txt 
-rw-rw-r--+ 1 root root 0 Jun 14 16:21 a.txt

# 2.目标文件只记录了错误。
[root@VM-8-11-centos tmp]# cat test.txt 
ls: cannot access badfile: No such file or directory

1.2.2 重定向错误消息和正常输出

  • 如果想重定向错误消息和正常输出,则必须使用两个重定向符号。

  • 对输出进行区分后重定向:

# 1.将命令执行的错误重定向到error.log;将正常输出重定向到info.log。
[root@VM-8-11-centos tmp]# ls a.txt b.txt badfile 2>error.log 1>info.log

# 2.查看记录错误的日志。
[root@VM-8-11-centos tmp]# cat error.log 
ls: cannot access badfile: No such file or directory

# 3.查看记录正常输出的日志。
[root@VM-8-11-centos tmp]# cat info.log 
a.txt
b.txt
  • 也可以将 STDERR 和 STDOUT 的输出重定向到同一个文件。为此,bash shell提供了特殊的重定向符 &>:
# 1.将命令生成的所有输出都重定向至同一个文件。
[root@VM-8-11-centos tmp]# ls a.txt b.txt badfile &> service.log

# 2.查看文件。(错误消息展示在前。)
[root@VM-8-11-centos tmp]# cat service.log 
ls: cannot access badfile: No such file or directory
a.txt
b.txt
  • 提示为了避免错误消息散落在输出文件中,相较于标准输出,bash shell 自动赋予了错误消息更高的优先级

二、在脚本中重定向输出

脚本中重定向输出的方法有两种

  • 临时重定向每一行。
  • 永久重定向脚本中的所有命令。

2.1 临时重定向

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

  • 在重定向到文件描述符时,必须文件描述符索引值之前加一个 &。
  • 应用示例
# 1.脚本内容。(`>&2`生成错误消息。)
[root@VM-8-11-centos testdir]# cat test.sh 
#!/bin/bash
echo "error msg." >&2
echo "normal output."

# 2.执行脚本。(当前看不出任何区别。)
[root@VM-8-11-centos testdir]# ./test.sh 
error msg.
normal output.

# 3.执行脚本。(STDOUT正常显示,而STDERR描述符2,将错误消息进行了重定向。)
[root@VM-8-11-centos testdir]# ./test.sh 2> error.log
normal output.

# 4.查看目标日志。(成功地只记录了错误消息。)
[root@VM-8-11-centos testdir]# cat error.log 
error msg.
  • 这种方法非常适合在脚本中生成错误消息。

2.2 永久重定向

如果脚本中有大量数据需要重定向,那么逐条重定向所有的 echo 语句会很烦琐。这时可以用 exec 命令,它会告诉 shell 在脚本执行期间重定向某个特定文件描述符。

  • 应用示例
# 1.脚本内容。(使用exec命令进行重定向。)
[root@VM-8-11-centos testdir]# cat test.sh 
#!/bin/bash
#
exec 2>error.log
exec 1>info.log
#
echo "error msg."  >&2
echo "normal output."

# 2.执行脚本。
[root@VM-8-11-centos testdir]# ./test.sh 

# 3.均成功记录输出。
[root@VM-8-11-centos testdir]# cat error.log 
error msg.
[root@VM-8-11-centos testdir]# cat info.log 
normal output.
  • 只想将脚本的部分输出重定向到其他位置(比如错误日志)时,这个特性用起来非常方便。

三、在脚本中重定向输入

在 Linux 系统中,exec 命令允许将 STDIN 重定向为文件。

  • 应用示例
# 1.脚本内容。(当read命令试图从STDIN读入数据时,就会到文件中而不是键盘上检索数据。)
[root@VM-8-11-centos testdir]# cat test.sh 
#!/bin/bash
exec 0<data.txt
count=1
#
while read line; do
    echo "#$count: $line"
    count=$((count+1))
done
#
exit

# 1.被读文件内容。
[root@VM-8-11-centos testdir]# cat data.txt 
A
B
C
D
E

# 2.执行脚本。
[root@VM-8-11-centos testdir]# ./test.sh 
#1: A
#2: B
#3: C
#4: D
#5: E
  • 这是完成日志文件中读取并处理数据的最简单办法。

四、创建自己的重定向

在脚本中重定向输入和输出时,并不局限于这 3 个默认的文件描述符。

4.1 创建输出文件描述符

可以用 exec 命令分配用于输出的文件描述符。

  • 应用示例
# 1.脚本内容。(使用文件描述符3进行重定向。)
[root@VM-8-11-centos testdir]# cat test.sh 
#!/bin/bash
exec 3>other.log
#
echo "hello."
echo "msg." >&3
echo "hello."
#
exit

# 2.执行脚本。
[root@VM-8-11-centos testdir]# ./test.sh 
hello.
hello.

# 3.成功记录重定向内容。
[root@VM-8-11-centos testdir]# cat other.log 
msg.
  • 注意事项:在重定向时,如果使用大于 9 的文件描述符,那么一定要小心,因为有可能会与 shell 内部使用的文件描述符发生冲突

4.2 创建输入文件描述符

可以将另一个文件描述符分配给标准文件描述符,反之亦可。

  • 在重定向到文件之前,先将 STDIN 指向的位置保存到另一个文件描述符,然后在读取完文件之后将 STDIN 恢复到原先的位置
  • 应用示例
# 1.脚本内容。(使用另一个 read 命令来测试 STDIN 是否恢复原位,这次 read 会等待键盘的输入。)
# --------------------------------
# 具体执行步骤如下:
# 文件描述符 6 用于保存 STDIN 指向的位置。
# 然后脚本将 STDIN 重定向到一个文件。
# read 命令的所有输入都来自重定向后的 STDIN。
# 在读完所有行之后,脚本会将 STDIN 重定向到文件描述符 6,恢复 STDIN 原先的位置。
[root@VM-8-11-centos testdir]# cat test.sh 
#!/bin/bash
exec 6<&0
exec 0<data.txt
#
count=1
while read line; do
    echo "#$count: $line"
    count=$((count + 1))
done
#
exec 0<&6
#
read -p "Are you done now? "
case "$REPLY" in
y | Y)
    echo "Goodbye."
    ;;
n | N)
    echo "Sorry, this is the end."
    ;;
*)
    echo "Invalid input."
    ;;
esac
#
exit

# 2.执行脚本。(STDIN 成功恢复。)
[root@VM-8-11-centos testdir]# ./test.sh 
#1: A
#2: B
#3: C
#4: D
#5: E
Are you done now? y
Goodbye.

4.3 创建读/写文件描述符

你也可以打开单个文件描述符兼做输入输出,这样就能用同一个文件描述符对文件进行读和写两种操作了。

  • 由于这是对一个文件进行读和写两种操作,因此 shell 会维护一个内部指针,指明该文件的当前位置。
  • 任何读或写都会从文件指针上次的位置开始
  • 因此,使用这种方法时要特别小心
  • 应用示例
# 1.脚本内容。(通过文件描述符 3 操作文件的读和写。)
[root@VM-8-11-centos testdir]# cat test.sh 
#!/bin/bash
exec 3<>data.txt
#
while read line <&3; do
    echo "read: $line"
done
echo "write something..." >&3
#
exit

# 2.当前读写操作文件的内容。
[root@VM-8-11-centos testdir]# cat data.txt 
A
B
C

# 3.执行脚本。(读成功。)
[root@VM-8-11-centos testdir]# ./test.sh 
read: A
read: B
read: C

# 4.操作之后的文件内容变化。(写成功。)
[root@VM-8-11-centos testdir]# cat data.txt 
A
B
C
write something...
  • 提示:如果粗心的话,会产生一些令人意外的结果!因此,慎用!!!

4.4 关闭文件描述符

在一些情况下,需要在脚本结束前手动关闭文件描述符。

  • 关闭文件描述符,只需将其重定向到特殊符号 &- 即可。
  • 应用示例
# 1.脚本内容。(写入-关闭-再写入。)
[root@VM-8-11-centos testdir]# cat test.sh 
#!/bin/bash
exec 3>data.txt
echo "AAA" >&3
#
exec 3>&-
#
echo "BBB" >&3
exit 

# 2.执行脚本。(一旦关闭了文件描述符,就不能在脚本中向其写入任何数据,否则 shell 会发出错误消息。)
[root@VM-8-11-centos testdir]# ./test.sh
./test.sh: line 7: 3: Bad file descriptor

# 3.重定向文件。(只写入了关闭前的内容。)
[root@VM-8-11-centos testdir]# cat data.txt 
AAA
  • 在使用关闭文件描述符时,还需要注意:如果随后你在脚本中打开了同一个输出文件,那么 shell 就会用一个新文件来替换已有文件。(意味着如果你输出数据,它就会覆盖已有文件。)

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

有时要记住哪个文件描述符被重定向到了哪里就没那么容易了。

  • lsof 命令会列出整个 Linux 系统打开的所有文件描述符,这包括所有后台进程以及登录用户打开的文件。
  • 有大量的命令行选项和参数可用于过滤 lsof 的输出。
  • lsof 常用命令选项
选项描述
-p指定进程 ID(PID)。
-d指定要显示的文件描述符编号(多个编号之间以逗号分隔)。
-a用于对另外两个选项的结果执行 AND 运算。
  • 要想知道进程的当前 PID,可以使用特殊环境变量 $$(shell 会将其设为当前 PID)。
  • 应用示例
[root@VM-8-11-centos testdir]# lsof -a -p $$ -d 0,1,2
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
bash    31680 root    0u   CHR  136,1      0t0    4 /dev/pts/1
bash    31680 root    1u   CHR  136,1      0t0    4 /dev/pts/1
bash    31680 root    2u   CHR  136,1      0t0    4 /dev/pts/1
  • 结果显示了当前进程(bash shell)的默认文件描述符(0、1 和 2)。
  • lsof 的默认输出列信息如下:
描述
COMMAND进程对应的命令名的前 9 个字符。
PID进程的 PID。
USER进程属主的登录名。
FD文件描述符编号以及访问类型。(r 代表读,w 代表写,u 代表读/写。)
TYPE文件的类型。(CHR 代表字符型,BLK 代表块型,DIR 代表目录,REG 代表常规文件。)
DEVICE设备号(主设备号和从设备号)。
SIZE如果有的话,表示文件的大小。
NODE本地文件的节点号。
NAME文件名。

六、抑制命令输出

如果在后台运行的脚本出现错误消息,那么 shell 就会将其通过邮件发送给进程属主。这会很麻烦尤其是当运行的脚本生成很多烦琐的小错误时

  • 要解决这个问题,可以将 STDERR 重定向到一个名为 null 文件的特殊文件。
  • shell 输出到 null 文件任何数据都不会被保存,全部会被丢弃。
  • 在 Linux 系统中,null 文件的标准位置是 /dev/null。
  • 应用示例
# 1.重定向执行。(/dev/null 不会保存任何数据。)
[root@VM-8-11-centos testdir]# ls -al > /dev/null 
[root@VM-8-11-centos testdir]# cat /dev/null 
[root@VM-8-11-centos testdir]# 
  • 也可以在输入重定向中将 /dev/null 作为输入文件。
  • 通常用它来快速清除现有文件中的数据,这样就不用先删除文件再重新创建了:
# 1.原文件内容。
[root@VM-8-11-centos testdir]# cat data.txt 
AAA

# 2.使用 /dev/null 作为输入,进行快速清除。
[root@VM-8-11-centos testdir]# cat /dev/null > data.txt 

# 3.目标文件内容被成功清除。
[root@VM-8-11-centos testdir]# cat data.txt 
[root@VM-8-11-centos testdir]# 
  • 这是清除日志文件的常用方法,因为日志文件必须时刻等待应用程序操作。

七、使用临时文件

Linux 系统有一个专供临时文件使用的特殊目录 /tmp,其中存放那些不需要永久保留的文件。系统中的任何用户都有权限读写 /tmp 目录中的文件。

7.1 创建本地临时文件

  • 专门用于创建临时文件的命令 mktemp,该命令可以直接在 /tmp 目录中创建唯一的临时文件。
  • 默认情况下mktemp 会在本地目录中创建一个文件。
  • 在使用 mktemp 命令时,只需指定一个文件名模板即可。
  • 模板可以包含任意文本字符,同时在文件名末尾要加上 6 X:
# 1.mktemp 命令会任意地将 6 个 X 替换为同等数量的字符,以保证文件名在目录中是唯一的。
[root@VM-8-11-centos testdir]# mktemp testing.XXXXXX
testing.WkMvVu
[root@VM-8-11-centos testdir]# mktemp testing.XXXXXX
testing.0HQESu

# 2.查看当前目录创建的临时文件。
[root@VM-8-11-centos testdir]# ls -al testing.*
-rw------- 1 root shared 0 Jun 20 16:24 testing.0HQESu
-rw------- 1 root shared 0 Jun 20 16:24 testing.WkMvVu
  • 应用示例
# 1.脚本内容。(创建本地临时文件->重定向输出内容->关闭操作符->显示内容->删除本地临时文件。)
[root@VM-8-11-centos testdir]# cat test.sh 
#!/bin/bash
tmpfile=$(mktemp test99.XXXXXX)
exec 3>"$tmpfile"
#
echo "writes to temp file $tmpfile"
echo "hello" >&3
echo "world" >&3
#
exec 3>&-
#
echo "write finish,the temp file contents are: "
cat "$tmpfile"
rm -f "$tmpfile"
echo "remove $tmpfile successfully!"
#
exit

# 2.执行脚本。
[root@VM-8-11-centos testdir]# ./test.sh 
writes to temp file test99.PvEpUs
write finish,the temp file contents are: 
hello
world
remove test99.PvEpUs successfully!

7.2 在 /tmp 目录中创建临时文件

-t 选项会强制 mktemp 命令在系统的临时目录中创建文件。

  • 应用示例
# 1.脚本内容。(与上一个脚本逻辑一致。只不过在创建临时文件时使用了 -t 选项,它会返回一个完整的路径名。)
[root@VM-8-11-centos testdir]# cat test.sh 
#!/bin/bash
tmpfile=$(mktemp -t tmp.XXXXXX)
exec 3>"$tmpfile"
#
echo "writes to temp file $tmpfile"
echo "hello" >&3
echo "world" >&3
#
exec 3>&-
#
echo "write finish ,the temp file contents are: "
cat "$tmpfile"
rm -f "$tmpfile"
echo "remove $tmpfile successfully!"
#
exit

# 2.执行脚本。
[root@VM-8-11-centos testdir]# ./test.sh 
writes to temp file /tmp/tmp.0zhdS0
write finish ,the temp file contents are: 
hello
world
remove /tmp/tmp.0zhdS0 successfully!
  • 在创建临时文件时,mktemp 会将全路径名返回给环境变量。这样就能在任何命令中使用该值来引用临时文件了。

7.3 创建临时目录

-d 选项会告诉 mktemp 命令创建一个临时目录

  • 应用示例
# 1.脚本内容。(创建临时目录,再在该目录下创建两个临时文件,分别写入不同的内容。)
[root@VM-8-11-centos testdir]# cat test.sh 
#!/bin/bash
tmpdir=$(mktemp -d tmpdir.XXXXXX)
cd $tmpdir
tmpfile1=$(mktemp test01.XXXXXX)
tmpfile2=$(mktemp test02.XXXXXX)
#
exec 3>$tmpfile1
exec 4>$tmpfile2
#
echo "Sending data to directory $tmpdir"
#
echo "writes to temp file $tmpfile1"
echo "file 1" >&3
echo "writes to temp file $tmpfile2"
echo "file 2" >&4
#
exit

# 2.执行脚本。
[root@VM-8-11-centos testdir]# ./test.sh 
Sending data to directory tmpdir.nlMgmz
writes to temp file test01.Gmh5Ao
writes to temp file test02.mSNKA1

# 2.对应的临时文件写入成功。
[root@VM-8-11-centos testdir]# cat tmpdir.nlMgmz/test01.Gmh5Ao 
file 1
[root@VM-8-11-centos testdir]# cat tmpdir.nlMgmz/test02.mSNKA1 
file 2

八、记录消息

有时候,也确实需要将输出同时送往显示器和文件。

  • tee 命令就像是连接管道的 T 型接头,它能将来自 STDIN 的数据同时送往两处(STDOUT 及 文件)。

  • 注意,在默认情况下tee 命令会在每次使用时覆盖指定文件的原先内容。

  • 如果想将数据追加到指定文件中,就必须使用 -a 选项。

  • 应用示例

# 1.脚本内容。(既保存数据到文件,又将其显示在屏幕上。)
[root@VM-8-11-centos testdir]# cat test.sh 
#!/bin/bash
file=test.txt
echo "AAA" | tee $file
echo "BBB" | tee -a $file
echo "CCC" | tee -a $file
exit

# 2.执行脚本。(成功显示。)
[root@VM-8-11-centos testdir]# ./test.sh 
AAA
BBB
CCC

# 3.查看目标文件。(成功写入。)
[root@VM-8-11-centos testdir]# cat test.txt 
AAA
BBB
CCC
  • 现在,你可以在为用户显示输出同时再永久保存一份输出内容了。

九、结束语


“-------怕什么真理无穷,进一寸有一寸的欢喜。”

微信公众号搜索:饺子泡牛奶

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值