[英]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
行和第4
, p
(打印)命令用於打印這些行。
您還可以使用范圍,例如:
sed -n '2,4p' inputFile
兩個純 Bash 版本。 由於您正在尋找通用且可重用的解決方案,因此您不妨在這方面付出一些努力。 (另請參閱最后一節)。
該腳本將整個 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
此版本解決了之前的無限流問題。 但是您將失去重復和重新排序行的能力。
同樣的事情,范圍是允許的:
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 和版本 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
嘗試這個 :
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.