Linux 用文件描述符来标识每个文件对象。
文件描述符是一个非负整数,标识 会话中打开的文件。
每个进程一次最多可以打开9个文件描述符(非固定)。出于特殊目的,bash shell保留了前3个文件描述符(0、1和2)
文件描述符
缩写
描述
0
STDIN
标准输入
1
STDOUT
标准输出
2
STDERR
标准错误
1.1.1,STDIN 标准输入
STDIN文件描述符代表shell的标准输入。对终端界面来说,标准输入就是键盘。shell会从STDIN文件描述符对应的键盘获得输入并进行处理。
在使用输入重定向符<时,Linux会用重定向指定的文件替换标准输入文件描述符。命令就会从文件中读取数据。
许多 bash命令能从STDIN接收输入.
当在命令行中只输入cat命令时,会从STDIN接收输入。输入一行,cat命令就显示一行。
$ cat
this is a test ##输入
this is a test
this is a second test. ##输入
this is a second test.
也可以通过输入重定向符强制cat命令接收来自STDIN之外的文件输入:
$ cat < testfile
This is the first line.
This is the second line.
This is the third line.
1.1.2,STDOUT 标准输出
STDOUT文件描述符代表shell的标准输出。
在终端界面上,标准输出就是终端显示器。shell的所有输出会被送往标准输出 (显示器)。
通过输出重定向符(>),所有输出 可以被 重定向到 指定的文件。也可以使用>>将数据追加到某个文件:
错误消息:
$ ls -al badfile > test3
ls: cannot access badfile: No such file or directory
$ cat test3
shell对于错误消息的处理是跟普通输出分开的。
如果 创建了一个在后台运行的shell脚本,则通常必须依赖 发送到日志文件 的 输出消息。
可以将单独的一行输出重定向到 STDERR。在重定向到文件描述符时,必须在文件描述符索引值之前加 &.
echo "This is an error message" >&2
!!!!!默认情况下,STDERR和STDOUT指向的位置是一样的。但是,如果在运行脚本时重定向了STDERR,那么脚本中所有送往STDERR的文本都会被重定向
示例:
$ cat test8
#!/bin/bash
echo "This is an error" >&2 ##设置文件描述符为 STDERR
echo "This is normal output"
$ ./test8 ## 默认 STDERR STDOUT 输出均为控制台
This is an error
This is normal output
$ ./test8 2> test9 ##设置 STDERR 重定向输出到 test9
This is normal output
$ cat test9
This is an error
2.2,永久重定向 exec
exec命令告诉 shell 在脚本执行期间重定向某个特定文件描述符:
$ cat test10
#!/bin/bash
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"
$ ./test10
$ cat testout
This is a test of redirecting all output
from a script to another file.
without having to redirect every individual line
exec命令会启动一个新shell 并将STDOUT文件描述符重定向到指定文件。脚本中送往STDOUT的所有输出都会被重定向。
3,在脚本中重定向输出 exec 0< file
可以使用与重定向STDOUT和STDERR相同的方法,将STDIN从键盘重定向到其他位置。
在Linux系统中,exec命令允许将STDIN重定向为文件:
exec 0< testfile
shell 应从文件 testfile中而不是键盘上获取输入。只要脚本需要输入,这个重定向就会起作用。
示例:
$ cat test12
#!/bin/bash
exec 0< testfile
count=1
while read line
do
echo "Line #$count: $line"
count=$[ $count + 1 ]
done
$ ./test12
Line #1: This is the first line.
Line #2: This is the second line.
Line #3: This is the third line.
将 STDIN重定向为文件后,当read命令试图从STDIN读入数据时,就会到文件中而不是键盘上检索数据。
用 exec命令分配用于输出的文件描述符。一旦将替代性文件描述符指向文件,此重定向就会一直有效,直至重新分配。
$ cat test13
#!/bin/bash
exec 3>test13out ## exec 3>>test13out 追加
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"
$ ./test13
This should display on the monitor
Then this should be back on the monitor
$ cat test13out
and this should be stored in the file
4.2,重定向文件描述符 exec 3>&1 exec 1>&3
复已重定向的文件描述符.可以将另一个文件描述符分配给标准文件描述符,反之亦可。
即可以将 STDOUT 的原先位置 重定向到另一个文件描述符,然后再利用该文件描述符恢复STDOUT.
$ cat test14
#!/bin/bash
exec 3>&1
exec 1>test14out ## 此时 stdout 会输出到文件 >&3会输出到控制台
echo "This should store in the output file"
echo "along with this line."
exec 1>&3
echo "Now things should be back to normal"
$ ./test14
Now things should be back to normal
$ cat test14out
This should store in the output file
along with this line.
4.3,创建输入文件描述符
重定向输入文件描述符,在重定向到文件之前,先将 STDIN指向的位置保存到另一个文件描述符,然后在读取完文件之后将 STDIN恢复到原先的位置:
$ cat test15
#!/bin/bash
exec 6<&0
exec 0< testfile ## 6文件描述符保存 STDIN, testfile 重定向到 STDIN
count=1
while read line
do
echo "Line #$count: $line"
count=$[ $count + 1 ]
done
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
$ ./test15
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
4.4,创建读写文件描述符 n<>file
可以打开单个文件描述符兼做输入和输出,就能用同一个文件描述符对文件进行读和写两种操作。
用这种方法时要特别小心。由于这是对一个文件进行读和写两种操作,因此shell会维护一个内部指针,指明该文件的当前位置。任何读或写都会从文件指针上次的位置开始。
$ cat test16
#!/bin/bash
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.
$ ./test16
Read: This is the first line.
$ cat testfile
This is the first line.
This is a test line
ine.
This is the third line.
read命令读取了第一行数据,这使得文件指针指向了第二行数据的第一个字符。
当echo语句将数据输出到文件时,会将数据写入文件指针的当前位置,覆盖该位置上的已有数据。
4.5,关闭文件描述符 >&-
如果创建了新的输入文件描述符或输出文件描述符,shell会在脚本退出时自动将其关闭。
一些情况下,需要在脚本结束前手动关闭文件描述符
关闭文件描述符,只需将其重定向到特殊符号&-即可。在向文件发送了字符串并关闭该文件描述符之后,脚本会使用cat命令显示文件内容.!!!
exec n>&-
示例:
$ cat badtest
#!/bin/bash
exec 3> test17file
echo "This is a test line of data" >&3
exec 3>&-
echo "This won't work" >&3
$ ./badtest
./badtest: 3: Bad file descriptor
一旦关闭了文件描述符,就不能在脚本中向其写入任何数据,否则shell会发出错误消息。
关闭文件描述符后在脚本中打开了同一个输出文件,那么shell就会用一个新文件来替换已有文件。如果输出数据,就会覆盖已有文件。
$ cat test17
#!/bin/bash
exec 3> test17file
echo "This is a test line of data" >&3
exec 3>&-
cat test17file
exec 3> test17file
echo "This'll be bad" >&3
$ ./test17
This is a test line of data
$ cat test17file
This'll be bad
将脚本作为后台进程运行时不想显示脚本输出。
可以将STDERR重定向到一个名为null文件的特殊文件。null文件里什么都没有。shell输出到null文件的任何数据都不会被保存,全部会被丢弃。
Linux系统中,null文件的标准位置是/dev/null。重定向到该位置的任何数据都会被丢弃,不再显示:
$ ls -al > /dev/null
$ cat /dev/null
抑制错误消息出现且无须保存它们的一种常用方法:
$ ls -al badfile test16 2> /dev/null
-rwxr--r-- 1 rich rich 135 Jun 20 19:57 test16*
也可以在输入重定向中将/dev/null作为输入文件。
由于/dev/null文件不含任何内容,通常用它来快速清除现有文件中的数据,这样就不用先删除文件再重新创建:
$ cat testfile
This is the first line.
This is the second line.
This is the third line.
$ cat /dev/null > testfile
$ cat testfile
文件 testfile仍然还在,但现在是一个空文件。这是清除日志文件的常用方法,因为日志文件必须时刻等待应用程序操作。
mktemp会在本地目录中创建一个文件。在使用mktemp命令时,指定一个文件名模板,文件名末尾加上6个X。(模板可以包含任意文本字符)
$ mktemp testing.XXXXXX
testing.1DRLuV
$ mktemp testing.XXXXXX
testing.lVBtkW
$ mktemp testing.XXXXXX
testing.PgqNKG
$ ls -l testing*
-rw------- 1 rich rich 0 Jun 20 21:57 testing.1DRLuV
-rw------- 1 rich rich 0 Jun 20 21:57 testing.PgqNKG
-rw------- 1 rich rich 0 Jun 20 21:57 testing.lVBtkW
会将6个X替换为同等数量的字符,以保证文件名在目录中是唯一的。你可以创建多个临时文件,并确保每个文件名都不重复:
mktemp命令的输出是 所创建的文件名。在脚本中使用 mktemp命令时,可以将文件名保存到变量中,就能在随后的脚本中引用
$ cat test19
#!/bin/bash
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 second 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
$ ./test19
This script writes to temp file test19.vCHoya
Done creating temp file. The contents are:
This is the first line
This is the second line.
This is the last line.
$ ls -al test19*
-rwxr--r-- 1 rich rich 356 Jun 20 22:03 test19
-d选项 创建一个临时目录。可以根据需要使用该目录,比如在其中创建其他的临时文件:
$ cat test21
#!/bin/bash
tempdir=$(mktemp -d dir.XXXXXX)
cd $tempdir
tempfile1=$(mktemp temp.XXXXXX)
tempfile2=$(mktemp temp.XXXXXX)
exec 7> $tempfile1
exec 8> $tempfile2
echo "Sending data to directory $tempdir"
echo "This is a test line of data for $tempfile1" >&7
echo "This is a test line of data for $tempfile2" >&8
8,记录消息 tee
将输出同时送往显示器和文件,与其对输出进行两次重定向,不如改用特殊的tee命令.
tee 命令 能将来自STDIN的数据同时送往两处。一处是STDOUT,另一处是tee命令行所指定的文件名:
tee filename
tee会重定向来自STDIN的数据,可以用来管道命令来重定向命令输出:
$ date | tee testfile
Sun Jun 21 18:56:21 EDT 2020
$ cat testfile
Sun Jun 21 18:56:21 EDT 2020
会在每次使用时覆盖指定文件的原先内容,将数据追加到指定文件中,就必须使用-a选项:
9,实战演练
shell脚本使用命令行参数指定待读取的CSV文件。
CSV格式用于从电子表格中导出数据,把这些数据库数据放入电子表格,将电子表格保存为CSV格式,读取文件,然后创建 INSERT语句将数据插入MySQL数据库。
$ 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
$cat test23
#!/bin/bash
outfile='members.sql'
IFS=',' ## read语句使用IFS字符解析读入的文本,这里将IFS指定为逗号
while read lname fname address city state zip
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
done < ${1}
$ ./test23 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');
一个输出追加重定向(双大于号)和一个输入追加重定向(双小于号)。
输出重定向将cat命令的输出追加到由 $outfile变量指定的文件中。
cat命令的输入不再取自标准输入,而是被重定向到脚本内部的数据。EOF符号标记了文件中的数据起止:
cat >> $outfile << EOF
当运行脚本 test23时,$1代表第一个命令行参数,指明了待读取数据的文件。
done < ${1}