Bash Расширение цитируемого массива

Когда я пишу программу bash, я обычно создаю вызовы вроде:

declare -a mycmd=( command.ext "arg1 with space" arg2 thing etc )

"${mycmd[@]}" || echo "Failed: foo"

Где die foo - это функция bash, которая печатает Error foo и выходит.

Но если я хочу четко объяснить причину ошибки, я хочу напечатать неудачную команду:

"${mycmd[@]}" || echo "Failed: foo: ${mycmd[*]}"

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

Есть ли у кого-нибудь предложение для компактного способа устранения этой проблемы?


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

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

#!/bin/bash
mkdir qatest; cd qatest
declare -a myargs=(1 2 "3 4")
touch "${myargs[@]}"
ls
echo "${myargs[@]}"

фактический результат:

1  2  3 4
1 2 3 4

желаемый результат

1  2  3 4
1 2 "3 4"

ИЛИ

1  2  3 4
"1" "2" "3 4"

В нескольких дополнительных символах кода bash.


Вопрос закрыт: @camh великолепно ответил:

обновлен script:

#!/bin/bash
mkdir qatest; cd qatest
declare -a myargs=(1 2 "3 4")
touch "${myargs[@]}"
ls
echo "${myargs[@]}"
echo $(printf "'%s' " "${myargs[@]}")

выход:

1  2  3 4
1 2 3 4
'1' '2' '3 4'

Ответ 1

Ваша проблема связана с echo. Он получает правильное количество параметров, с некоторыми параметрами, содержащими пробелы, но этот вывод теряет различие пробелов между параметрами и пробелами внутри параметров.

Вместо этого вы можете использовать printf(1) для вывода параметров и всегда включать кавычки, используя функцию printf, которая последовательно применяет строку формата к параметрам, когда в строке формата есть больше параметров, чем спецификаторы формата:

echo "Failed: foo:" $(printf "'%s' " "${mycmd[@]}")

Это добавит одинарные кавычки вокруг каждого аргумента, даже если это не требуется:

Failed: foo: 'command.ext' 'arg1 with space' 'arg2' 'thing' 'etc'

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

Изменить: почти 5 лет спустя, и после того, как я ответил на этот вопрос, был выпущен bash 4.4. Это имеет расширение "${[email protected]}", которое цитирует переменную так, что ее можно проанализировать с помощью bash.

Это упрощает этот ответ:

echo "Failed: foo: " "${mycmd[@]@Q}"

Это правильно обрабатывает одинарные кавычки в аргументе, который не был у моей более ранней версии.

Ответ 2

bash Команда printf имеет формат% q, который добавляет соответствующие кавычки в строки при печати:

echo "Failed: foo:$(printf " %q" "${mycmd[@]}")"

Помните, что его идея "лучшего" способа процитировать что-то не всегда такая же, как моя, например. он предпочитает избегать забавных символов вместо того, чтобы обернуть строку в кавычки. Например:

crlf=$'\r\n'
declare -a mycmd=( command.ext "arg1 with space 'n apostrophe" "arg2 with control${crlf} characters" )
echo "Failed: foo:$(printf " %q" "${mycmd[@]}")"

Печать

Failed: foo: command.ext arg1\ with\ space\ \'n\ apostrophe $'arg2 with control\r\n characters'

Ответ 3

Как насчет declare -p quotedarray?

- изменить -

Фактически, declare -p quotedarray удовлетворит вашу цель. Если вы настаиваете на выходном формате результата, то у меня есть небольшой трюк, который выполнит эту работу, но только для индексированного массива, который не является ассоциативным.

declare -a quotedarray=(1 2 "3 4")
temparray=( "${quotedarray[@]/#/\"}" ) #the outside double quotes are critical
echo ${temparray[@]/%/\"}

Ответ 4

Громоздкий метод (который цитирует только аргументы, содержащие пробелы):

declare -a myargs=(1 2 "3 4")
for arg in "${myargs[@]}"; do
    # testing if the argument contains space(s)
    if [[ $arg =~ \  ]]; then
      # enclose in double quotes if it does 
      arg=\"$arg\"
    fi 
    echo -n "$arg "
done

Вывод:

1 2 "3 4"

Кстати, что касается quoting is lost on this pass, обратите внимание, что кавычки никогда не сохраняются. " " - это специальный символ, который сообщает оболочке обрабатывать все, что внутри, как одно поле/аргумент (т.е. не разделять его). С другой стороны, буквенные кавычки (такие как \") сохраняются.

Ответ 5

Мне нравится вводить код в функцию, поэтому его легче использовать и документировать:

function myjoin
{
   local list=("${@}")
   echo $(printf "'%s', " "${list[@]}")
}

declare -a colorlist=()
colorlist+=('blue')
colorlist+=('red')
colorlist+=('orange')

echo "[$(myjoin ${colorlist[@]})]"

Обратите внимание, как я добавил запятую в решение, потому что я генерирую код с помощью bash script.

[EDIT: исправить проблему, которую EM0 указала, что она возвращает ['blue',]]