我希望有一个名为lines.sh
的脚本,我可以将数据通过管道来选择一系列行。
例如,如果我有以下文件:
的test.txt
a
b
c
d
然后我可以跑:
cat test.txt | lines 2,4
并输出
b
d
我使用的是zsh,但如果可能的话,我更喜欢使用bash解决方案。
答案 0 :(得分:7)
你可以使用这个awk:
awk -v s='2,4' 'BEGIN{split(s, a, ","); for (i in a) b[a[i]]} NR in b' file
two
four
通过单独的脚本lines.sh
:
#!/bin/bash
awk -v s="$1" 'BEGIN{split(s, a, ","); for (i in a) b[a[i]]} NR in b' "$2"
然后给予执行权限:
chmod +x lines.sh
并将其命名为:
./lines.sh '2,4' 'test.txt'
答案 1 :(得分:3)
尝试sed
:
sed -n '2p; 4p' inputFile
-n
告诉sed
取消输出,但对于行2
和4
,p
(打印)命令用于打印这些行
您也可以使用范围,例如:
sed -n '2,4p' inputFile
答案 2 :(得分:3)
两个纯Bash版本。既然您正在寻找通用和可重用的解决方案,那么您也可以付出一些努力。 (另见最后一节)。
这个脚本将整个stdin放入一个数组(使用mapfile
,因此效率很高),然后打印其参数中指定的行。范围是有效的,例如,
1-4 # for lines 1, 2, 3 and 4
3- # for everything from line 3 till the end of the file
您可以用空格或逗号分隔这些。这些行完全按照给出参数的顺序打印:
lines 1 1,2,4,1-3,4- 1
将打印第1行两次,然后第2行,第4行,第1行,第2行和第3行,然后是第4行到结束的所有内容,最后是第1行。
你走了:
#!/bin/bash
lines=()
# Slurp stdin in array
mapfile -O1 -t lines
# Arguments:
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
# Process arguments
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
# If nothing to read, exit
((tillend==0 && ${#lines[@]}==0)) && exit
# Now read stdin
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和版本2的功能的超级通用脚本,那么你绝对应该考虑使用其他语言,例如Perl:你会获得很多(特别是速度)!你将能够有很好的选择,可以做很多很酷的东西。从长远来看,它可能是值得的,因为您需要一个通用且可重用的脚本。你甚至可能最终得到一个阅读电子邮件的脚本!
免责声明。我还没有彻底检查过这些脚本......所以要小心错误!
答案 3 :(得分:2)
嗯,只要:
你可以使用类似的东西:
cat test.txt |tr "\\n" ";"|cut -d';' -f2,4|tr ";" "\\n"
其中-f2,4表示要提取的行
答案 4 :(得分:2)
快速解决方案给你的朋友。 输入:
<强>的test.txt
a
b
c
d
e
f
g
h
i
j
<强> test.sh
lines (){
sed -n "$( echo "$@" | sed 's/[0-9]\+/&p;/g')"
}
cat 1.txt | lines 1 5 10
或者,如果您想将lines
作为脚本:
<强> lines.sh
IFS=',' read -a lines <<< "$1"; sed -n "$( echo "${lines[@]}" | sed 's/[0-9]\+/&p;/g')" "$2"
./lines.sh 1,5,10 test.txt
两种情况下的输出:
a
e
j
答案 5 :(得分:2)
答案 6 :(得分:1)
试试这个:
file=$1
for var in "$@" //var is all line numbers
do
sed -n "${var}p" $file
done
我创建了一个带有1个文件参数的脚本,以及无限数量的行号参数。你会这样称呼它:
lines txt 2 3 4...etc