从某个文件中按特定顺序select某些行的简单方法

我有一个文本文件,有很多行。 我也有一些我想要打印的行数,按照一定的顺序。 比方说,“5,3,10,6”。 按此顺序。

有没有一些容易和“规范”的方式做到这一点? (使用“标准”Linux工具和bash

当我尝试从这个问题的答案

Bash工具从文件中获得第n行

它总是打印行,以便他们在文件中。

如果你的文件不是太大的话,一个相当有效的方法是在内存中读取所有内容,使用mapfile (这是一个Bash≥4内建),每个字段每行一行。

 mapfile -t array < file.txt 

然后你可以以任何顺序回显你想要的所有行,例如,

 printf '%s\n' "${array[4]}" "${array[2]}" "${array[9]}" "${array[5]}" 

打印第5,3,10,6行。现在你会感觉到数组字段以0开头,这样就不得不抵消你的数字。 这可以通过mapfile-O选项轻松解决:

 mapfile -t -O 1 array < file.txt 

这将开始分配在索引1的array ,以便您可以打印您的行5,3,10和6为:

 printf '%s\n' "${array[5]}" "${array[3]}" "${array[10]}" "${array[6]}" 

最后,你想为此做一个包装函数:

 printlines() { local i for i; do printf '%s\n' "${array[i]}"; done } 

这样你就可以说明:

 printlines 5 3 10 6 

而且这都是纯粹的Bash,没有外部工具!


正如@glennjackmann在注释中所建议的那样,你可以让帮助函数也照顾读取文件(作为参数传递):

 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 } 

那么你可以使用它:

 printlinesof file.txt 5 3 10 6 

如果你还想处理stdin:

 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 } 

以便

 printf '%s\n' {a..z} | printlinesof - 5 3 10 6 

也将工作。

一个使用sed的班轮:

 for i in 5 3 10 6 ; do sed -n "${i}p" < ff; done 

这里是使用awk的一种方法:

 awk -vs='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 -vs='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 

首先,生成一个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- 
 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 } 

您可以使用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-