Передача нескольких аргументов в кавычках команде в оболочке Bourne

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

myutil setOptions "ONE\APPLE" "ONE\PEAR" "TWO\RED GRAPE" "TWO\TOMATO"

Я работаю над скриптом, который будет считывать эти параметры из многострочного файла и передавать их в скрипт. Входной файл myinput.txt выглядит так:

# cat myinput.txt
ONE\APPLE
ONE\PEAR
TWO\RED GRAPE
TWO\TOMATO

Я использую следующий код для анализа файла и выполнения myutil. Сценарий считывает каждую строку как аргумент и заключает ее в двойные кавычки (как myutil будет ожидать значений с пробелами или специальными символами) и создает одну переменную для хранения всей строки аргумента:

#!/bin/sh
MYCOMMAND=myutil
if [ -f myinput.txt ]; then
    ARGSLIST=`cat myinput.txt| sed -e 's/^/"/g' -e 's/$/"/g' | awk '{ printf "%s ",  $0  }'`
    $MYCOMMAND setOptions "${ARGSLIST}"
    printf "%s\n" "$MYCOMMAND setOptions ${ARGSLIST}"
fi

Как и следовало ожидать, вывод этой команды на экран выглядит так, как и ожидалось:

myutil setOptions "ONE\APPLE" "ONE\PEAR" "TWO\RED GRAPE" "TWO\TOMATO"

Однако на самом деле это не то, что выполняется, основываясь на том, как myutil обычно обрабатывает эту команду. Вместо этого myutil обрабатывает это так, как если бы ВСЕ аргументы также были заключены в одинарные кавычки. Запуск этой отладки под sh не отражает этого:

+ MYCOMMAND=myutil
+ [ -f myinput.txt ]
+ awk { printf "%s ",  $0  }
+ sed -e s/^/"/g -e s/$/"/g
+ cat myinput.txt
+ ARGSLIST="ONE\APPLE" "ONE\PEAR" "TWO\RED GRAPE" "TWO\TOMATO"
+ myutil setOptions "ONE\APPLE" "ONE\PEAR" "TWO\RED GRAPE" "TWO\TOMATO"
+ printf %s\n myutil setOptions "ONE\APPLE" "ONE\PEAR" "TWO\RED GRAPE" "TWO\TOMATO"
myutil setOptions "ONE\APPLE" "ONE\PEAR" "TWO\RED GRAPE" "TWO\TOMATO"

ОДНАКО запуск отладки под bash, похоже, показывает, что на самом деле выполняется:

+ MYCOMMAND=myutil
+ '[' -f myinput.txt ']'
++ awk '{ printf "%s ",  $0  }'
++ sed -e 's/^/"/g' -e 's/$/"/g'
++ cat myinput.txt
+ ARGSLIST='"ONE\APPLE" "ONE\PEAR" "TWO\RED GRAPE" "TWO\TOMATO" '
+ myutil setOptions '"ONE\APPLE" "ONE\PEAR" "TWO\RED GRAPE" "TWO\TOMATO" '
+ printf '%s\n' 'myutil setOptions "ONE\APPLE" "ONE\PEAR" "TWO\RED GRAPE" "TWO\TOMATO" '
myutil setOptions "ONE\APPLE" "ONE\PEAR" "TWO\RED GRAPE" "TWO\TOMATO"

Вы можете увидеть строку отладки, которая выглядит так:

+ myutil setOptions '"ONE\APPLE" "ONE\PEAR" "TWO\RED GRAPE" "TWO\TOMATO" '

Эта строка на самом деле выполняется на основе вывода, который я вижу в своей утилите, но на экран выводится вышеупомянутое за вычетом одинарных кавычек:

myutil setOptions "ONE\APPLE" "ONE\PEAR" "TWO\RED GRAPE" "TWO\TOMATO"

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

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

Во-первых, просто используйте eval:

#!/bin/sh
MYCOMMAND=myutil
if [ -f myinput.txt ]; then
    ARGSLIST=`cat myinput.txt| sed -e 's/^/"/g' -e 's/$/"/g' | awk '{ printf "%s ",  $0  }'`
    eval $MYCOMMAND setOptions "${ARGSLIST}"
    printf "%s\n" "$MYCOMMAND setOptions ${ARGSLIST}"
