Как получить все поля во внешнем соединении с Unix соединением?

Предположим, у меня есть два файла, en.csv а также sp.csvкаждая из которых содержит ровно две запятые записи:

en.csv:

1,dog,red,car
3,cat,white,boat

sp.csv:

2,conejo,gris,tren
3,gato,blanco,bote

Если я выполню

join -t, -a 1 -a 2 -e MISSING en.csv sp.csv

вывод, который я получаю:

1,dog,red,car
2,conejo,gris,tren
3,cat,white,boat,gato,blanco,bote

Обратите внимание, что все пропущенные поля были свернуты. Чтобы получить "правильное" полное внешнее соединение, мне нужно указать формат; таким образом

join -t, -a 1 -a 2 -e MISSING -o 0,1.2,1.3,1.4,2.2,2.3,2.4 en.csv sp.csv

доходность

1,dog,red,car,MISSING,MISSING,MISSING
2,MISSING,MISSING,MISSING,conejo,gris,tren
3,cat,white,boat,gato,blanco,bote

Один из недостатков этого способа создания полного внешнего объединения заключается в том, что необходимо явно указать формат финальной таблицы, что может быть непросто сделать в программных приложениях (где идентичность соединяемых таблиц известна только во время выполнения).

Последние версии GNU join устранить этот недостаток, поддерживая специальный формат auto, Поэтому с такой версией join последняя команда выше может быть заменена гораздо более общей

join -t, -a 1 -a 2 -e MISSING -o auto en.csv sp.csv

Как я могу добиться этого же эффекта с версиями join которые не поддерживают -o auto вариант?


Фон и детали

У меня есть сценарий оболочки Unix (zsh), который предназначен для обработки нескольких плоских файлов CSV, и делает это путем широкого использования GNU joinОпция 's '-o auto'. Мне нужно изменить этот скрипт, чтобы он мог работать в средах, где доступны join команда не поддерживает -o auto вариант (как в случае с BSD join а также для более старых версий GNU join).

Типичное использование этой опции в скрипте примерно так:

_reccut () {
    cols="1,$1"
    shift
    in=$1
    shift
    if (( $# > 0 )); then
        join -t, -a 1 -a 2 -e 'MISSING' -o auto \
          <( cut -d, -f $cols $in | sort -t, -k1 ) \
          <( _reccut "$@" )
    else
        cut -d, -f $cols $in | sort -t, -k1
    fi
}

Я показываю этот пример, чтобы проиллюстрировать, что было бы трудно заменить -o auto с явным форматом, поскольку поля для включения в этот формат не известны до времени выполнения.

Функция _reccut Выше в основном извлекает столбцы из файлов и объединяет результирующие таблицы вдоль их первого столбца. Чтобы увидеть как _reccut в действии, представьте, что в дополнение к файлам, упомянутым выше, у нас также был файл

de.csv

2,Kaninchen,Grau,Zug
1,Hund,Rot,Auto

Затем, например, для отображения рядом столбца 3 en.csvколонки 2 и 4 sp.csvи столбец 3 из de.csv один будет работать:

% _reccut 3 en.csv 2,4 sp.csv 3 de.csv | cut -d, 2-
red,MISSING,MISSING,Rot
MISSING,conejo,tren,Grau
white,gato,bote,MISSING

1 ответ

Вот решение, которое может или не может работать для ваших данных. Это решает проблему путем выравнивания записей в CSV-файле по номеру строки, то есть записи 2 заканчивается на линии 2, запись 3123 на номер строки 3123 и так далее. Недостающие записи / строки дополняются MISSING поля, поэтому входные файлы будут выглядеть так:

en.csv:

1,dog,red,car
2,MISSING,MISSING,MISSING
3,cat,white,boat

de.csv:

1,Hund,Rot,Auto
2,Kaninchen,Grau,Zug
3,MISSING,MISSING,MISSING

sp.csv:

1,MISSING,MISSING,MISSING
2,conejo,gris,tren
3,gato,blanco,bote

Оттуда легко вырезать интересующие столбцы и просто печатать их рядом, используя paste,

Чтобы добиться этого, мы сначала сортируем входные файлы, а затем применяем некоторые глупые awk магия:

  • Если запись появляется в ожидаемом номере строки, распечатайте ее
  • В противном случае выведите столько строк, сколько ожидаемого числа (это зависит от количества полей первой строки в файле, так же как join -o auto делает) MISSING поля, пока выравнивание снова не будет правильным
  • Не все входные файлы идут с одинаковым количеством записей, поэтому поиск максимума производится до всего этого. Тогда больше строк с MISSING поля печатаются до тех пор, пока не будет достигнут максимум.

Код

reccut.sh:

#!/bin/bash

get_max_recnum()
{
    awk -F, '{ if ($1 > max) { max = $1 } } END { print max }' "$@"
}

align_by_recnum()
{
    sort -t, -k1 "$1" \
        | awk -F, -v MAXREC="$2" '
            NR==1 { for(x = 1; x < NF; x++) missing = missing ",MISSING" }
            {
                i = NR
                if (NR < $1)
                {
                    while (i < $1)
                    {
                        print i++ missing
                    }
                    NR+=i
                }
            }1
            END { for(i++; i <= MAXREC; i++) { print i missing } }
            '
}

_reccut()
{
    local infiles=()
    local args=( $@ )
    for arg; do
        infiles+=( "$2" )
        shift 2
    done
    MAXREC="$(get_max_recnum "${infiles[@]}")" __reccut "${args[@]}"
}

__reccut()
{
    local cols="$1"
    local infile="$2"
    shift 2

    if (( $# > 0 )); then
        paste -d, \
            <(align_by_recnum "${infile}" "${MAXREC}" | cut -d, -f ${cols}) \
            <(__reccut "$@")
    else
        align_by_recnum "${infile}" "${MAXREC}" | cut -d, -f ${cols}
    fi
}

_reccut "$@"

Бежать

$ ./reccut.sh 3 en.csv 2,4 sp.csv 3 de.csv
red,MISSING,MISSING,Rot
MISSING,conejo,tren,Grau
white,gato,bote,MISSING
Другие вопросы по тегам