
我有許多文件,每個文件的名稱中都包含特定模式,例如ABC1234001
攜帶有關某些資料組(多個列表)的資訊。我也有一個info.tsv
這樣的表:
group1 ABC1234001 ABC1234010
group2 ABC1234011 ABC1234018
group3 ABC1234019 ABC1234028
... ... ...
它包含了:
- 「group」列,指定群組,
- 「第一個檔案」列,指定包含對應群組資訊的第一個檔案的模式(按字母順序),
- 「最後一個檔案」列,指定包含對應群組資訊的最後一個檔案(按字母順序)的模式。
所以我需要做的是將每個組的文件合併到一個文件中 - 就像
cat ABC123401{1..8}* >> group2.tsv
以 group2 為例 - 在讀取此info.tsv
檔案時。在此給定範例中,所有文件 ( ABC1234011.tsv
, ABC1234012.tsv
, ABC1234013.tsv
, ABC1234014.tsv
, ABC1234015.tsv
, ABC1234016.tsv
, ABC1234017.tsv
, ABC1234018.tsv
) 都連接成一個group2.tsv
文件
我要做的事情如下:
while read $file; do
#assign columns to variables like $1="group", $2="firstfile", $3="lastfile"
cat *{$2..$3}* > $1.tsv;
done < info.tsv
但我不太確定如何迭代地更改這種方法的變數。也許使用awk
更有用,但我不知道。該腳本應該產生一堆名為group1.tsv
,的文件group2.tsv
,其中包含表中從“第一個文件”到“最後一個文件”的相應文件的內容。請幫我編寫腳本來執行此操作。
答案1
下面的腳本假設您可能想要連接的所有檔案都與模式相符*.tsv
。如果您知道它們全部匹配ABC*.tsv
,那麼您可能希望在腳本開頭使用該模式來代替*.tsv
.
該腳本還假設進入特定群組的所有檔案名稱都是作為*.tsv
擴展列表的連續子列表產生的。
#!/bin/sh
set -- *.tsv
while read -r group first last; do
collect=false
for name do
if ! "$collect"; then
[ "$name" = "$first.tsv" ] || continue
collect=true
fi
if "$collect"; then
cat -- "$name"
[ "$name" = "$last.tsv" ] && break
fi
done >"$group.tsv"
done <info.tsv
此腳本將位置參數清單設定為符合的名稱清單*.tsv
。然後,它將每行的三個欄位讀取到info.tsv
變數group
、first
和中last
。
對於以info.tsv
這種方式讀取的每一行,將掃描位置參數清單以尋找與群組中第一個名稱相符的名稱。一旦找到這個名字,我們就設定一個標誌 ,collect
它告訴腳本的邏輯從清單中的目前位置開始從位置參數清單中指定的檔案收集資料。一旦我們遇到與群組的姓氏相對應的名稱,這就結束。
請注意,此處true
和false
用作命令而不是簡單的字串。儲存在變數中的值$collect
正在執行,if ! "$collect"
這意味著腳本將運行兩個 shell 內建命令之一true
或false
. shell 不像其他語言(例如 Python)有任何特殊的 true 或 false 關鍵字。
測試:
$ ls
script
$ touch ABC{1234001..1234030}.tsv
$ for name in ABC*.tsv; do printf 'Name: %s\n' "$name" >"$name"; done
$ cat ABC1234015.tsv
Name: ABC1234015.tsv
$ cat >info.tsv <<END_DATA
group1 ABC1234001 ABC1234010
group2 ABC1234025 ABC1234030
END_DATA
$ ./script
$ cat group1.tsv
Name: ABC1234001.tsv
Name: ABC1234002.tsv
Name: ABC1234003.tsv
Name: ABC1234004.tsv
Name: ABC1234005.tsv
Name: ABC1234006.tsv
Name: ABC1234007.tsv
Name: ABC1234008.tsv
Name: ABC1234009.tsv
Name: ABC1234010.tsv
$ cat group2.tsv
Name: ABC1234025.tsv
Name: ABC1234026.tsv
Name: ABC1234027.tsv
Name: ABC1234028.tsv
Name: ABC1234029.tsv
Name: ABC1234030.tsv
正如對此答案的評論中提到的,我開發此腳本供我個人使用的方式是讓腳本看起來像這樣:
#!/bin/sh
while read -r group first last; do
collect=false
for name do
filename=$( basename "$name" )
if ! "$collect"; then
[ "$filename" = "$first.tsv" ] || continue
collect=true
fi
if "$collect"; then
cat -- "$name"
[ "$filename" = "$last.tsv" ] && break
fi
done >"$group.tsv"
done
set
請注意頂部命令的刪除(這將被命令列參數替換),以及重定向的刪除info.tsv
(這將被命令列上的重定向替換)。我還引入了一個filename
變量,它將保存命令行上給出的路徑名的文件名部分。
然後我會像這樣運行腳本:
$ ./script ABC*.tsv <info.tsv
我用這個實現的是一個腳本,它不知道輸入組列表的存儲位置或其名稱,並且不關心文件的名稱ABC
(只要它們具有.tsv
文件名後綴)或它們的存儲位置。
答案2
您的方法是一個好主意,但不幸的是它不起作用,因為變數不會在大括號擴展內擴展:
$ echo {1..5}
1 2 3 4 5
$ a=1
$ b=5
$ echo {$a..$b}
{1..5}
你可以透過使用以下方法來解決這個問題eval
:
sed 's/ABC//g' info.tsv |
while read -r group start end; do
files=( $(eval echo ABC{$start..$end}.tsv) )
cat "${files[@]}" > "$group.tsv";
done
這將首先ABC
從info.tsv
文件中刪除 的所有實例,以便我們可以單獨獲取數字。請注意,這假設了您向我們展示的確切資料結構。如果ABC
也可以出現在組名中,那麼這就會被打破。
刪除 後ABC
,結果將透過管道傳輸到while
讀取三個變數的循環中:$group
、$start
和$end
。然後將它們傳遞給eval
將在調用大括號擴展之前擴展變量的變量,從而允許您獲取文件名列表:
$ eval echo ABC{1..5}
ABC1 ABC2 ABC3 ABC4 ABC5
的結果eval
儲存在$files
數組中,該數組作為輸入傳遞給cat
:
cat "${files[@]}" > "$group.tsv";
答案3
如果我理解正確的話,這是一個選項
$ while IFS= read -r i; do
f=$(echo "$i" | cut -d' ' -f1)
cat $(echo "$i" | cut -d' ' -f2- | sed -E 's/([0-9])\s+/\1.tsv /;s/([0-9])$/\1.tsv /') > "$f.txt"
done < info.tsv
f=$(echo "$i" | cut -d' ' -f1)
檢索組的名稱。cat $(cut -d' ' -f2- | sed -E 's/([0-9])\s+|([0-9])$/\1.tsv /g')
連接該行中的文件列表。