Команда печати путем чтения файла CSV с помощью AWK в массив bash

У меня есть следующий файл CSV (mycsv.csv):

love,hate,--command yes this is bad --other-command
fox,duck,--command yes this is bad --other-command
turtle,rabbit

Это означает, что команда будет --command "command_argument" --other-command

Затем я создаю массив:

my_csv = "/home/sometimesihatebash/mycsv.csv"
first_column=( $(awk -F "\"*,\"*" '{print $1}' $my_csv) )
second_column=( $(awk -F "\"*,\"*" '{print $2}' $my_csv) )
IFS=$'\n' command=( $(awk -F "\"*,\"*" '{print $3}' $my_csv) )

Затем распечатайте все столбцы в команду

for i in "${!first_column[@]}"; do
    echo "${first_column[i]}"
    echo "${second_column[i]}"
    echo "${command[i]}"
    
    execute_program_name --first "${first_column[i]}" --second "${second_column[i]}" "${command[i]}"

done

Но затем для третьей строки (которая имеет пустой третий столбец) я получаю:

error: 1 unrecognized argument:
'

И для первых двух строк я получаю:

no such option: --command yes this is bad --other-command

При повторении каждого элемента массива я не вижу никаких проблем. Вставка точного вывода echo также нормально запускает программу.

Запуск цикла for только с массивами first_column и Second_column работает нормально

Вероятно, это как-то связано со специальными символами в последнем массиве, но я запутался.

Есть идеи?

Это кажется сумасшедшим усложнением. Зачем вообще сохранять команды в файле CSV? И в том неправдоподобном случае, когда это имело бы смысл, зачем вам считывать части в разные массивы?

tripleee 05.06.2024 21:18

@tripleee Потому что в bash нет двумерных массивов?

Barmar 05.06.2024 21:27

При первой ошибке вам следует проверить, пуст ли "${command[i]}", прежде чем пытаться его использовать.

Barmar 05.06.2024 21:28

О второй ошибке см. stackoverflow.com/questions/12136948/…, чтобы понять, почему это происходит. Однако обычное решение — поместить команду и аргументы в массив, но для этого требуются многомерные массивы.

Barmar 05.06.2024 21:28
bash просто кажется, что для этого неподходящий язык, вам нужен язык с лучшими структурами данных. Питон был бы хорош.
Barmar 05.06.2024 21:29

Согласен, это сложно, но не бесполезно. У нас есть много данных в табличном формате с именами образцов, файлами, тегами и т. д. Я упрощаю процесс ввода этой информации непосредственно в командную строку, чтобы люди, менее склонные к bash, могли подготовить эту информацию и легко ее использовать. Это сэкономит мне массу раз в будущем

Gabriel G. 05.06.2024 21:30

@Barmar некоторые строки пусты, при повторении «${command[2]}» я не получаю абсолютно ничего, чего и ожидал. Вот почему я не уверен, почему получаю первую ошибку!

Gabriel G. 05.06.2024 21:35

Первая ошибка связана с тем, что execute_program_name ожидает непустое имя программы, но "${command[2]}" пусто. Вам нужно что-то вроде if [ -n "${command[2]}" ], прежде чем пытаться его использовать.

Barmar 05.06.2024 21:43

если вы переместите turtle,rabbit так, чтобы это была 1-я строка в файле, вы обнаружите, что ваши записи массива command[] сдвинуты, потому что в 1-й строке нет заполнителя для отсутствующего столбца (т. е. --command yes this is bad --other-command загружается в позиции 0 и 1, а не в позиции 1). на позиции 1 и 2); конечный результат: вам придется переосмыслить заполнение вашего массива command[], чтобы адресовать строки с отсутствующим третьим столбцом

markp-fuso 05.06.2024 21:45

@markp-fuso, понятно! Вот почему я получаю 'недопустимый вариант, если содержимое индекса для этой позиции в массиве равно нулю. Похоже, мое решение, заключающееся в повторении команды в новый массив, работает. Если у вас есть какие-либо соображения по поводу этого решения, которое я опубликовал, пожалуйста, скажите. Спасибо!

Gabriel G. 05.06.2024 22:00

рассмотрите возможность обновления вопроса, указав, как бы выглядел вызов execute_program_name, если бы вы вводили его вручную в командной строке, чтобы мы могли видеть, где вы (не) будете использовать одинарные кавычки.

markp-fuso 05.06.2024 22:02

нужны ли вам массивы для каких-либо других целей, кроме этого единственного цикла for?

markp-fuso 05.06.2024 23:10

Добавьте ожидаемый результат (команды)

