Сохранить кавычки в аргументах 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'"

https://ss64.com/bash/eval.html

Просто разделите каждый аргумент с помощью кавычек и символа 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}"


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