fi

Второй — использование xargs:

#!/bin/sh
MYCOMMAND=myutil
if [ -f myinput.txt ]; then
    ARGSLIST=`cat myinput.txt| sed -e 's/^/"/g' -e 's/$/"/g' | awk '{ printf "%s ",  $0  }'`
    printf "%s\n" "$ARGSLIST" | xargs $MYCOMMAND setOptions 
    printf "%s\n" "$MYCOMMAND setOptions ${ARGSLIST}"
fi

Мне трудно поверить, что xargs требуется, и я неоднократно читал, что использование eval, вероятно, также не рекомендуется.

Я видел несколько похожих тем по этому поводу с советами по использованию массивов, но я не смог правильно применить их советы к моему примеру. Кроме того, мне нужно, чтобы это решение работало под bourne sh и было максимально переносимым, поскольку оно будет работать на большинстве стандартных разновидностей Linux/UNIX. Я не верю, что sh поддерживает массивы того типа, который рекомендовали другие решения.

Каков наилучший способ выполнить мою команду, поскольку она фактически отображается на экране без дополнительных кавычек, предоставляемых оболочкой?


person BenH    schedule 27.07.2015    source источник


Ответы (1)


Вы можете определить рекурсивную функцию оболочки, которая считывает одну строку из своего стандартного ввода за раз для создания командной строки. Когда больше нет доступных входных данных, фактическая команда запускается с накопленными аргументами. Рекурсия добавляет некоторые накладные расходы, но я предполагаю, что входной файл не очень большой.

run_from_file () {
   if IFS= read -r newarg; then
       run_from_file "$@" "$newarg"
   else
       "$MYCOMMAND" "$@"
   fi
}

run_from_file < myinput.txt
person chepner    schedule 27.07.2015
comment
Это работает на большинстве систем. К сожалению, while read под shв Solaris не поддерживает параметр -r. Без этого я не могу разобрать файл, содержащий разделители "\". Хотя я мог бы реализовать это в решении, в котором я сохраняю данные с другим разделителем (скажем, ||), а затем переводю их после прочтения строки, я думаю, что, возможно, должен быть более изящный способ? Я знаю, что ограничения здесь строгие... почему sh не совместим с POSIX, я понятия не имею... Но спасибо за пример массива, он делает некоторые другие примеры, которые я видел, намного яснее. - person BenH; 27.07.2015
comment
Я думаю, вам лучше убедиться, что Solaris выполняет сценарий с помощью /usr/xpg4/bin/sh (оболочка POSIX), а не /bin/sh (которая, насколько я понимаю, является фактической оболочкой Bourne). - person chepner; 27.07.2015
comment
это сработает, но опять же, я мог бы просто использовать /bin/bash в этих системах. Мне были даны требования, согласно которым bash может быть доступен не во всех системах, и поэтому сценарий написан для запуска как #!/bin/sh. Я не уверен, как мы можем убедиться, что скрипт работает с соответствующей оболочкой. Если мы поместим #!/usr/xpg4/bin/sh вверху, то любая система, не имеющая этого пути (не Solaris), выйдет из строя с bad interpreter. Я думаю, что единственный способ выбрать правильный — это использовать сценарий-оболочку, чего мы хотели бы избежать. - person BenH; 27.07.2015
comment
Это немного не по теме, но что запускает ваш скрипт? Вместо того, чтобы предполагать, что сценарий является исполняемым и использует шебанг, просто попросите исполнителя выполнить сценарий, используя правильный исполняемый файл. - person chepner; 27.07.2015
comment
Да, это вариант, но не идеальный для нашего сценария. Сценарий должен быть доставлен потребителям и запущен на любых системах, которые они обслуживают. Потребители могут быть осведомлены о тонкостях кода скрипта, а могут и не знать, и от них не требуется самостоятельно отвечать на вопрос о выборе оболочки. Таким образом, хотя время разработки и, возможно, самое элегантное решение могут быть скомпрометированы, если конечный результат позволяет потребителю не принимать никаких разумных решений, и это работает - тогда цель достигнута! - person BenH; 27.07.2015