我有一个文本文件,有很多行。我也有一定数量的线要打印出来,按一定的顺序排列。比方说,例如,“5,3,10,6”。按此顺序。
这样做有一些简单而“规范”的方法吗? (使用“标准”Linux工具和bash)
当我尝试这个问题的答案时
Bash tool to get nth line from a file
它始终按照文件中的顺序打印行。
答案 0 :(得分:3)
使用sed的单线:
for i in 5 3 10 6 ; do sed -n "${i}p" < ff; done
答案 1 :(得分:2)
以下是使用awk的一种方法:
awk -v s='5,3,10,6' 'BEGIN{split(s, a, ","); for (i=1; i<=length(a); i++) b[a[i]]=i}
b[NR]{data[NR]=$0} END{for (i=1; i<=length(a); i++) print data[a[i]]}' file
<强>测试强>
cat file
Line 1
Line 2
Line 3
Line 4
Line 5
Line 6
Line 7
Line 8
Line 9
Line 10
Line 11
Line 12
awk -v s='5,3,10,6' 'BEGIN{split(s, a, ","); for (i=1; i<=length(a); i++) b[a[i]]=i}
b[NR]{data[NR]=$0} END{for (i=1; i<=length(a); i++) print data[a[i]]}' file
Line 5
Line 3
Line 10
Line 6
答案 2 :(得分:2)
如果你的文件不是太大,一个相当有效的方法是在内存中读取所有内容,在数组中,使用mapfile
每个字段一行(这是Bash≥4内置):
mapfile -t array < file.txt
然后你可以按任何顺序回显你想要的所有行,例如,
printf '%s\n' "${array[4]}" "${array[2]}" "${array[9]}" "${array[5]}"
打印第5行,第3行,第10行,第6行。现在你会觉得数组字段以0
开头有点尴尬,所以你必须要抵消你的数字。使用-O
的{{1}}选项
mapfile
这将开始分配到索引1的mapfile -t -O 1 array < file.txt
,以便您可以将第5,3,10和6行打印为:
array
最后,你想为此创建一个包装函数:
printf '%s\n' "${array[5]}" "${array[3]}" "${array[10]}" "${array[6]}"
这样你就可以说:
printlines() {
local i
for i; do printf '%s\n' "${array[i]}"; done
}
这都是纯粹的Bash,没有外部工具!
正如@glennjackmann在评论中建议你可以让辅助函数也负责读取文件(作为参数传递):
printlines 5 3 10 6
然后您可以将其用作:
printlinesof() {
# $1 is filename
# $2,... are the lines to print
local i array
mapfile -t -O 1 array < "$1" || return 1
shift
for i; do printf '%s\n' "${array[i]}"; done
}
如果你还想处理stdin:
printlinesof file.txt 5 3 10 6
这样
printlinesof() {
# $1 is filename or - for stdin
# $2,... are the lines to print
local i array file=$1
[[ $file = - ]] && file=/dev/stdin
mapfile -t -O 1 array < "$file" || return 1
shift
for i; do printf '%s\n' "${array[i]}"; done
}
也可以。
答案 3 :(得分:1)
首先,生成一个sed表达式,该表达式将在开头打印一个数字,以后可以用来对输出进行排序:
#!/bin/bash
lines=(5 3 10 6)
sed=''
i=0
for line in "${lines[@]}" ; do
sed+="${line}s/^/$((i++)) /p;"
done
for i in {a..z} ; do echo $i ; done \
| sed -n "$sed" \
| sort -n \
| cut -d' ' -f2-
我可能会使用Perl,但是:
for c in {a..z} ; do echo $c ; done \
| perl -e 'undef @lines{@ARGV};
while (<STDIN>) {
$lines{$.} = $_ if exists $lines{$.};
}
print @lines{@ARGV};
' 5 3 10 6
您也可以在第一个解决方案中使用Perl而不是使用sed进行黑客攻击:
for c in {a..z} ; do echo $c ; done \
| perl -e ' %lines = map { $ARGV[$_], ++$i } 0 .. $#ARGV;
while (<STDIN>) {
print "$lines{$.} $_" if exists $lines{$.};
}
' 5 3 10 6 | sort -n | cut -d' ' -f2-
答案 4 :(得分:0)
l=(5 3 10 6)
printf "%s\n" {a..z} |
sed -n "$(printf "%d{=;p};" "${l[@]}")" |
paste - - | {
while IFS=$'\t' read -r nr text; do
line[nr]=$text
done
for n in "${l[@]}"; do
echo "${line[n]}"
done
}
答案 5 :(得分:0)
您可以使用nl
技巧:对输入中的行进行编号,并将输出与实际行号列表连接起来。需要额外的排序以使join
成为可能,因为它需要排序输入(因此nl
技巧再次使用预期行的数量):
#! /bin/bash
LINES=(5 3 10 6)
lines=$( IFS=$'\n' ; echo "${LINES[*]}" | nl )
for c in {a..z} ; do
echo $c
done | nl \
| grep -E '^\s*('"$( IFS='|' ; echo "${LINES[*]}")"')\s' \
| join -12 -21 <(echo "$lines" | sort -k2n) - \
| sort -k2n \
| cut -d' ' -f3-