2014-06-11 33 views
5

Tôi có tệp văn bản, có nhiều dòng. Tôi cũng có một số dòng được chọn mà tôi muốn in ra, theo thứ tự nhất định. Ví dụ: "5, 3, 10, 6". Theo thứ tự này.Cách dễ dàng để chọn một số dòng nhất định từ một tệp theo một thứ tự nhất định

Có cách nào dễ dàng và "kinh điển" để thực hiện việc này không? (Với công cụ Linux "tiêu chuẩn", và bash)

Khi tôi cố gắng câu trả lời từ câu hỏi này

Bash tool to get nth line from a file

nó luôn in các dòng theo thứ tự họ đang có trong tập tin.

Trả lời

2

Một phương pháp khá hiệu quả nếu bạn tệp không quá lớn là đọc tất cả trong bộ nhớ, trong một mảng, một dòng trên mỗi trường sử dụng mapfile (đây là nội dung Bash ≥4):

mapfile -t array < file.txt 

Sau đó, bạn có thể echo tất cả các dòng bạn muốn trong bất kỳ thứ tự, ví dụ:

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

để in các dòng 5, 3, 10, 6. Bây giờ bạn sẽ cảm thấy đó là một chút vụng về rằng các trường mảng bắt đầu bằng một số 0 để bạn phải bù đắp số của mình. Điều này có thể dễ dàng chữa khỏi với -O tùy chọn mapfile:

mapfile -t -O 1 array < file.txt 

này sẽ bắt đầu gán để array tại chỉ số 1, do đó bạn có thể in dòng của bạn 5, 3, 10 và 6 như:

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

Cuối cùng, bạn muốn thực hiện một chức năng bao bọc cho việc này:

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

để bạn c một trạng thái chỉ:

printlines 5 3 10 6 

Và tất cả đều là Bash thuần túy, không có công cụ bên ngoài!


Như @glennjackmann gợi ý trong các ý kiến ​​bạn có thể tận dụng chức năng helper cũng chăm sóc của việc đọc các tập tin (thông qua như là đối số):

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 
} 

Sau đó, bạn có thể sử dụng nó như:

printlinesof file.txt 5 3 10 6 

Và nếu bạn cũng muốn xử lý 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 
} 

sao cho

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

cũng sẽ hoạt động.

+1

+1 rất đẹp. Yêu cầu bash v4 cho 'mapfile'. Tôi muốn tăng cường điều đó bằng cách truyền tên tệp và thực hiện mapfile trong hàm: 'printlines() {local i array; mapfile -t -O 1 mảng <"$ 1"; thay đổi; cho tôi; làm printf '% s \ n' "$ {mảng [i]}"; làm xong; }; printlines file.txt 5 3 10 6' –

+1

Tôi thích câu trả lời này nhất, ngay cả khi nó không "mở rộng" nếu tệp quá lớn. –

2

Đây là một trong những cách sử dụng 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 

Thử nghiệm:

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 
+0

Wow, đây là tất cả rất phức tạp. Tôi cho rằng sẽ có một cái gì đó đơn giản, cho rằng các công cụ unix được "làm" để xử lý văn bản –

+0

Nó có thể trông phức tạp nhưng đây chỉ là cách tôi biết để có được điều này được thực hiện bằng cách sử dụng một ** đơn lệnh **. Lý do phức tạp là tất cả các công cụ xử lý dòng dữ liệu đầu vào theo từng dòng để có được đầu ra theo cách được xác định trước mà trước tiên cần xử lý tệp và sau đó in theo thứ tự được chỉ định. – anubhava

+0

Ngoài ra tôi đề nghị chạy thử nghiệm với tất cả các giải pháp được đề xuất trên một tệp rất lớn. Tôi đã bổ sung thêm một chút mã ở đây để đảm bảo rằng tôi chỉ lưu các số dòng trong bộ nhớ thay vì lưu vào bộ nhớ đệm tất cả các tệp. – anubhava

3

Một lót sử dụng sed:

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

Điều này đọc tập tin * n * lần. Không phải là rất khả năng mở rộng. – tripleee

+0

cho i (5 3 10 6) sed -n "$ {i} p" zzapper

1

Đầu tiên, tạo một biểu sed mà có in các dòng với một số ở đầu mà sau này bạn có thể sử dụng để sắp xếp đầu ra :

#!/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- 

tôi là lẽ sử dụng Perl, mặc dù:

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 

Bạn cũng có thể sử dụng Perl thay vì hack với sed trong dung dịch đầu tiên:

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- 
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 
} 
0

Bạn có thể sử dụng thủ thuật nl: đánh số các dòng trong đầu vào và tham gia đầu ra với danh sách số dòng thực tế. các loại bổ sung là cần thiết để làm cho join càng tốt vì nó cần đầu vào sắp xếp (để lừa nl được sử dụng một lần nữa số lượng các đường dự kiến):

#! /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- 
Các vấn đề liên quan