有時我們需要用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;
}'