У меня есть следующий файл 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 работает нормально
Вероятно, это как-то связано со специальными символами в последнем массиве, но я запутался.
Есть идеи?
@tripleee Потому что в bash нет двумерных массивов?
При первой ошибке вам следует проверить, пуст ли "${command[i]}"
, прежде чем пытаться его использовать.
О второй ошибке см. stackoverflow.com/questions/12136948/…, чтобы понять, почему это происходит. Однако обычное решение — поместить команду и аргументы в массив, но для этого требуются многомерные массивы.
bash
просто кажется, что для этого неподходящий язык, вам нужен язык с лучшими структурами данных. Питон был бы хорош.
Согласен, это сложно, но не бесполезно. У нас есть много данных в табличном формате с именами образцов, файлами, тегами и т. д. Я упрощаю процесс ввода этой информации непосредственно в командную строку, чтобы люди, менее склонные к bash, могли подготовить эту информацию и легко ее использовать. Это сэкономит мне массу раз в будущем
@Barmar некоторые строки пусты, при повторении «${command[2]}» я не получаю абсолютно ничего, чего и ожидал. Вот почему я не уверен, почему получаю первую ошибку!
Первая ошибка связана с тем, что execute_program_name
ожидает непустое имя программы, но "${command[2]}"
пусто. Вам нужно что-то вроде if [ -n "${command[2]}" ]
, прежде чем пытаться его использовать.
если вы переместите turtle,rabbit
так, чтобы это была 1-я строка в файле, вы обнаружите, что ваши записи массива command[]
сдвинуты, потому что в 1-й строке нет заполнителя для отсутствующего столбца (т. е. --command yes this is bad --other-command
загружается в позиции 0 и 1, а не в позиции 1). на позиции 1 и 2); конечный результат: вам придется переосмыслить заполнение вашего массива command[]
, чтобы адресовать строки с отсутствующим третьим столбцом
@markp-fuso, понятно! Вот почему я получаю 'недопустимый вариант, если содержимое индекса для этой позиции в массиве равно нулю. Похоже, мое решение, заключающееся в повторении команды в новый массив, работает. Если у вас есть какие-либо соображения по поводу этого решения, которое я опубликовал, пожалуйста, скажите. Спасибо!
рассмотрите возможность обновления вопроса, указав, как бы выглядел вызов execute_program_name
, если бы вы вводили его вручную в командной строке, чтобы мы могли видеть, где вы (не) будете использовать одинарные кавычки.
нужны ли вам массивы для каких-либо других целей, кроме этого единственного цикла for
?
Добавьте ожидаемый результат (команды)
Из описания вашей проблемы неясно, какой оператор в вашем цикле выдает сообщение об ошибке.
Правильный ответ зависит от того, что --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
или что-то еще? Пожалуйста, отредактируйте свой вопрос, чтобы показать нам это.
Если бы ваш CSV-файл содержал --command --other-command
, что бы это значило? Следует ли относиться к этому так, как execute_program_name --command '' --other-command
, или как execute_program_name --command '--other-command'
, или как-то еще? Пожалуйста, отредактируйте свой вопрос, чтобы показать и это.
Я согласен. Я обновил вопрос, чтобы прояснить, что имя_программы_execute_program_name --command ''command_argument" --other-command - это то, что должно быть в третьем столбце.
Кажется, я в итоге нашел решение... Это некрасиво, потому что использует 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]}")
, если вы это имеете в виду, хотя я думаю, вам следует просто добавить одну строку.
Также бесполезное использование eval. Это работает: for i in "${configs[@]}"; do $i; done
Я буду тестировать и обновлять здесь!
Предположения:
--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
Я думаю, что это наиболее развернутый ответ, и другие могут найти это полезным в зависимости от каждого варианта использования.
Я думаю, что наиболее элегантно это решается с помощью 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
Предположим, что:
=~
).Вы можете попробовать:
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
, чтобы извлечь остальные параметры, начиная с последнего (потому что сопоставление регулярных выражений является жадным).
Это кажется сумасшедшим усложнением. Зачем вообще сохранять команды в файле CSV? И в том неправдоподобном случае, когда это имело бы смысл, зачем вам считывать части в разные массивы?