Diego Torres Milano 05.06.2024 23:40

Из описания вашей проблемы неясно, какой оператор в вашем цикле выдает сообщение об ошибке.

user1934428 06.06.2024 08:54

Правильный ответ зависит от того, что --command yes this is bad --other-command должно означать execute_program_name — если бы вы написали командную строку, это было бы execute_program_name --command 'yes this is bad' --other-command, execute_program_name --command 'yes' 'this' 'is' bad' --other-command или что-то еще? Пожалуйста, отредактируйте свой вопрос, чтобы показать нам это.

Ed Morton 06.06.2024 19:53

Если бы ваш CSV-файл содержал --command --other-command, что бы это значило? Следует ли относиться к этому так, как execute_program_name --command '' --other-command, или как execute_program_name --command '--other-command', или как-то еще? Пожалуйста, отредактируйте свой вопрос, чтобы показать и это.

Ed Morton 06.06.2024 19:58

Я согласен. Я обновил вопрос, чтобы прояснить, что имя_программы_execute_program_name --command ''command_argument" --other-command - это то, что должно быть в третьем столбце.

Gabriel G. 06.06.2024 20:28
Структурированный массив Numpy
Структурированный массив Numpy
Однако в реальных проектах я чаще всего имею дело со списками, состоящими из нескольких типов данных. Как мы можем использовать массивы numpy, чтобы...
T - 1Bits: Генерация последовательного массива
T - 1Bits: Генерация последовательного массива
По мере того, как мы пишем все больше кода, мы привыкаем к определенным способам действий. То тут, то там мы находим код, который заставляет нас...
Что такое деструктуризация массива в JavaScript?
Что такое деструктуризация массива в JavaScript?
Деструктуризация позволяет распаковывать значения из массивов и добавлять их в отдельные переменные.
2
17
151
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Кажется, я в итоге нашел решение... Это некрасиво, потому что использует eval, что само по себе может стать источником проблем.

Решение заключается в том, чтобы сначала открыть новый массив с правильными командами в каждой строке:

configs=()
for i in "${!first_column[@]}"; do
    echo "${first_column[i]}"
    echo "${second_column[i]}"
    echo "${command[i]}"
    
    configs+=( $(echo "execute_program_name --first "${first_column[i]}" --second "${second_column[i]}" "${command[i]}"") )

done

Затем запустите:

for i in "${configs[@]}"; do
    eval $i
done

Я не уверен, что одна строка вашего ввода работает неправильно, а следующая будет. Но на моем примере это сработало

Это бесполезное использование эха; просто назначьте configs+=("execute_program_name" "--first" "${first_column[i]}" "--second" "${second_column[i]}" "${command[i]}"), если вы это имеете в виду, хотя я думаю, вам следует просто добавить одну строку.

tripleee 06.06.2024 09:53

Также бесполезное использование eval. Это работает: for i in "${configs[@]}"; do $i; done

dawg 06.06.2024 15:29

Я буду тестировать и обновлять здесь!

Gabriel G. 06.06.2024 20:31
Ответ принят как подходящий

Предположения:

  • входную строку --command yes this is bad --other-command необходимо передать execute_program_name в следующем формате: --command 'yes this is bad' --other-command
  • третий столбец состоит из вариантов длинной формы (т. е. варианты всегда начинаются с --)
  • если третий столбец заполнен, первая строка всегда будет иметь длинную форму (т. е. строка всегда будет иметь формат --option)
  • содержимое mycsv.csv нужно проанализировать только для этого одного for цикла; конечный результат: нам понадобится только один массив (args[]); (см. вторую половину ответа о подходе, который допускает одиночный анализ и многократное повторное использование)

Мы внесем некоторые изменения во входные данные OP, чтобы продемонстрировать варианты обработки с аргументами и без них:

$ cat mycsv.csv
love,hate,--command yes this is bad --other-command
fox,duck,--command yes this is bad --other-command single_string
turtle,rabbit 
sweet,sour,--new-command --newer-command

Простой скрипт для вывода списка входных параметров:

$ cat execute_program_name
#!/bin/bash

echo "### $0 $@"
cnt=0

for arg in "$@"
do
    ((cnt++))
    echo "arg #${cnt} : ${arg}"
done

Один bash подход

$ cat parse_n_run
#!/bin/bash

