簡體   English   中英

如何從文件或腳本中的管道中選擇多行?

[英]How to select multiple lines from a file or from pipe in a script?

我想要一個名為lines.sh的腳本,我可以將數據通過管道傳輸到其中以選擇一系列行。

例如,如果我有以下文件:

測試.txt

a 
b
c
d

然后我可以運行:

cat test.txt | lines 2,4

它會輸出

b
d

我正在使用 zsh,但如果可能的話,我更喜歡 bash 解決方案。

你可以使用這個awk:

awk -v s='2,4' 'BEGIN{split(s, a, ","); for (i in a) b[a[i]]} NR in b' file
two
four

通過單獨的腳本lines.sh

#!/bin/bash
awk -v s="$1" 'BEGIN{split(s, a, ","); for (i in a) b[a[i]]} NR in b' "$2"

然后賦予執行權限:

chmod +x lines.sh

並將其稱為:

./lines.sh '2,4' 'test.txt'

試試sed

sed -n '2p; 4p' inputFile

-n告訴sed抑制輸出,但對於第2行和第4p (打印)命令用於打印這些行。

您還可以使用范圍,例如:

sed -n '2,4p' inputFile

兩個純 Bash 版本。 由於您正在尋找通用且可重用的解決方案,因此您不妨在這方面付出一些努力。 (另請參閱最后一節)。

版本 1

該腳本將整個 stdin 放入一個數組中(使用mapfile ,因此效率很高),然后打印在其參數中指定的行。 范圍是有效的,例如,

1-4 # for lines 1, 2, 3 and 4
3-  # for everything from line 3 till the end of the file

您可以用空格或逗號將它們分開。 這些行完全按照給定參數的順序打印:

lines 1 1,2,4,1-3,4- 1

將打印第 1 行兩次,然后是第 2 行,然后是第 4 行,然后是第 1、2 和 3 行,然后是從第 4 行到最后的所有內容,最后再次打印第 1 行。

干得好:

#!/bin/bash

lines=()

# Slurp stdin in array
mapfile -O1 -t lines

# Arguments:
IFS=', ' read -ra args <<< "$*"

for arg in "${args[@]}"; do
   if [[ $arg = +([[:digit:]]) ]]; then
      arg=$arg-$arg
   fi
   if [[ $arg =~ ([[:digit:]]+)-([[:digit:]]*) ]]; then
      ((from=10#${BASH_REMATCH[1]}))
      ((to=10#${BASH_REMATCH[2]:-$((${#lines[@]}))}))
      ((from==0)) && from=1
      ((to>=${#lines[@]})) && to=${#lines[@]}
      ((from<=to)) || printf >&2 'Argument %d-%d: lines not in increasing order' "$from" "$to"
      for((i=from;i<=to;++i)); do
         printf '%s\n' "${lines[i]}"
      done
   else
      printf >&2 "Error in argument \`%s'.\n" "$arg"
   fi
done
  • 親:真的很酷。
  • 缺點:需要將整個流讀入內存。 不適合無限流。

版本 2

此版本解決了之前的無限流問題。 但是您將失去重復和重新排序行的能力。

同樣的事情,范圍是允許的:

lines 1 1,4-6 9-

將打印第 1、4、5、6、9 行以及所有內容直到最后。 如果行集有界,則在讀取最后一行后立即退出。

#!/bin/bash

lines=()
tillend=0
maxline=0

# Process arguments
IFS=', ' read -ra args <<< "$@"

for arg in "${args[@]}"; do
   if [[ $arg = +([[:digit:]]) ]]; then
       arg=$arg-$arg
   fi
   if [[ $arg =~ ([[:digit:]]+)-([[:digit:]]*) ]]; then
      ((from=10#${BASH_REMATCH[1]}))
      ((from==0)) && from=1
      ((tillend && from>=tillend)) && continue
      if [[ -z ${BASH_REMATCH[2]} ]]; then
         tillend=$from
         continue
      fi
      ((to=10#${BASH_REMATCH[2]}))
      if ((from>to)); then
         printf >&2 "Invalid lines order: %s\n" "$arg"
         exit 1
      fi
      ((maxline<to)) && maxline=$to
      for ((i=from;i<=to;++i)); do
         lines[i]=1
      done
   else
      printf >&2 "Invalid argument \`%s'\n" "$arg"
      exit 1
   fi
done

# If nothing to read, exit
((tillend==0 && ${#lines[@]}==0)) && exit

# Now read stdin
linenb=0
while IFS= read -r line; do
   ((++linenb))
   ((tillend==0 && maxline && linenb>maxline)) && exit
   if [[ ${lines[linenb]} ]] || ((tillend && linenb>=tillend)); then
      printf '%s\n' "$line"
   fi
done
  • 優點:它真的很酷,並且不會讀取內存中的完整流。
  • 缺點:不能像版本 1 那樣重復或重新排列行。速度不是它的最強點。

進一步的想法

如果你真的想要一個很棒的通用腳本來完成版本 1 和版本 2 的功能,以及更多,你絕對應該考慮使用另一種語言,例如 Perl:你會獲得很多(特別是速度)! 你將能夠有很好的選擇,可以做很多更酷的事情。 從長遠來看,這可能是值得的,因為您需要一個通用且可重用的腳本。 您甚至可能最終擁有一個閱讀電子郵件的腳本!


免責聲明。 我還沒有徹底檢查這些腳本......所以要小心錯誤!

好吧,前提是:

  • 你的文件足夠小
  • 您在文件中沒有任何分號(或您選擇的其他特定字符)
  • 你不介意使用多個管道

你可以使用類似的東西:

cat test.txt |tr "\\n" ";"|cut -d';' -f2,4|tr ";" "\\n"

其中 -f2,4 表示要提取的行

您朋友的快速解決方案。 輸入:

測試.txt

a
b
c
d
e
f
g
h
i
j

測試文件

lines (){
sed -n "$( echo "$@" | sed 's/[0-9]\+/&p;/g')"
}

cat 1.txt | lines 1 5 10

或者,如果您想將lines作為腳本:

行.sh

IFS=',' read -a lines <<< "$1"; sed -n "$( echo "${lines[@]}" | sed 's/[0-9]\+/&p;/g')" "$2"

./lines.sh 1,5,10 test.txt

兩種情況下的輸出:

a
e
j

如果這是一次性操作並且沒有很多行可以選擇,您可以使用pick手動選擇它們:

cat test.txt | pick | ...

將打開一個交互式屏幕,允許您選擇所需的內容。

嘗試這個 :

file=$1
for var in "$@"  //var is all line numbers
do
sed -n "${var}p" $file
done

我創建了一個帶有 1 個文件參數和無限數量的行號參數的腳本。 你會這樣稱呼它:

lines txt 2 3 4...etc

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM