有时我们需要用shell脚本处理一些文件,通常我们会使用awk这个强大的可编程命令来处理文本文件,当我们在一次awk调用中处理很多文件时,如果没有正确的关闭打开的文件和管道,则会造成文件句柄泄露,awk命令会报错,我们通过以下测试说明这个问题,并看看如何正确的使用close命令解决这个问题。
我们先用ulimit命令查看一个进程最多可以打开多少个文件句柄:
ulimit -n
通常是1024。
生成一批测试文件,数量超过1024,这里我们生成了10000个文件用于测试:
mkdir test
cd test
for ((i=0; i < 10000; ++i)); do
filename=$(printf "file%04d" $i);
echo $filename;
for ((j=0; j < 10; ++j)); do
echo line$j >> ${filename};
done;
done
我们来看看下面这个awk命令的执行情况:
ls file* | awk 'BEGIN {
count=0;
}
{
file=$0;
if (getline line <file)
{
count++;
}
}
END {
print count;
}'
这个命令用于统计非空文件的个数,当然这不是最好的方法,在这里只用于awk打开文件的测试,命令执行时会提示:
awk: cmd. line:6: (FILENAME=- FNR=1022) fatal: too many pipes or input files open
可见打开了太多的文件,可以在if语句后面加上close来关闭打开的文件以解决这个问题,如下:
ls file* | awk 'BEGIN {
count=0;
}
{
file=$0;
if (getline line <file)
{
count++;
}
close(file);
}
END {
print count;
}'
除了打开文件占用文件句柄之外的另一种占用文件句柄的情况就是调用shell命令并用管道处理,这种情况也要用close关闭打开管道,close的参数必须与打开管道的命令字符串完全一致,例如:
ls file* | awk 'BEGIN {
count=0;
}
{
file=$0;
while ("cat "file | getline line)
{
count++;
}
close("cat "file);
}
END {
print count;
}'
这个问题有一个例外,就是我们用getline读入一个文件时,遇到文件结束(getline失败时)awk会自动关闭这个文件,不需要主动close,例如以下统计所有文件行数的命令是可以正确执行的,这个便利并不适用于管道,如上例所示,管道还是需要主动关闭的:
ls file* | awk 'BEGIN {
count=0;
}
{
file=$0;
while (getline line <file)
{
count++;
}
}
END {
print count;
}'
以上测试在CentOS 7.3下进行,不同的awk版本可能结果不同
awk使用这种方式方便用户对文件和管道的流式操作,没有提供独立的打开文件和管道的命令,而是默默的记录了打开的文件和管道,在一次使用后并不自动关闭,后续使用时继续在已打开的句柄上操作,直到用户显示的关闭文件和管道为止,关闭后再次用到相同的文件和管道时会自动重新打开,而重新打开的文件就又从文件起始位置开始处理了,例如:
ls file0000 | awk '{
file=$0;
getline line < file;
print line;
getline line < file;
print line;
close(file);
getline line < file;
print line;
}'