while IFS=, read -r c1 c2 c3
do
    printf "\n########### %s : %s : %s\n\n" "${c1}" "${c2}" "${c3}"

    args=( "--first" "${c1}" "--second" "${c2}" )       # store first 2 sets of options in array

    while read -r option string
    do
        [[ -z "${option}" ]] && continue                # skip first line (blank)
        args+=( "${option}" )                           # add "--option" to array
        [[ -n "${string}" ]] && args+=( "${string}" )   # if there's an argument then add to the array

    done <<< "${c3//--/$'\n'&}"                         # split c3 into lines by placing a "\n" before each "--"

    typeset -p args                                     # display contents of array
    echo ""
    execute_program_name "${args[@]}"
done < mycsv.csv

Берем на тест-драйв:

$ parse_n_run

########### love : hate : --command yes this is bad --other-command

declare -a args=([0] = "--first" [1] = "love" [2] = "--second" [3] = "hate" [4] = "--command" [5] = "yes this is bad" [6] = "--other-command")

### ./execute_program_name --first love --second hate --command yes this is bad --other-command
arg #1 : --first
arg #2 : love
arg #3 : --second
arg #4 : hate
arg #5 : --command
arg #6 : yes this is bad
arg #7 : --other-command

########### fox : duck : --command yes this is bad --other-command single_string

declare -a args=([0] = "--first" [1] = "fox" [2] = "--second" [3] = "duck" [4] = "--command" [5] = "yes this is bad" [6] = "--other-command" [7] = "single_string")

### ./execute_program_name --first fox --second duck --command yes this is bad --other-command single_string
arg #1 : --first
arg #2 : fox
arg #3 : --second
arg #4 : duck
arg #5 : --command
arg #6 : yes this is bad
arg #7 : --other-command
arg #8 : single_string

########### turtle : rabbit  :

declare -a args=([0] = "--first" [1] = "turtle" [2] = "--second" [3] = "rabbit ")

### ./execute_program_name --first turtle --second rabbit
arg #1 : --first
arg #2 : turtle
arg #3 : --second
arg #4 : rabbit

########### sweet : sour : --new-command --newer-command

declare -a args=([0] = "--first" [1] = "sweet" [2] = "--second" [3] = "sour" [4] = "--new-command" [5] = "--newer-command")

### ./execute_program_name --first sweet --second sour --new-command --newer-command
arg #1 : --first
arg #2 : sweet
arg #3 : --second
arg #4 : sour
arg #5 : --new-command
arg #6 : --newer-command

Если OP необходимо проанализировать mycsv.csv и (повторно) использовать несколько раз, и OP имеет доступ к bash 4.3+, я бы, вероятно, предпочел использовать nameref для управления массивами args_1[], args_2[], ..., args_n[].

Вносим несколько изменений в текущий скрипт:

$ cat parse_n_run
#!/bin/bash

cnt=0

##########
# parse and store

echo "Parsing ..."

while IFS=, read -r c1 c2 c3
do
    printf "\n########### %s : %s : %s\n\n" "${c1}" "${c2}" "${c3}"

    ((cnt++))
    declare -n _args = "args_${cnt}"                          # name ref

    _args=( "--first" "${c1}" "--second" "${c2}" )

    while read -r option string
    do
        [[ -z "${option}" ]] && continue
        _args+=( "${option}" )
        [[ -n "${string}" ]] && _args+=( "${string}" )
    done <<< "${c3//--/$'\n'&}"

    typeset -p "${!_args}"
done < mycsv.csv

### at this point we have 4 arrays: args_1[], args_2[], args_3[] and args_4[]

##########
# make use of arrays

printf "\n###########\n"
echo "Using ..."

for ((i=1; i<=cnt; i++))
do
    declare -n _args = "args_$i"

    printf "\n### %s\n\n" "${!_args}"

    execute_program_name "${_args[@]}"
done

Берем на тест-драйв:

ПРИМЕЧАНИЕ. Обратите внимание, что 4 строки declare -a ссылаются на наши 4 массива args_1[], args_2[], args_3[] и args_4[].

$ parse_n_run
Parsing ...

########### love : hate : --command yes this is bad --other-command

declare -a args_1=([0] = "--first" [1] = "love" [2] = "--second" [3] = "hate" [4] = "--command" [5] = "yes this is bad" [6] = "--other-command")

########### fox : duck : --command yes this is bad --other-command single_string

declare -a args_2=([0] = "--first" [1] = "fox" [2] = "--second" [3] = "duck" [4] = "--command" [5] = "yes this is bad" [6] = "--other-command" [7] = "single_string")

########### turtle : rabbit  :

declare -a args_3=([0] = "--first" [1] = "turtle" [2] = "--second" [3] = "rabbit ")

########### sweet : sour : --new-command --newer-command

declare -a args_4=([0] = "--first" [1] = "sweet" [2] = "--second" [3] = "sour" [4] = "--new-command" [5] = "--newer-command")

