Сохранить кавычки в аргументах bash
Я делаю сценарий bash, который будет печатать и передавать сложные аргументы другой внешней программе.
./script -m root@hostname,root@hostname -o -q -- 'uptime ; uname -a'
Как мне распечатать необработанные аргументы как таковые:
-m root@hostname,root@hostname -o -q -- 'uptime ; uname -a'
С помощью $@
а также $*
удаляет одинарные кавычки uptime ; uname -a
что может привести к нежелательным результатам. Мой скрипт не должен анализировать каждый аргумент. Мне просто нужно напечатать / записать строку аргумента и передать их другой программе в точности так, как они заданы.
Я знаю, что могу избежать кавычек с чем-то вроде "'uptime ; uname -a'"
но я не могу гарантировать, что пользователь сделает это.
10 ответов
Кавычки удаляются до того, как аргументы передаются в ваш скрипт, поэтому их слишком поздно сохранять. Что вы можете сделать, так это сохранить их эффект при передаче аргументов внутренней команде и восстановить эквивалентную кавычку / экранированную версию аргументов для печати.
Для передачи аргументов внутренней команде "$@"
- в двойных кавычках $@ сохраняет исходные разрывы слов, что означает, что внутренняя команда получает точно такой же список аргументов, что и ваш скрипт.
Для печати вы можете использовать формат% q в команде bash printf для восстановления цитирования. Обратите внимание, что это не всегда восстанавливает исходную кавычку, но создает эквивалентную строку в кавычках / экранированную. Например, если вы передали аргумент 'uptime ; uname -a'
это может напечатать uptime\ \;\ uname\ -a
или же "uptime ; uname -a"
или любой другой эквивалент (см. ответ @William Pursell для похожих примеров).
Вот пример использования этих:
printf "Running command:"
printf " %q" innercmd "$@" # note the space before %q -- this inserts spaces between arguments
printf "\n"
innercmd "$@"
Используйте ${@@Q} для простого решения. Для проверки поместите следующие строки в скрипт bigQ.
#!/bin/bash
line="${@@Q}"
echo $line
./bigQ 1 a "4 5" b="6 7 8"
'1' 'a' '4 5' 'b=6 7 8'
Если пользователь вызывает вашу команду как:
./script 'foo'
первый аргумент, данный сценарию, является строкой foo
без кавычек. В вашем сценарии нет способа разграничить этот и любой другой метод, с помощью которого он может получить foo
в качестве аргумента (например, ./script $(echo foo)
или же ./script foo
или же ./script "foo"
или же ./script \f\o""''""o
).
Если вы хотите напечатать список аргументов как можно ближе к тому, что, вероятно, ввел пользователь:
#!/bin/bash
chars='[ !"#$&()*,;<>?\^`{|}]'
for arg
do
if [[ $arg == *\'* ]]
then
arg=\""$arg"\"
elif [[ $arg == *$chars* ]]
then
arg="'$arg'"
fi
allargs+=("$arg") # ${allargs[@]} is to be used only for printing
done
printf '%s\n' "${allargs[*]}"
Это не идеально. Аргумент как ''\''"'
труднее приспособить, чем оправдано.
Как уже упоминалось, когда вы обращаетесь к аргументам внутри вашего скрипта, уже слишком поздно знать, какие аргументы цитировались при его вызове. Однако вы можете повторно заключить в кавычки аргументы, содержащие пробелы или другие специальные символы, которые необходимо заключить в кавычки для передачи в качестве параметров.
Вот реализация Bash на основе Pythonshlex.quote(s)
это делает именно это:
function quote() {
declare -a params
for param; do
if [[ -z "${param}" || "${param}" =~ [^A-Za-z0-9_@%+=:,./-] ]]; then
params+=("'${param//\'/\'\"\'\"\'}'")
else
params+=("${param}")
fi
done
echo "${params[*]}"
}
Ваш пример немного изменился, чтобы показать пустые аргументы:
$ quote -m root@hostname,root@hostname -o -q -- 'uptime ; uname -a' ''
-m root@hostname,root@hostname -o -q -- 'uptime ; uname -a' ''
В моем случае я попытался вызвать bash какscript --argument="--arg-inner=1 --arg-inner2"
. К сожалению, любое решение выше не помогает в моем случае.
Окончательное решение было
#!/bin/bash
# Fix given array argument quotation
function quote() {
local QUOTED_ARRAY=()
for ARGUMENT; do
case ${ARGUMENT} in
--*=*)
QUOTED_ARRAY+=( "${ARGUMENT%%=*}=$(printf "%q" "${ARGUMENT#*=}")" )
shift
;;
*)
QUOTED_ARRAY+=( "$(printf " %q" "${ARGUMENT}")" )
;;
esac
done
echo ${QUOTED_ARRAY[@]}
}
ARGUMENTS="$(quote "${@}")"
echo "${ARGUMENTS}"
Результат в случае MacOS:--argument=--arg-inner=1\ --arg-inner2
что логически одно и то же.
У меня есть очень простое решение, и оно также работает вsh
оболочка:
Файл script1.sh
#!/bin/sh
concatenated_string=""
for ARG in "$@"; do
concatenated_string="${concatenated_string}'${ARG}' "
done
echo "${concatenated_string}"
eval "${concatenated_string}"
Когда я вызываю с помощью:./script1.sh sh -c "serve -l 9527 -s dist"
Результатом будет:
'sh' '-c' 'serve -l 9527 -s dist'
┌───────────────────────────────────────────────────┐
│ │
│ Serving! │
│ │
│ - Local: http://localhost:9527 │
│ - On Your Network: http://192.168.2.102:9527 │
│ │
└───────────────────────────────────────────────────┘
Как видите, первая строка отображает строку, в которой сохранены кавычки; вторая строка прямого выполненияsh -c "serve -l 9527 -s dist"
используяeval
. Обратите внимание, что вы можете передать любую команду, которую хотите оценить, какscript1.sh
аргументы.
eval "./script -m root@hostname,root@hostname -o -q -- 'uptime ; uname -a'"
Просто разделите каждый аргумент с помощью кавычек и символа nul :
#! /bin/bash
sender () {
printf '"%s"\0' "$@"
}
receiver () {
readarray -d '' args < <(function "$@")
}
receiver "$@"
Как прокомментировал user14122 .
🗂️ Сохранение разделения
Вам не нужно сохранять кавычки, а только разделение аргументов.
Для этого передайте аргументы как:
"${@}"
🗳️ Функция возвращает
А если вам нужно вернуть аргументы из функции, используйте цикл for :
for arg in "${args[@]}"; do
echo "${arg}"
done
И реконструируем в приемнике как массив :
readarray -t args < <(function "${@}")
🧩 Цитируется
Если даже этого недостаточно, процитируйте каждый отдельный аргумент следующим образом :
"${@@Q}"