Ответ 1
Это абсолютно выполнимо.
Ответ, который вы видите Джесси Глик, примерно там, но у него есть несколько ошибок, и у меня есть несколько альтернатив для вашего рассмотрения, так как это проблема, с которой я столкнулся не раз.
Во-первых, и вы, возможно, уже знаете это, эхо - это плохая идея, вместо этого следует использовать printf, если целью является переносимость: "echo" имеет поведение undefined в POSIX, если аргумент, который он получает, равен "-n", и на практике некоторые реализации эха рассматривают как специальный вариант, в то время как другие просто рассматривают его как обычный аргумент для печати. Таким образом, это станет следующим:
esceval()
{
printf %s "$1" | sed "s/'/'\"'\"'/g"
}
В качестве альтернативы вместо того, чтобы скрывать встроенные одинарные кавычки, превращая их в:
'"'"'
.. вместо этого вы можете включить их:
'\''
.. стилистические различия. Я предполагаю (я думаю, что разница в производительности ничтожно мала в любом случае, хотя я никогда не тестировал). Получившаяся строка sed выглядит следующим образом:
esceval()
{
printf %s "$1" | sed "s/'/'\\\\''/g"
}
(Это четыре обратной косой черты, потому что двойные кавычки проглатывают два из них и оставляют два, а затем sed ласточки один, оставляя только один. Лично я считаю этот способ более читабельным, так что то, что я буду использовать в остальной части примеры, которые включают его, но оба должны быть эквивалентными.)
НО, у нас все еще есть ошибка: подстановка команды удалит по крайней мере одну (но во многих оболочках ВСЕ) конечных строк новой строки из выходных команд (не все пробелы, только новые строки). Таким образом, вышеупомянутое решение работает, если у вас нет новой строки (-ов) в самом конце аргумента. Затем вы потеряете эту/новую строку (ы). Исправление, очевидно, просто: добавьте еще один символ после фактического значения команды перед выводом из функции quote/esceval. Кстати, нам все равно нужно было это сделать, потому что нам нужно было запустить и остановить экранированный аргумент с одинарными кавычками. Честно говоря, я не понимаю, почему это не было сделано для начала. У вас есть две альтернативы:
esceval()
{
printf '%s\n' "$1" | sed "s/'/'\\\\''/g; 1 s/^/'/; $ s/$/'/"
}
Это гарантирует, что аргумент выходит уже полностью экранированным, не нужно добавлять больше одинарных кавычек при создании окончательной строки. Скорее всего, это самая близкая вещь, которую вы попадете в одну, встроенную версию. Если вы в порядке с зависимостью sed, вы можете остановиться здесь.
Если вы не согласны с зависимостью sed, но вы в порядке, полагая, что ваша оболочка на самом деле совместима с POSIX (там все еще есть некоторые, в частности, /bin/sh на Solaris 10 и ниже, что не сможет сделать следующий вариант - но почти все снаряды, о которых вам нужно позаботиться, сделают это просто отлично):
esceval()
{
printf \'
UNESCAPED=$1
while :
do
case $UNESCAPED in
*\'*)
printf %s "${UNESCAPED%%\'*}""'\''"
UNESCAPED=${UNESCAPED#*\'}
;;
*)
printf %s "$UNESCAPED"
break
esac
done
printf \'
}
Возможно, вы заметили, что избыточное цитирование здесь:
printf %s "${UNESCAPED%%\'*}""'\''"
.. это можно заменить на:
printf %s "${UNESCAPED%%\'*}'\''"
Единственная причина, по которой я делаю предыдущую, состоит в том, что в то время были ракеты Bourne, у которых были ошибки при замене переменных в строки с кавычками, где цитата вокруг переменной точно не начиналась и не заканчивалась там, где выполнялась замена переменных. Следовательно, это параноидальная привычка к переносимости. На практике вы можете сделать последнее, и это не будет проблемой.
Если вы не хотите сжимать переменную UNESCAPED в остальной части вашей оболочки, то вы можете обернуть все содержимое этой функции в подоболочку, например:
esceval()
{
(
printf \'
UNESCAPED=$1
while :
do
case $UNESCAPED in
*\'*)
printf %s "${UNESCAPED%%\'*}""'\''"
UNESCAPED=${UNESCAPED#*\'}
;;
*)
printf %s "$UNESCAPED"
break
esac
done
printf \'
)
}
"Но подождите", вы говорите: "Что я хочу сделать для MULTIPLE аргументов в одной команде? И я хочу, чтобы результат все еще выглядел красивым и разборчивым для меня, как пользователя, если я запустил его из командной строки по какой-то причине".
Никогда не бойтесь, я вас покрыл:
esceval()
{
case $# in 0) return 0; esac
while :
do
printf "'"
printf %s "$1" | sed "s/'/'\\\\''/g"
shift
case $# in 0) break; esac
printf "' "
done
printf "'\n"
}
.. или то же самое, но с версией только для оболочки:
esceval()
{
case $# in 0) return 0; esac
(
while :
do
printf "'"
UNESCAPED=$1
while :
do
case $UNESCAPED in
*\'*)
printf %s "${UNESCAPED%%\'*}""'\''"
UNESCAPED=${UNESCAPED#*\'}
;;
*)
printf %s "$UNESCAPED"
break
esac
done
shift
case $# in 0) break; esac
printf "' "
done
printf "'\n"
)
}
В последних четырех случаях вы можете свернуть некоторые внешние инструкции printf и свернуть их одиночные кавычки в другой printf - я сохранил их отдельно, потому что я чувствую, что это делает логику более ясной, когда вы можете видеть начальную и конечную одно- цитаты в отдельных отчетах о печати.
P.S. Кроме того, это чудовище, которое я сделал, это polyfill, который будет выбирать между двумя предыдущими версиями в зависимости от того, может ли ваша оболочка поддерживать необходимый синтаксис подстановки переменных (это выглядит ужасно, хотя, поскольку версия с оболочкой имеет чтобы внутри встроенной строки держать несовместимые оболочки от штрихов, когда они видят это): https://github.com/mentalisttraceur/esceval/blob/master/sh/esceval.sh