###########
Using ...

### args_1

### execute_program_name --first love --second hate --command yes this is bad --other-command
arg #1 : --first
arg #2 : love
arg #3 : --second
arg #4 : hate
arg #5 : --command
arg #6 : yes this is bad
arg #7 : --other-command

### args_2

### execute_program_name --first fox --second duck --command yes this is bad --other-command single_string
arg #1 : --first
arg #2 : fox
arg #3 : --second
arg #4 : duck
arg #5 : --command
arg #6 : yes this is bad
arg #7 : --other-command
arg #8 : single_string

### args_3

### execute_program_name --first turtle --second rabbit
arg #1 : --first
arg #2 : turtle
arg #3 : --second
arg #4 : rabbit

### args_4

### execute_program_name --first sweet --second sour --new-command --newer-command
arg #1 : --first
arg #2 : sweet
arg #3 : --second
arg #4 : sour
arg #5 : --new-command
arg #6 : --newer-command

Я думаю, что это наиболее развернутый ответ, и другие могут найти это полезным в зависимости от каждого варианта использования.

Gabriel G. 06.06.2024 20:40

Я думаю, что наиболее элегантно это решается с помощью awk и Bash с заменой процессов и циклом while.

Данный:

$ cat file.csv
love,hate,--command yes this is bad --other-command
fox,duck,--command yes this is bad --other-command
turtle,rabbit

Вы можете сделать это:

configs=()
while read fst sec cmd; do
    configs+=("execute_program_name --first $fst --second $sec $cmd")
done < <(awk -F, '{print $1, $2, $3}' file.csv)

Результат:

$ printf "%s\n" "${configs[@]}"
execute_program_name --first love --second hate --command yes this is bad --other-command
execute_program_name --first fox --second duck --command yes this is bad --other-command
execute_program_name --first turtle --second rabbit 

Вы можете видеть, что у вас есть массив из трех элементов:

$ declare -p configs
declare -a configs=([0] = "execute_program_name --first love --second hate --command yes this is bad --other-command" [1] = "execute_program_name --first fox --second duck --command yes this is bad --other-command" [2] = "execute_program_name --first turtle --second rabbit ")

Это работает только с полями без пробелов, но именно так написан ваш пример.

Если вы хотите, чтобы это было выполнено, вам вообще не нужен eval. Просто выполните каждую построенную строку. В этом случае я буду использовать echo в качестве заполнителя для execute_program_name, чтобы остальная часть построенной строки отображалась эхом:

while read fst sec cmd; do
    echo "--first ${fst} --second ${sec} ${cmd}"
done < <(awk -F, '{print $1, $2, $3}' file.csv)

Распечатки:

--first love --second hate --command yes this is bad --other-command
--first fox --second duck --command yes this is bad --other-command
--first turtle --second rabbit 

Тот же подход можно использовать с массивом построенных строк — пройдитесь по массиву, а затем просто передайте строку Bash. По умолчанию Bash, если ему передана строка, пытается выполнить эту строку.

Итак, это работает:

for i in "${configs[@]}"; do
    $i                       # look ma! no eval!!!
done

Предположим, что:

  1. ваш формат CSV прост (нет полей в кавычках с запятыми, нет новых строк в полях...),
  2. ваш bash достаточно новый, чтобы поддерживать сопоставление массивов и регулярных выражений (=~).

Вы можете попробовать:

while IFS=, read -r x y z; do
  read -r x <<< "$x"; read -r y <<< "$y"
  declare -a a=()
  while [[ $z =~ (.*)(--[^[:space:]]*)(.*) ]]; do
    read -r z <<< "${BASH_REMATCH[1]}"; read -r v <<< "${BASH_REMATCH[3]}"
    if [[ -n $v ]]; then a=("$v" "${a[@]}"); fi
    a=("${BASH_REMATCH[2]}" "${a[@]}")
  done
  if [[ -n $z ]]; then a=("$z" "${a[@]}"); fi
  execute_program_name --first "$x" --second "$y" "${a[@]}"
done < mycsv.csv

Примечание. read -r variable <<< "value" — это всего лишь способ обрезать начальные и конечные пробелы.

Идея состоит в том, чтобы создать массив параметров (a) и передать "${a[@]}" вашей команде так, чтобы все параметры и их значения были правильно разделены в отдельные слова.

IFS=, read -r x y z сохраняет значения первого и второго параметра в x и y. Остальная часть строки хранится в z. Затем мы обрабатываем z в цикле while, чтобы извлечь остальные параметры, начиная с последнего (потому что сопоставление регулярных выражений является жадным).

Другие вопросы по теме