2014-06-11 73 views

回答

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開頭,以便您必須抵消數字。

mapfile -t -O 1 array < file.txt 

這將啓動索引1分配給array,這樣就可以打印線5,3,10和6:

這可以通過 -O選項 mapfile很容易治癒
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 

如果你也想標準輸入處理:

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 

也會起作用。

+1

+1非常好。 'mapfile'需要bash v4。我會通過傳遞文件名並在函數中執行mapfile來增強它:'printlines(){local i array; mapfile -t -O 1數組<「$ 1」;轉移;爲我;做printf'%s \ n'「$ {array [i]}」;完成; }; printlines file.txt 5 3 10 6' –

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

哇,這一切都是那麼複雜。我認爲會有一些簡單的事情,因爲unix工具是用於文本處理的「製造」 –

+0

它看起來可能很複雜,但這只是我知道使用**單個命令**完成此操作的唯一方法。複雜性的原因在於,所有工具都逐行處理輸入數據,因此以預先定義的方式獲得輸出,需要先處理文件,然後按指定順序打印。 – anubhava

+0

另外我建議在一個非常大的文件上使用所有建議的解決方案運行測試。我在這裏添加了額外的代碼,以確保我只緩存內存中給定的行號而不是緩存所有文件。 – anubhava

3

甲一個襯裏使用SED:

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

這會讀取文件* n *次。不可擴展性。 – tripleee

+0

for i(5 3 10 6)sed -n「$ {i} p」 zzapper

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 

您還可以使用的,而不是在第一個解決方案的sed黑客的Perl:

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

可以使用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- 
相關問題