Сохранение котировок в параметрах функции bash

То, что я хотел бы сделать, это взять в качестве функции функции строку, которая может включать в себя кавычки (одиночные или двойные) и эхо-строку, точно так же, как она была предоставлена ​​функции. Например:

function doit {
   printf "%s " ${@} 
   eval "${@}"
   printf " # [%3d]\n" ${?}
}

Что, учитывая следующий ввод

doit VAR=42
doit echo 'single quote $VAR'
doit echo "double quote $VAR"

Допускается следующее:

VAR=42  # [  0]
echo single quote $VAR  # [  0]
echo double quote 42  # [  0]

Таким образом, семантика расширения переменной сохраняется, как я ожидал, но я не могу получить точный формат строки, как она была предоставлена ​​функции. Я хотел бы получить doit echo 'single quote $VAR' результат echo 'single quote $VAR'.

Я уверен, что это связано с bash обработкой аргументов, прежде чем они будут переданы функции; Я просто ищу способ обойти это (если возможно).

Изменить

Итак, я намеревался затенять выполнение script, предоставляя точную копию выполнения, которое может быть использовано в качестве диагностического инструмента, включая статус выхода на каждом шаге.

Пока я могу получить желаемое поведение, описанное выше, сделав что-то вроде

while read line ; do 
   doit ${line}
done < ${INPUT}

Этот подход выходит из строя перед структурами управления (т.е. if, while и т.д.). Я думал об использовании set -x, но у него тоже есть ограничения: " становится ', а статус выхода не отображается для команд, которые не работают.

Ответы

Ответ 1

Причина этого в том, что bash интерпретирует аргументы, как вы думали. Котировки просто не существуют, когда он вызывает функцию, поэтому это невозможно. Он работал в DOS, потому что программы могли сами интерпретировать командную строку, а не то, что она вам помогает!

Ответ 2

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

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

Здесь мой script настроен на тень ls:

CMD=ls
PARAMS=""

for PARAM in "[email protected]"
do
  PARAMS="${PARAMS} \"${PARAM}\""
done

echo Running: ${CMD} ${PARAMS}
bash -c "${CMD} ${PARAMS}"
echo Exit Code: $?

И это пример вывода:

$ ./shadow.sh missing-file "not a file"
Running: ls "missing-file" "not a file"
ls: missing-file: No such file or directory
ls: not a file: No such file or directory
Exit Code: 1

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

Ответ 3

Хотя @Peter Westlake ответ является правильным, и нет кавычек для сохранения, можно попробовать выведите, если кавычки там, где это требуется, и таким образом переданы изначально. Лично я использовал эту функцию requote, когда мне понадобилось доказательство в моих журналах, что команда запускалась с правильным цитированием:

function requote() {
    local res=""
    for x in "${@}" ; do
        # try to figure out if quoting was required for the $x:
        grep -q "[[:space:]]" <<< "$x" && res="${res} '${x}'" || res="${res} ${x}"
    done
    # remove first space and print:
    sed -e 's/^ //' <<< "${res}"
}

И вот как я его использую:

CMD=$(requote "${@}")
# ...
echo "${CMD}"

Ответ 4

doit echo "'single quote $VAR'"
doit echo '"double quote $VAR"'

Оба будут работать.

bash будет только разделять внешний набор кавычек при вводе функции.

Ответ 5

Bash удалит цитату, когда вы передаете строку с цитатой в качестве аргумента командной строки. Цитата просто не существует, когда строка переходит к вашему script. У вас нет возможности узнать, есть ли одна цитата или двойная кавычка.

Что вы, вероятно, можете сделать, так это:

doit VAR=42
doit echo \'single quote $VAR\'
doit echo \"double quote $VAR\"

В script вы получите

echo 'single quote $VAR'
echo "double quote $VAR"

Или сделайте это

doit VAR=42
doit echo 'single quote $VAR'
doit echo '"double quote $VAR"'

В script вы получите

echo single quote $VAR
echo "double quote $VAR"

Ответ 6

Это:

ponerApostrofes1 () 
{
    for (( i=1; i<=$#; i++ ));
    do
        eval VAR="\${$i}"; 
        echo \'"${VAR}"\';
    done; 
    return; 
}

В качестве примера возникают проблемы, когда параметры имеют апострофы.

Эта функция:

ponerApostrofes2 () 
{ 
    for ((i=1; i<=$#; i++ ))
    do
        eval PARAM="\${$i}";
        echo -n \'${PARAM//\'/\'\\\'\'}\'' ';
    done;
    return
}

решает указанную проблему, и вы можете использовать параметры, включая апострофы внутри, например "Porky's", и возвращает, по-видимому (?), ту же строку параметров, когда каждый параметр цитируется; если нет, это цитирует его. Удивительно, но я не понимаю, почему, если вы используете его рекурсивно, он не возвращает тот же список, но каждый параметр цитируется снова. Но если вы выполняете эхо каждого из них, вы восстанавливаете исходный параметр.

Пример:

$ ponerApostrofes2 'aa aaa' 'bbbb b' 'c' 
'aa aaa' 'bbbb b' 'c'

$ ponerApostrofes2 $(ponerApostrofes2 'aa aaa' 'bbbb b' 'c' )
''\''aa' 'aaa'\''' ''\''bbbb' 'b'\''' ''\''c'\''' 

и

$ echo ''\''bbbb' 'b'\'''
'bbbb b'
$ echo ''\''aa' 'aaa'\'''
'aa aaa'
$ echo ''\''c'\''' 
'c'

И этот:

ponerApostrofes3 () 
{ 
    for ((i=1; i<=$#; i++ ))
    do
        eval PARAM="\${$i}";
        echo -n ${PARAM//\'/\'\\\'\'} ' ';
    done;
    return
}

возврат на один уровень меньше, тоже не работает, не чередуя рекурсивно.

Ответ 7

Оболочка будет интерпретировать кавычки и $, прежде чем передать ее вашей функции. Не так много может сделать ваша функция, чтобы вернуть специальные символы, потому что у нее нет возможности узнать (в примере с двойной кавычкой), был ли 42 жестко закодирован или если он пришел из переменной. Вам придется избегать специальных символов, если вы хотите, чтобы они выдержали достаточно долго, чтобы выполнить эту функцию.

Ответ 8

Если одна оболочка не поддерживает подстановку шаблона, т.е. ${param/pattern/string}, то следующее выражение sed может использоваться для безопасного цитирования любой строки, так что она снова будет eval в один параметр:

sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/"

Объединяя это с printf, можно написать небольшую функцию, которая возьмет любой список строк, созданных расширением имени файла, или "[email protected]" и превратит его в нечто, которое можно безопасно передать в eval, чтобы развернуть его в аргументы для другой команды при безопасном сохранении разделения параметров.

# Usage: quotedlist=$(shell_quote args...)
#
# e.g.:  quotedlist=$(shell_quote *.pdf)    # filenames with spaces
#
# or:    quotedlist=$(shell_quote "[email protected]")
#
# After building up a quoted list, use it by evaling it inside
# double quotes, like this:
#
#   eval "set -- $quotedlist"
#   for str in "[email protected]"; do
#       # fiddle "${str}"
#   done
#
# or like this:
#
#   eval "\$a_command $quotedlist \$another_parameter"
#
shell_quote()
{
    local result=''
    local arg
    for arg in "[email protected]" ; do

        # Append a space to our result, if necessary
        #
        result=${result}${result:+ }

        # Convert each embedded ' to \' , then insert ' at the
        # beginning of the line, and append ' at the end of
        # the line.
        #
        result=${result}$(printf "%s\n" "$arg" | \
            sed -e "s/'/'\\\\''/g" -e "1s/^/'/" -e "\$s/\$/'/")
    done

    # use printf(1) instead of echo to avoid weird "echo"
    # implementations.
    #
    printf "%s\n" "$result"
}

В некоторых ситуациях может быть проще (а может быть, безопаснее, а значит избегать eval) использовать "невозможный" символ в качестве разделителя полей, а затем использовать IFS для управления расширением значения снова.