
У меня есть несколько файлов, каждый из которых содержит определенный шаблон в своих именах, например, ABC1234001
которые несут информацию об определенных группах моих данных (таблицы из нескольких столбцов). У меня также есть таблица info.tsv
вроде этой:
group1 ABC1234001 ABC1234010
group2 ABC1234011 ABC1234018
group3 ABC1234019 ABC1234028
... ... ...
Это содержит:
- столбец «группа», который указывает группу,
- столбец «первый файл», который определяет шаблон для первого файла (в алфавитном порядке), содержащего информацию для соответствующей группы,
- Столбец «последний файл», в котором указан шаблон для последнего файла (в алфавитном порядке), содержащего информацию для соответствующей группы.
Итак, мне нужно объединить файлы для каждой группы в один файл — вот так
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"
поэтому скрипт выполнит одну из двух встроенных команд оболочки true
или false
. В оболочке нет специальных ключевых слов для true или false, как в некоторых других языках (например, Python).
Тестирование:
$ 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')
объединяет список файлов в строку.