создайте переменные из CSV с различным количеством полей

Вопрос или проблема

Ищу помощь в преобразовании 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 для хранения множества записей.

Применение

Существует несколько подходов для применения этой теории:

  1. Ассоциативные массивы Bash: Можно создать ассоциативный массив для каждого типа данных, который будет хранить записи из CSV. Это позволит легко обращаться к данным по ключевым словам.

  2. Конверсия в JSON: С использованием утилиты jq csv-данные можно преобразовать в JSON, что предоставляет большую гибкость в обработке данных.

  3. Языки программирования: Perl, Python или другие более продвинутые языки имеют встроенные библиотеки для эффективной обработки CSV-файлов, что делает их предпочтительным решением для сложных задач с изменяющейся структурой данных.

Таким образом, при использовании продвинутых подходов к обработке данных из CSV-файлов, вы можете динамически управлять данными даже при изменяющейся структуре входных данных, что особенно полезно в задачах автоматизации и преобразования данных.

Оцените материал
Добавить комментарий

Капча загружается...