Объединить файлы по табличным значениям

Объединить файлы по табличным значениям

У меня есть несколько файлов, каждый из которых содержит определенный шаблон в своих именах, например, 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')объединяет список файлов в строку.

Связанный контент