我正在使用shell脚本循环一系列大文件:
i=0
while read line
do
# get first char of line
first=`echo "$line" | head -c 1`
# make output filename
name="$first"
if [ "$first" = "," ]; then
name='comma'
fi
if [ "$first" = "." ]; then
name='period'
fi
# save line to new file
echo "$line" >> "$2/$name.txt"
# show live counter and inc
echo -en "\rLines:\t$i"
((i++))
done <$file
每行中的第一个字符可以是字母数字,也可以是上面定义的字符之一(这就是为什么我要重命名它们以便在输出文件名中使用)。
这太慢了。
5,000行需要128秒。
按照这个速度,我有一个坚实的月份处理。
awk会更快吗?
如果是这样,我如何将逻辑纳入awk?
答案 0 :(得分:3)
这肯定可以在bash中更有效地完成。
举个例子:echo foo | head
进行fork()
调用,创建子shell,设置管道,启动外部head
程序......并且没有理由一点都不。
如果你想要一行的第一个字符,而没有任何低效的子进程错误,它就像这样简单:
c=${line:0:1}
我也会认真考虑对您的输入进行排序,因此您只能在看到新的第一个字符时重新打开输出文件,而不是每次都通过循环。
即 - 使用sort进行预处理(将<$file
替换为< <(sort "$file")
)并在每次循环时执行以下操作,仅有条件地重新打开输出文件:
if [[ $name != "$current_name" ]] ; then
current_name="$name"
exec 4>>"$2/$name" # open the output file on FD 4
fi
...然后附加到打开的文件描述符:
printf '%s\n' "$line" >&4
(不使用echo,因为如果你的行是-e
或-n
,它会表现得不合理。
或者,如果可能的输出文件数量很少,您可以在前面的不同FD上打开它们(替换我选择4
的其他更高的数字),并有条件地输出到其中一个预先打开的文件。打开和关闭文件很昂贵 - 每个close()
强制刷新到磁盘 - 所以这应该是一个重要的帮助。
答案 1 :(得分:2)
#!/usr/bin/awk -f
BEGIN {
punctlist = ", . ? ! - '"
pnamelist = "comma period question_mark exclamation_mark hyphen apostrophe"
pcount = split(punctlist, puncts)
ncount = split(pnamelist, pnames)
if (pcount != ncount) {print "error: counts don't match, pcount:", pcount, "ncount:", ncount; exit}
for (i = 1; i <= pcount; i++) {
punct_lookup[puncts[i]] = pnames[i]
}
}
{
print > punct_lookup[substr($0, 1, 1)] ".txt"
printf "\r%6d", i++
}
END {
printf "\n"
}
BEGIN
块构建了一个关联数组,因此您可以执行punct_lookup[","]
并获取“逗号”。
主块只是查找文件名并将该行输出到文件中。在AWK中,>
第一次截断文件并随后附加。如果您有不想截断的现有文件,请将其更改为>>
(但不要使用>>
。)
答案 2 :(得分:2)
要加快速度的一些事情:
不要使用echo / head来获取第一个字符。你是 每行产生至少两个额外的进程。代替, 使用bash的参数扩展工具来获取第一个字符。
使用if-elif避免检查$first
所有内容
可能性
每一次。更好的是,如果您使用的是bash 4.0或更高版本,请使用关联数组
存储输出文件名,而不是检查
$first
在每行的if语句中。{/ p>
如果您没有支持关联的bash版本 数组,用以下代码替换你的if语句。
if [[ "$first" = "," ]]; then
name='comma'
elif [[ "$first" = "." ]]; then
name='period'
else
name="$first"
fi
但建议如下。如果没有给出名称(仅限FYI),请注意使用$REPLY
作为read
使用的默认变量。
declare -A OUTPUT_FNAMES
output[","]=comma
output["."]=period
output["?"]=question_mark
output["!"]=exclamation_mark
output["-"]=hyphen
output["'"]=apostrophe
i=0
while read
do
# get first char of line
first=${REPLY:0:1}
# make output filename
name=${output[$first]:-$first}
# save line to new file
echo $REPLY >> "$name.txt"
# show live counter and inc
echo -en "\r$i"
((i++))
done <$file
答案 3 :(得分:1)
还有另一种看法:
declare -i i=0
declare -A names
while read line; do
first=${line:0:1}
if [[ -z ${names[$first]} ]]; then
case $first in
,) names[$first]="$2/comma.txt" ;;
.) names[$first]="$2/period.txt" ;;
*) names[$first]="$2/$first.txt" ;;
esac
fi
printf "%s\n" "$line" >> "${names[$first]}"
printf "\rLine $((++i))"
done < "$file"
和
awk -v dir="$2" '
{
first = substr($0,1,1)
if (! (first in names)) {
if (first == ",") names[first] = dir "/comma.txt"
else if (first == ".") names[first] = dir "/period.txt"
else names[first] = dir "/" first ".txt"
}
print > names[first]
printf("\rLine %d", NR)
}
'