以特定顺序从文件中选择某些行的简便方法

时间:2014-06-11 14:05:27

标签: linux bash

我有一个文本文件,有很多行。我也有一定数量的线要打印出来,按一定的顺序排列。比方说,例如,“5,3,10,6”。按此顺序。

这样做有一些简单而“规范”的方法吗? (使用“标准”Linux工具和bash)

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

Bash tool to get nth line from a file

它始终按照文件中的顺序打印行。

6 个答案:

答案 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-