[英]Use Bash scripting to select columns and rows with specific name
我正在使用一個非常大的文本文件(4GB),並且我想使用僅需要的數據制作一個較小的文件。 它是制表符分隔的文件,並且具有行標題和列標題。 我基本上想選擇具有給定列和/或行名稱的數據子集。
colname_1 colname_2 colname_3 colname_4
row_1 1 2 3 5
row_2 4 6 9 1
row_3 2 3 4 2
我計划有一個包含我想要的列列表的文件。
colname_1 colname_3
我是bash腳本的新手,我真的不知道該怎么做。 我看到了其他示例,但是它們都提供了他們預先想要的列號,而我沒有。 抱歉,如果這是重復問題,我嘗試搜索。
我希望結果是
colname_1 colname_3
row_1 1 3
row_2 2 9
row_3 2 4
實際上,您可以通過跟蹤與包含列列表的文件中的列 名稱匹配的列的數組索引來做到這一點。 在數據文件中找到列列表文件中列名稱的數組索引后,您只需讀取數據文件(從第二行開始),然后輸出row_label以及確定的數組索引處的列數據將列列表文件與原始列進行匹配。
可能有幾種方法可以解決此問題,以下假設每列中的數據不包含任何空格。 數組的使用假定為bash(或其他高級shell支持數組),而不是POSIX shell。
該腳本將兩個文件名作為輸入。 第一個是您的原始數據文件。 第二個是您的列列表文件。 一種方法可以是:
#!/bin/bash
declare -a cols ## array holding original columns from original data file
declare -a csel ## array holding columns to select (from file 2)
declare -a cpos ## array holding array indexes of matching columns
cols=( $(head -n 1 "$1") ) ## fill cols from 1st line of data file
csel=( $(< "$2") ) ## read select columns from file 2
## fill column position array
for ((i = 0; i < ${#csel[@]}; i++)); do
for ((j = 0; j < ${#cols[@]}; j++)); do
[ "${csel[i]}" = "${cols[j]}" ] && cpos+=( $j )
done
done
printf " "
for ((i = 0; i < ${#csel[@]}; i++)); do ## output header row
printf " %s" "${csel[i]}"
done
printf "\n" ## output newline
unset cols ## unset cols to reuse in reading lines below
while read -r line; do ## read each data line in data file
cols=( $line ) ## separate into cols array
printf "%s" "${cols[0]}" ## output row label
for ((j = 0; j < ${#cpos[@]}; j++)); do
[ "$j" -eq "0" ] && { ## handle format for first column
printf "%5s" "${cols[$((${cpos[j]}+1))]}"
continue
} ## output remaining columns
printf "%13s" "${cols[$((${cpos[j]}+1))]}"
done
printf "\n"
done < <( tail -n+2 "$1" )
使用示例數據,如下所示:
資料檔案
$ cat dat/col+data.txt
colname_1 colname_2 colname_3 colname_4
row_1 1 2 3 5
row_2 4 6 9 1
row_3 2 3 4 2
列選擇文件
$ cat dat/col.txt
colname_1 colname_3
使用/輸出示例
$ bash colnum.sh dat/col+data.txt dat/col.txt
colname_1 colname_3
row_1 1 3
row_2 4 9
row_3 2 4
試試看,如果您有任何疑問,請告訴我。 請注意,bash以處理大型文件的盲目速度而聞名,但只要列列表的長度不可怕,腳本就應該相當快。
Bash在標准命令行實用程序之間的“膠水”效果最佳。 您可以編寫循環來讀取海量文件中的每一行,但由於bash並未針對速度進行優化,因此循環速度非常慢。 因此,讓我們看看如何使用一些標准實用程序(grep,tr,剪切和粘貼)來實現此目標。
為簡單起見,讓我們將所需的列標題放入文件中,每行一個。 (您總是可以將制表符分隔的列標題行轉換為這種格式;我們將只使用數據文件的列標題來做到這一點。但是一次只能做一件事。)
$ printf '%s\n' colname_{1,3} > columns
$ cat columns
colname_1
colname_2
printf命令行實用程序的一個重要功能是,它重復其格式,直到用完參數為止。
現在,我們想知道這些列標題中的每個對應於數據文件中的哪一列。 我們可以嘗試將其寫為awk甚至bash中的循環,但是如果將數據文件的標題行轉換為每行一個標題的文件,則可以使用-n
選項使用grep告訴我們(在輸出的前面加上匹配項的行號)。
由於列標題是制表符分隔的,因此我們可以使用tr
將制表符轉換為換行符,從而將它們轉換為單獨的行:
$ head -n1 giga.dat | tr '\t' '\n'
colname_1
colname_2
colname_3
colname_4
請注意開頭的空白行。 這很重要,因為colname_1
實際上對應於第2列,因為行標題位於第1列中。
因此,讓我們查找列名。 在這里,我們將使用幾個grep
選項:
-F
模式參數由幾種模式組成,每行一種,被解釋為普通字符串而不是正則表達式。 -x
模式必須與整行匹配。 -n
輸出應以匹配的行號為前綴。 如果我們有Gnu grep
,我們也可以使用-f columns
從名為columns
的文件中讀取模式。 或者,如果我們使用bash,則可以使用bashism "$(<columns)"
將文件的內容作為grep的單個參數插入。 但是現在,我們將保持與Posix兼容:
$ head -n1 giga.dat | tr '\t' '\n' | grep -Fxn "$(cat columns)"
2:colname_1
4:colname_3
好,那很接近。 我們只需要除去行號以外的所有內容即可; 以逗號分隔數字,並在開頭放置1。
$ { echo 1
> grep -Fxn "$(<columns)" < <(head -n1 giga.dat | tr '\t' '\n')
> } | cut -f1 -d: | paste -sd,
1,2,4
cut -f1
選擇字段1。自變量可以是逗號分隔的列表,如cut -f1,2,4
。 cut -d:
使用:
代替制表符作為字段分隔符(“定界符”) paste -s
連接單個文件的行,而不是多個文件的相應行 paste -d,
使用逗號代替制表符作為字段分隔符。 因此,現在我們需要傳遞參數以cut
以選擇所需的列:
$ cut -f"$({ echo 1
> head -n1 giga.dat | tr '\t' '\n' | grep -Fxn -f columns
> } | cut -f1 -d: | paste -sd,)" giga.dat
colname_1 colname_3
row_1 1 3
row_2 4 9
row_3 2 4
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.