bash 例程到 return 文本文件中给定行号的页码
bash routine to return the page number of a given line number from text file
考虑一个包含分页 ASCII 控制字符的纯文本文件 "Form Feed" ($'\f'):
alpha\n
beta\n
gamma\n\f
one\n
two\n
three\n
four\n
five\n\f
earth\n
wind\n
fire\n
water\n\f
请注意,每页的行数是随机的。
需要一个 bash 例程,该例程 return 包含分页 ASCII 控制字符的文本文件中给定行号的页码。
经过长时间研究解决方案,我终于找到了这段代码:
function get_page_from_line
{
local nline=""
local input_file=""
local npag=0
local ln=0
local total=0
while IFS= read -d $'\f' -r page; do
npag=$(( ++npag ))
ln=$(echo -n "$page" | wc -l)
total=$(( total + ln ))
if [ $total -ge $nline ]; then
echo "${npag}"
return
fi
done < "$input_file"
echo "0"
return
}
但是,不幸的是,这个解决方案在某些情况下被证明非常慢。
有更好的解决方案吗?
谢谢!
awk
救援!
awk -v RS='\f' -v n=09 '[=10=]~"^"n"." || [=10=]~"\n"n"." {print NR}' file
3
更新锚定如下评论。
$ for i in $(seq -w 12); do awk -v RS='\f' -v n="$i"
'[=11=]~"^"n"." || [=11=]~"\n"n"." {print n,"->",NR}' file; done
01 -> 1
02 -> 1
03 -> 1
04 -> 2
05 -> 2
06 -> 2
07 -> 2
08 -> 2
09 -> 3
10 -> 3
11 -> 3
12 -> 3
这个 gnu awk 脚本为作为命令行参数给出的行号打印 "page":
BEGIN { ffcount=1;
search = ARGV[2]
delete ARGV[2]
if (!search ) {
print "Please provide linenumber as argument"
exit(1);
}
}
~ search { printf( "line %s is on page %d\n", search, ffcount) }
/[\f]/ { ffcount++ }
像 awk -f formfeeds.awk formfeeds.txt 05
一样使用它,其中 formfeeds.awk
是脚本,formfeeds.txt
是文件,'05' 是行号。
BEGIN
规则主要处理命令行参数。其他规则为简单规则:
~ search
当第一个字段与存储在 search
中的命令行参数匹配时适用
/[\f]/
在有换页时适用
可以在 bash 本身中编写类似长度的脚本来定位和响应包含在文件中的嵌入式 <form-feed>
。 (它也适用于 POSIX shell,替代字符串索引和 expr
数学)例如,
#!/bin/bash
declare -i ln=1 ## line count
declare -i pg=1 ## page count
fname="${1:-/dev/stdin}" ## read from file or stdin
printf "\nln:pg text\n" ## print header
while read -r l; do ## read each line
if [ ${l:0:1} = $'\f' ]; then ## if form-feed found
((pg++))
printf "<ff>\n%2s:%2s '%s'\n" "$ln" "$pg" "${l:1}"
else
printf "%2s:%2s '%s'\n" "$ln" "$pg" "$l"
fi
((ln++))
done < "$fname"
示例输入文件
带有嵌入式 <form-feed>
的简单输入文件是用以下方法创建的:
$ echo -e "a\nb\nc\n\fd\ne\nf\ng\nh\n\fi\nj\nk\nl" > dat/affex.txt
当输出给出时:
$ cat dat/affex.txt
a
b
c
d
e
f
g
h
i
j
k
l
例子Use/Output
$ bash affex.sh <dat/affex.txt
ln:pg text
1: 1 'a'
2: 1 'b'
3: 1 'c'
<ff>
4: 2 'd'
5: 2 'e'
6: 2 'f'
7: 2 'g'
8: 2 'h'
<ff>
9: 3 'i'
10: 3 'j'
11: 3 'k'
12: 3 'l'
使用 read -d $'\f'
然后计算行数的想法很好。
这个版本可能看起来不够优雅:如果 nline
大于或等于文件中的行数,那么文件会被读取两次。
试一试,因为它超级快:
function get_page_from_line ()
{
local nline=""
local input_file=""
if [[ $(wc -l "${input_file}" | awk '{print }') -lt nline ]] ; then
printf "0\n"
else
printf "%d\n" $(( $(head -n ${nline} "${input_file}" | grep -c "^"$'\f') + 1 ))
fi
}
awk 的性能优于上面的 bash 版本。 awk 就是为这种文本处理而创建的。
试试这个测试版本:
function get_page_from_line ()
{
awk -v nline="" '
BEGIN {
npag=1;
}
{
if (index([=11=],"\f")>0) {
npag++;
}
if (NR==nline) {
print npag;
linefound=1;
exit;
}
}
END {
if (!linefound) {
print 0;
}
}' ""
}
遇到\f
时,增加页码
NR
是当前行号。
----
关于历史,还有另一个 bash 版本。
此版本仅使用内置命令来计算当前页面中的行数。
您在评论中提供的 speedtest.sh
显示它有点超前(大约 20 秒),这与您的版本相同:
function get_page_from_line ()
{
local nline=""
local input_file=""
local npag=0
local total=0
while IFS= read -d $'\f' -r page; do
npag=$(( npag + 1 ))
IFS=$'\n'
for line in ${page}
do
total=$(( total + 1 ))
if [[ total -eq nline ]] ; then
printf "%d\n" ${npag}
unset IFS
return
fi
done
unset IFS
done < "$input_file"
printf "0\n"
return
}
使用awk,你可以定义RS
(记录分隔符,默认换行符)到换页符(\f
)和IFS
(输入字段分隔符,默认任意序列水平空白)到换行符(\n
)并获得行数作为 "record" 中 "fields" 的行数,即 "page".
在您的数据中放置换页会在页面中产生一些空行,因此发生这种情况的地方计数不对。
awk -F '\n' -v RS='\f' '{ print NF }' file
如果$NF == ""
,您可以将数字减一,也许可以将所需页面的数字作为变量传递:
awk -F '\n' -v RS='\f' -v p="2" 'NR==p { print NF - ($NF == "") }' file
要获取特定行的页码,只需将 head -n number
提供给脚本,或遍历这些数字,直到计算出行的总和。
line=1
page=1
for count in $(awk -F '\n' -v RS='\f' '{ print NF - ($NF == "") }' file); do
old=$line
((line += count))
echo "Lines $old through line are on page $page"
((page++)
done
考虑一个包含分页 ASCII 控制字符的纯文本文件 "Form Feed" ($'\f'):
alpha\n
beta\n
gamma\n\f
one\n
two\n
three\n
four\n
five\n\f
earth\n
wind\n
fire\n
water\n\f
请注意,每页的行数是随机的。
需要一个 bash 例程,该例程 return 包含分页 ASCII 控制字符的文本文件中给定行号的页码。
经过长时间研究解决方案,我终于找到了这段代码:
function get_page_from_line
{
local nline=""
local input_file=""
local npag=0
local ln=0
local total=0
while IFS= read -d $'\f' -r page; do
npag=$(( ++npag ))
ln=$(echo -n "$page" | wc -l)
total=$(( total + ln ))
if [ $total -ge $nline ]; then
echo "${npag}"
return
fi
done < "$input_file"
echo "0"
return
}
但是,不幸的是,这个解决方案在某些情况下被证明非常慢。
有更好的解决方案吗?
谢谢!
awk
救援!
awk -v RS='\f' -v n=09 '[=10=]~"^"n"." || [=10=]~"\n"n"." {print NR}' file
3
更新锚定如下评论。
$ for i in $(seq -w 12); do awk -v RS='\f' -v n="$i"
'[=11=]~"^"n"." || [=11=]~"\n"n"." {print n,"->",NR}' file; done
01 -> 1
02 -> 1
03 -> 1
04 -> 2
05 -> 2
06 -> 2
07 -> 2
08 -> 2
09 -> 3
10 -> 3
11 -> 3
12 -> 3
这个 gnu awk 脚本为作为命令行参数给出的行号打印 "page":
BEGIN { ffcount=1;
search = ARGV[2]
delete ARGV[2]
if (!search ) {
print "Please provide linenumber as argument"
exit(1);
}
}
~ search { printf( "line %s is on page %d\n", search, ffcount) }
/[\f]/ { ffcount++ }
像 awk -f formfeeds.awk formfeeds.txt 05
一样使用它,其中 formfeeds.awk
是脚本,formfeeds.txt
是文件,'05' 是行号。
BEGIN
规则主要处理命令行参数。其他规则为简单规则:
~ search
当第一个字段与存储在search
中的命令行参数匹配时适用
/[\f]/
在有换页时适用
可以在 bash 本身中编写类似长度的脚本来定位和响应包含在文件中的嵌入式 <form-feed>
。 (它也适用于 POSIX shell,替代字符串索引和 expr
数学)例如,
#!/bin/bash
declare -i ln=1 ## line count
declare -i pg=1 ## page count
fname="${1:-/dev/stdin}" ## read from file or stdin
printf "\nln:pg text\n" ## print header
while read -r l; do ## read each line
if [ ${l:0:1} = $'\f' ]; then ## if form-feed found
((pg++))
printf "<ff>\n%2s:%2s '%s'\n" "$ln" "$pg" "${l:1}"
else
printf "%2s:%2s '%s'\n" "$ln" "$pg" "$l"
fi
((ln++))
done < "$fname"
示例输入文件
带有嵌入式 <form-feed>
的简单输入文件是用以下方法创建的:
$ echo -e "a\nb\nc\n\fd\ne\nf\ng\nh\n\fi\nj\nk\nl" > dat/affex.txt
当输出给出时:
$ cat dat/affex.txt
a
b
c
d
e
f
g
h
i
j
k
l
例子Use/Output
$ bash affex.sh <dat/affex.txt
ln:pg text
1: 1 'a'
2: 1 'b'
3: 1 'c'
<ff>
4: 2 'd'
5: 2 'e'
6: 2 'f'
7: 2 'g'
8: 2 'h'
<ff>
9: 3 'i'
10: 3 'j'
11: 3 'k'
12: 3 'l'
使用 read -d $'\f'
然后计算行数的想法很好。
这个版本可能看起来不够优雅:如果 nline
大于或等于文件中的行数,那么文件会被读取两次。
试一试,因为它超级快:
function get_page_from_line ()
{
local nline=""
local input_file=""
if [[ $(wc -l "${input_file}" | awk '{print }') -lt nline ]] ; then
printf "0\n"
else
printf "%d\n" $(( $(head -n ${nline} "${input_file}" | grep -c "^"$'\f') + 1 ))
fi
}
awk 的性能优于上面的 bash 版本。 awk 就是为这种文本处理而创建的。
试试这个测试版本:
function get_page_from_line ()
{
awk -v nline="" '
BEGIN {
npag=1;
}
{
if (index([=11=],"\f")>0) {
npag++;
}
if (NR==nline) {
print npag;
linefound=1;
exit;
}
}
END {
if (!linefound) {
print 0;
}
}' ""
}
遇到\f
时,增加页码
NR
是当前行号。
----
关于历史,还有另一个 bash 版本。
此版本仅使用内置命令来计算当前页面中的行数。
您在评论中提供的 speedtest.sh
显示它有点超前(大约 20 秒),这与您的版本相同:
function get_page_from_line ()
{
local nline=""
local input_file=""
local npag=0
local total=0
while IFS= read -d $'\f' -r page; do
npag=$(( npag + 1 ))
IFS=$'\n'
for line in ${page}
do
total=$(( total + 1 ))
if [[ total -eq nline ]] ; then
printf "%d\n" ${npag}
unset IFS
return
fi
done
unset IFS
done < "$input_file"
printf "0\n"
return
}
使用awk,你可以定义RS
(记录分隔符,默认换行符)到换页符(\f
)和IFS
(输入字段分隔符,默认任意序列水平空白)到换行符(\n
)并获得行数作为 "record" 中 "fields" 的行数,即 "page".
在您的数据中放置换页会在页面中产生一些空行,因此发生这种情况的地方计数不对。
awk -F '\n' -v RS='\f' '{ print NF }' file
如果$NF == ""
,您可以将数字减一,也许可以将所需页面的数字作为变量传递:
awk -F '\n' -v RS='\f' -v p="2" 'NR==p { print NF - ($NF == "") }' file
要获取特定行的页码,只需将 head -n number
提供给脚本,或遍历这些数字,直到计算出行的总和。
line=1
page=1
for count in $(awk -F '\n' -v RS='\f' '{ print NF - ($NF == "") }' file); do
old=$line
((line += count))
echo "Lines $old through line are on page $page"
((page++)
done