Как получить все поля во внешнем соединении с 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