两个纯Bash版本。由于您正在寻找通用和可重复使用的解决方案,因此最好在其中投入一点精力(另请参见最后一节)。
版本1
此脚本将整个stdin读入数组中(使用mapfile
,因此效率相当高),然后打印其参数指定的行。范围是有效的,例如,
1-4
3-
您可以通过空格或逗号将它们分开。行的打印顺序与给定参数的顺序完全相同:
lines 1 1,2,4,1-3,4- 1
这段代码会先输出两次第一行,然后是第二行,接着是第四行,再输出第一、二、三行,最后输出从第四行到结尾的所有内容,最终再输出一次第一行。
#!/bin/bash
lines=()
mapfile -O1 -t lines
IFS=', ' read -ra args <<< "$*"
for arg in "${args[@]}"; do
if [[ $arg = +([[:digit:]]) ]]; then
arg=$arg-$arg
fi
if [[ $arg =~ ([[:digit:]]+)-([[:digit:]]*) ]]; then
((from=10#${BASH_REMATCH[1]}))
((to=10#${BASH_REMATCH[2]:-$((${#lines[@]}))}))
((from==0)) && from=1
((to>=${#lines[@]})) && to=${#lines[@]}
((from<=to)) || printf >&2 'Argument %d-%d: lines not in increasing order' "$from" "$to"
for((i=from;i<=to;++i)); do
printf '%s\n' "${lines[i]}"
done
else
printf >&2 "Error in argument \`%s'.\n" "$arg"
fi
done
- 优点:它真的很酷。
- 缺点:需要将整个流读入内存。不适用于无限流。
第二版
该版本解决了无限流的问题。但您将失去重复和重新排序行的能力。
同样,范围是允许的:
lines 1 1,4-6 9-
将打印第1、4、5、6、9行以及之后的所有内容。如果行数有限,则读取到最后一行时退出。
#!/bin/bash
lines=()
tillend=0
maxline=0
IFS=', ' read -ra args <<< "$@"
for arg in "${args[@]}"; do
if [[ $arg = +([[:digit:]]) ]]; then
arg=$arg-$arg
fi
if [[ $arg =~ ([[:digit:]]+)-([[:digit:]]*) ]]; then
((from=10#${BASH_REMATCH[1]}))
((from==0)) && from=1
((tillend && from>=tillend)) && continue
if [[ -z ${BASH_REMATCH[2]} ]]; then
tillend=$from
continue
fi
((to=10#${BASH_REMATCH[2]}))
if ((from>to)); then
printf >&2 "Invalid lines order: %s\n" "$arg"
exit 1
fi
((maxline<to)) && maxline=$to
for ((i=from;i<=to;++i)); do
lines[i]=1
done
else
printf >&2 "Invalid argument \`%s'\n" "$arg"
exit 1
fi
done
((tillend==0 && ${#lines[@]}==0)) && exit
linenb=0
while IFS= read -r line; do
((++linenb))
((tillend==0 && maxline && linenb>maxline)) && exit
if [[ ${lines[linenb]} ]] || ((tillend && linenb>=tillend)); then
printf '%s\n' "$line"
fi
done
- 优点:非常酷,不会在内存中读取整个流。
- 缺点:不能重复或重新排序行,就像版本1一样。速度不是它的强项。
进一步的想法
如果你真的想要一个做版本1和版本2所做的事情,并且还有更多功能的通用脚本,你绝对应该考虑使用另一种语言,比如Perl:你将获得很多好处(特别是速度)!你将能够拥有很棒的选项,可以做很多更酷的事情。从长远来看,这可能是值得的,因为你想要一个通用和可重用的脚本。你甚至可能最终得到一个读取电子邮件的脚本!
免责声明。我没有彻底检查过这些脚本...所以要注意错误!
$2
更改为${2:-/dev/stdin}
以便它能够接受管道输入。 - Brad Parks