Подтвердить что ты не робот

POSIX sh для Bash s printf% q

Предположим, что у меня есть #!/bin/sh script, который может принимать различные позиционные параметры, некоторые из которых могут включать пробелы либо/оба типа кавычек, и т.д. Я хочу повторить итерацию "[email protected]", и для каждого аргумента немедленно обработать его или сохранить его позже. В конце script я хочу запустить (возможно, exec) другой процесс, передав некоторые из этих параметров со всеми специальными символами.

Если бы я не обрабатывал параметры, othercmd "[email protected]" работал бы отлично, но мне нужно немного вытащить некоторые параметры и обработать их.

Если бы я мог предположить Bash, то я мог бы использовать printf %q для вычисления цитированных версий args, которые я мог бы eval позже, но это не сработало бы, например. Ubuntu Dash (/bin/sh).

Существует ли какой-либо эквивалент printf %q, который может быть написан в простой оболочке Bourne script, используя только встроенные и POSIX-определенные утилиты, например, как функцию, которую я мог бы скопировать в script?

Например, script пытается ls аргументы в обратном порядке:

#!/bin/sh
args=
for arg in "[email protected]"
do
    args="'$arg' $args"
done
eval "ls $args"

работает во многих случаях:

$ ./handle goodbye "cruel world"
ls: cannot access cruel world: No such file or directory
ls: cannot access goodbye: No such file or directory

но не тогда, когда используется ':

$ ./handle goodbye "cruel'st world"
./handle: 1: eval: Syntax error: Unterminated quoted string

и следующее работает нормально, но полагается на Bash:

#!/bin/bash
args=
for arg in "[email protected]"
do
    printf -v argq '%q' "$arg"
    args="$argq $args"
done
eval "ls $args"
4b9b3361

Ответ 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

Ответ 2

Я думаю, что это POSIX. Он работает, очищая [email protected] после его расширения для цикла for, но только один раз, чтобы мы могли итеративно создавать его обратно (в обратном порядке) с помощью set.

flag=0
for i in "[email protected]"; do
    [ "$flag" -eq 0 ] && shift $#
    set -- "$i" "[email protected]"
    flag=1
done

echo "[email protected]"   # To see that "[email protected]" has indeed been reversed
ls "[email protected]"

Я понимаю, что реверсирование аргументов было просто примером, но вы можете использовать этот трюк set -- "$arg" "[email protected]" или set -- "[email protected]" "$arg" в других ситуациях.

И да, я понимаю, что, возможно, просто переопределил (плохо) ormaaj Push.

Ответ 3

Push. См. Пример readme.

Ответ 4

Следующее, похоже, работает со всем, что я на него набросал до сих пор, включая пробелы, оба вида кавычек и множество других метасимволов и встроенные строки:

#!/bin/sh
quote() {
    echo "$1" | sed "s/'/'\"'\"'/g"
}
args=
for arg in "[email protected]"
do
    argq="'"`quote "$arg"`"'"
    args="$argq $args"
done
eval "ls $args"