Вопрос или проблема
Ищу помощь в преобразовании CSV в переменные. Я пытался использовать IFS, но, похоже, нужно определять количество полей. Мне нужно что-то, что может обрабатывать различное количество полей.
*Я изменяю свой первоначальный вопрос с текущим кодом, который я использую (взято из ответа, предоставленного hschou), и который включает обновленные имена переменных с использованием типа вместо строки, секции и т. д.
Я уверен, вы увидите по моему коду, что я довольно “зелёный” в скрипах, поэтому я ищу помощи, чтобы определить, нужно ли и как я должен добавить еще один цикл или принять другой подход к разбору данных typeC, потому что, хотя они следуют одному и тому же формату, для данных typeA и typeB есть только одна запись, а для данных typeC может быть от 1 до 15 записей. Цель — только 3 файла, по одному для каждого типа данных.
Формат данных:
Container: PL[1-100]
TypeA: [1-20].[1-100].[1-1000].[1-100]-[1-100]
TypeB: [1-20].[1-100].[1-1000].[1-100]-[1-100]
TypeC (1 до 15 записей): [1-20].[1-100].[1-1000].[1-100]-[1-100]
*В CSV нет заголовка, но если бы он был, он выглядел бы так (дата о контейнере, typeA, и typeB всегда на позициях 1, 2, 3, а данные typeC все следующие): Container,typeA,typeB,typeC,typeC,typeC,typeC,typeC, …
CSV:
PL3,12.1.4.5-77,13.6.4.5-20,17.3.577.9-29,17.3.779.12-33,17.3.802.12-60,17.3.917.12-45,17.3.956.12-63,17.3.993.12-42
PL4,12.1.4.5-78,13.6.4.5-21,17.3.577.9-30,17.3.779.12-34
PL5,12.1.4.5-79,13.6.4.5-22,17.3.577.9-31,17.3.779.12-35,17.3.802.12-62,17.3.917.12-47
PL6,12.1.4.5-80,13.6.4.5-23,17.3.577.9-32,17.3.779.12-36,17.3.802.12-63,17.3.917.12-48,17.3.956.12-66
PL7,12.1.4.5-81,13.6.4.5-24,17.3.577.9-33,17.3.779.12-37,17.3.802.12-64,17.3.917.12-49,17.3.956.12-67,17.3.993.12-46
PL8,12.1.4.5-82,13.6.4.5-25,17.3.577.9-34
Код:
#!/bin/bash
#Установить входной файл
_input="input.csv"
# Извлечение переменных из csv
# чтение файла в цикле while
while read; do
declare -a COL=( ${REPLY//,/ } )
echo -e "containerID=${COL[0]}\ntypeA=${COL[1]}\ntypeB=${COL[2]}" >/tmp/typelist.txt
idx=1
while [ $idx -lt 10 ]; do
echo "typeC$idx=${COL[$((idx+2))]}" >>/tmp/typelist.txt
let idx=idx+1
#удаление пустых переменных
sed '/\=$/d' /tmp/typelist.txt > /tmp/typelist2.txt && mv /tmp/typelist2.txt /tmp/typelist.txt
#установить переменные из временного файла
. /tmp/typelist.txt
done
sleep 1
#Разбор данных в этом цикле.#
echo -e "\n"
echo "Начинаем обработку для $container"
#echo $typeA
#echo $typeB
#echo $typeC
#echo -e "\n"
#Удаление - из подданных для дальнейшего разбора
typeAsub="$(echo "$typeA" | sed 's/\-.*$//')"
typeBsub="$(echo "$typeB" | sed 's/\-.*$//')"
typeCsub1="$(echo "$typeC1" | sed 's/\-.*$//')"
#удаление первых двух десятичных знаков для дальнейшего разбора
typeAprefix="$(echo "$typeA" | cut -d "." -f1-2)"
typeBprefix="$(echo "$typeB" | cut -d "." -f1-2)"
typeCprefix1="$(echo "$typeC1" | cut -d "." -f1-2)"
#echo $typeAsub
#echo $typeBsub
#echo $typeCsub1
#echo -e "\n"
#echo $typeAprefix
#echo $typeBprefix
#echo $typeCprefix1
#echo -e "\n"
echo "Получение набора данных typeA для $typeA"
#вызвать скрипт api для извлечения данных; вывод для теста
echo "API-gather -option -b "$typeAsub" -g all > "$container"typeA-dataset"
sleep 1
echo "Получение набора данных typeB для $typeB"
#вызвать скрипт api для извлечения данных; вывод для теста
echo "API-gather -option -b "$typeBsub" -g all > "$container"typeB-dataset"
sleep 1
echo "Получение набора данных typeC для $typeC1"
#вызвать скрипт api для извлечения данных; вывод для теста
echo "API-gather -option -b "$typeCsub" -g all > "$container"typeC-dataset"
sleep 1
echo "Получение дополнительных наборов данных typeC для $typeC2-15"
#вызвать скрипт api для извлечения данных; вывод для теста
echo "API-gather -option -b "$typeCsub2-15" -g all >> "$container"typeC-dataset"
sleep 1
echo -e "\n"
done < "$_input"
exit 0
Скорость не имеет значения, но если я там что-то действительно глупое сделал, не стесняйтесь направить меня в правильном направлении. 🙂
В этом скрипте строка просто читается в стандартную переменную $REPLY
. Затем запятая заменяется на пробел ${REPLY//,/ }
и помещается в массив declare -a COL=()
. Часть секции затем обрабатывается циклом, где индекс столбца вычисляется с $((idx+2))
:
#! /bin/bash
while read; do
declare -a COL=( ${REPLY//,/ } )
echo -e "container=${COL[0]}\nrow=${COL[1]}\nshelf=${COL[2]}"
idx=1
while [ $idx -lt 10 ]; do
echo "section$idx=${COL[$((idx+2))]}"
let idx=idx+1
done
done
Я бы использовал один ассоциативный массив на запись csv:
предполагая, что ваши данные находятся в файле с именем input.csv
#!/usr/bin/env bash
counter=1 # предоставляет индекс для каждой записи csv
while read
do
IFS=',' a=( $REPLY ) # числовой массив, содержащий текущую строку
eval "declare -A row$counter" # объявляем асс. массив, представляющий
# эту строку
eval "row$counter+=( ['row']=${a[0]} )"
a=( "${a[@]:1}" )
eval "row$counter+=( ['shelf']=${a[0]} )"
a=( "${a[@]:1}" )
eval "row$counter+=( ['section1']=${a[0]} )"
a=( "${a[@]:1}" )
eval "row$counter+=( ['section2']=${a[0]} )"
a=( "${a[@]:1}" )
eval "row$counter+=( ['section3']=${a[0]} )"
a=( "${a[@]:1}" )
eval "row$counter+=( ['section4']=${a[0]} )"
a=( "${a[@]:1}" )
eval "row$counter+=( ['section5']=${a[0]} )"
a=( "${a[@]:1}" )
eval "row$counter+=( ['section6']=${a[0]} )"
a=( "${a[@]:1}" )
declare -p row$counter
(( counter = counter + 1 ))
done < <( cat input.csv )
# доступ к произвольному элементу
printf "\n---------\n%s\n" ${row3["section4"]}
это дает мне вывод, похожий на:
declare -A row1='([section6]="6" [section5]="5" [section4]="4" [section3]="4" [section2]="2" [section1]="1" [shelf]="12" [row]="PL3" )'
declare -A row2='([section6]="" [section5]="" [section4]="" [section3]="2" [section2]="1" [section1]="4" [shelf]="13" [row]="PL4" )'
declare -A row3='([section6]="" [section5]="" [section4]="3" [section3]="2" [section2]="1" [section1]="5" [shelf]="14" [row]="PL5" )'
declare -A row4='([section6]="5" [section5]="4" [section4]="3" [section3]="2" [section2]="1" [section1]="6" [shelf]="15" [row]="PL6" )'
declare -A row5='([section6]="5" [section5]="4" [section4]="3" [section3]="2" [section2]="1" [section1]="7" [shelf]="16" [row]="PL7" )'
declare -A row6='([section6]="5" [section5]="4" [section4]="3" [section3]="2" [section2]="1" [section1]="8" [shelf]="15" [row]="PL8" )'
declare -A row7='([section6]="5" [section5]="4" [section4]="3" [section3]="2" [section2]="1" [section1]="7" [shelf]="16" [row]="PL9" )'
---------
3
Я бы начал с этого:
while IFS=, read -ra fields; do
for (( i = ${#fields[@]} - 1; i >= 0; i-- )); do
[[ -z "${fields[i]}" ]] && unset fields[i] || break
done
declare -p fields
done < file
declare -a fields="([0]="PL3" [1]="12" [2]="3" [3]="1" [4]="2" [5]="3" [6]="4" [7]="5" [8]="6")"
declare -a fields="([0]="PL4" [1]="13" [2]="4" [3]="1" [4]="2")"
declare -a fields="([0]="PL5" [1]="14" [2]="5" [3]="1" [4]="2" [5]="3")"
declare -a fields="([0]="PL6" [1]="15" [2]="6" [3]="1" [4]="2" [5]="3" [6]="4" [7]="5" [8]="6" [9]="7" [10]="8")"
declare -a fields="([0]="PL7" [1]="16" [2]="7" [3]="1" [4]="2" [5]="3" [6]="4" [7]="5" [8]="6" [9]="7" [10]="8" [11]="9")"
declare -a fields="([0]="PL8" [1]="15" [2]="8" [3]="1" [4]="2" [5]="3" [6]="4" [7]="5" [8]="6" [9]="7" [10]="8")"
declare -a fields="([0]="PL9" [1]="16" [2]="7" [3]="1" [4]="2" [5]="3" [6]="4" [7]="5" [8]="6" [9]="7" [10]="8" [11]="9")"
Убедитесь, что в вашем файле нет каких-либо лишних пробелов в конце строки.
Я сомневаюсь, что вам нужно иметь числовые переменные. Возможно, вам нужны двумерные массивы, которых в bash нет. Уверены ли вы, что bash — это подходящий инструмент для этого?
Мы можем относительно легко преобразовать ваши данные CSV без заголовков в структурированный JSON-файл, предполагая, что данные находятся в “простом” формате CSV (то есть в полях нет необходимости в специальном CSV-цитировании). Следующее создает набор независимых JSON-объектов, по одному для каждой строки входных данных из вашего файла CSV:
$ jq -R 'split(",") | {container:.[0], typeA:.[1], typeB:.[2], typeC:.[3:]}' file file.csv
{
"container": "PL3",
"typeA": "12.1.4.5-77",
"typeB": "13.6.4.5-20",
"typeC": [
"17.3.577.9-29",
"17.3.779.12-33",
"17.3.802.12-60",
"17.3.917.12-45",
"17.3.956.12-63",
"17.3.993.12-42"
]
}
{
"container": "PL4",
"typeA": "12.1.4.5-78",
"typeB": "13.6.4.5-21",
"typeC": [
"17.3.577.9-30",
"17.3.779.12-34"
]
}
[...] # вывод обрезан для краткости
Предполагая, что эти данные JSON хранятся в file.json
, мы можем затем запросить их различными способами:
$ jq -r --arg container PL7 --arg type typeA 'select(.container==$container)[$type]' file.json
12.1.4.5-81
$ jq -r --arg container PL8 --arg type typeB 'select(.container==$container)[$type]' file.json
13.6.4.5-25
$ jq -r --arg container PL6 --arg type typeC 'select(.container==$container)[$type][]' file.json
17.3.577.9-32
17.3.779.12-36
17.3.802.12-63
17.3.917.12-48
17.3.956.12-66
(Заметьте, что я добавил []
в конец выражения выше, чтобы расширить массив в отдельные элементы.)
$ jq -r --arg container PL3 --arg type typeC --arg sub 60 'select(.container==$container)[$type][] | select(endswith("-"+$sub))' file.json
17.3.802.12-60
Ответ или решение
Создание переменных из CSV-файла с переменным количеством полей может представлять определённую сложность, особенно если использовать оболочные скрипты без возможности динамического определения структуры данных. Проблема заключается в том, что для полноценного выделения данных необходимо гибко обрабатывать изменяющееся количество полей, что требует продвинутого подхода.
Теория
В контексте обработки CSV-файлов с переменным количеством полей важно понимать принципы работы с текстовыми данными в терминале Linux. При чтении строк из CSV-файла стандартно используется разделитель для разложения строк на отдельные компоненты (в этом случае — запятая). Однако, когда количество полей не фиксировано (например, тип C может содержать от 1 до 15 записей), работа с фиксированными индексами оказывается проблематичной. Вместо этого необходимо использовать подход, позволяющий работать с изменяющимся количеством данных.
Пример
Представим CSV, содержащий данные о разных типах. Для каждого контейнера у нас один тип A и тип B, и до 15 записей для типа C. Доступ к данным можно организовать, например, в виде массива или объединить данные в структуру, которую легче обработать. Посмотрим на пример использования bash
для обработки такой структуры:
#!/bin/bash
_input="input.csv"
while IFS=',' read -ra FIELDS; do
CONTAINER=${FIELDS[0]}
TYPE_A=${FIELDS[1]}
TYPE_B=${FIELDS[2]}
# Все, что дальше, рассматривается как типы C
TYPE_C=("${FIELDS[@]:3}")
echo "Container: $CONTAINER"
echo "Type A: $TYPE_A"
echo "Type B: $TYPE_B"
echo -e "Type C:\n-${TYPE_C[@]}"
echo "-----------"
done < "$_input"
В приведённом выше примере сначала читаем каждую строку и раскладываем её на массив FIELDS
. Первые три элемента соответствуют контейнеру и типам A и B, а всё, что следует за ними, извлекается в массив TYPE_C
для хранения множества записей.
Применение
Существует несколько подходов для применения этой теории:
-
Ассоциативные массивы Bash: Можно создать ассоциативный массив для каждого типа данных, который будет хранить записи из CSV. Это позволит легко обращаться к данным по ключевым словам.
-
Конверсия в JSON: С использованием утилиты
jq
csv-данные можно преобразовать в JSON, что предоставляет большую гибкость в обработке данных. -
Языки программирования: Perl, Python или другие более продвинутые языки имеют встроенные библиотеки для эффективной обработки CSV-файлов, что делает их предпочтительным решением для сложных задач с изменяющейся структурой данных.
Таким образом, при использовании продвинутых подходов к обработке данных из CSV-файлов, вы можете динамически управлять данными даже при изменяющейся структуре входных данных, что особенно полезно в задачах автоматизации и преобразования данных.