Токенизация строки в bash без использования перенаправления

Что ж, в моем сценарии я использую простую строку для токенизации:

IFS=';' read -ra ALL_ADDR <<< "${SERVER}"

Однако в тот момент, когда я запускаю этот скрипт в док-контейнере busybox (alpine) linux, он спотыкается вокруг строки выше. line 43: syntax error: unexpected redirection

Я так понимаю, это потому, что скрипт запускается не Bash, а Ash, у которого нет параметра перенаправления <<<. Как я могу преобразовать приведенную выше строку в код, который работает одинаково во всех популярных сценариях оболочки. (в основном Баш, Эш и Дэш)?

для полноты переменная выглядит так:

SERVER="/api/v1:127.0.0.1:1337;/api/v2:127.0.0.1:1338"
IFS=';' read -ra ALL_ADDR <<< "${SERVER}"
for i in "${ALL_ADDR[@]}"; do
    IFS=':' read -ra ADDR <<< "${i}"
    PROXY=${ADDR[0]}
    IPADDR=${ADDR[1]}
    PORT=${ADDR[2]}
    LOC_STRING="${LOC_STRING}\tlocation ${PROXY} {\n\t\tproxy_pass http://${IPADDR}:${PORT}\n\t}\n"
done
LOC_STRING=${LOC_STRING//\//\\\/}

#"insert" above generated lines into a site config for nginx
sed -e "0,/^\s*location/{s/^\s*location/${LOC_STRING}\n&/}" 'portal' > "../sites-enabled/portal"

person paul23    schedule 08.01.2020    source источник
comment
Отвечает ли это на ваш вопрос? Bash: как маркировать строковую переменную?   -  person x3l51    schedule 08.01.2020
comment
Ни один из ответов на этот вопрос не является особенно безопасным, а некоторые из них используют массивы, которые ash не поддерживает.   -  person chepner    schedule 08.01.2020
comment
Вы можете использовать перенаправление here-doc для того же эффекта, но проблема перенаправления не единственная: read ash не поддерживает флаг -a. Вероятно, вам придется использовать цикл while read -r ADDR; do [...]; done <<DELIM; $SERVER; DELIM для достижения того же эффекта.   -  person Aaron    schedule 08.01.2020
comment
как выглядит содержимое var?   -  person Paul Hodges    schedule 08.01.2020
comment
@PaulHodges Я обновил вопрос, указав вариант использования.   -  person paul23    schedule 08.01.2020


Ответы (3)


Кажется, вам действительно не нужно его токенизировать. Просто сгенерируйте результирующую строку.

Ниже я сначала заменяю каждый : или ; на новую строку. Затем с помощью xargs я читаю 3 переменные за раз и перехожу к формату printf. sed 's@/@\\/@g' делает то же самое, что и LOC_STRING=${LOC_STRING//\//\\\/}.

SERVER="/api/v1:127.0.0.1:1337;/api/v2:127.0.0.1:1338"
LOC_STRING=$(
    printf "%s\n" "$SERVER"  |
    tr ':;' '\n\n' |
    xargs -d$'\n' -n3 printf '\tlocation %s {\\n\t\tproxy_pass http://%s:%s\\n\t}\\n' |
    sed 's@/@\\/@g'
)

echo "$LOC_STRING"

выходы:

    location \/api\/v1 {\n      proxy_pass http:\/\/127.0.0.1:1337\n  }\n location \/api\/v2 {\n      proxy_pass http:\/\/127.0.0.1:1338\n  }\n

Чтобы «разметить» его, проще всего вставить новую строку вместо разделителя и прочитать его как поток, разделенный новой строкой. Будут проблемы, если в строке есть сам перевод строки, но судя по IFS=';' read -ra ALL_ADDR <<< "${SERVER}" в SERVER нет новых строк или вас интересует только первая строка. Так же - замените ; и : на новую строку. Тогда просто читайте по 3 статьи за раз. Используйте << для перенаправления чего-либо в оболочке posix.

tmp=$(printf "%s\n" "$SERVER" | tr ';:' '\n\n')
while IFS= read -r PROXY &&
        IFS= read -r IPADDR &&
        IFS= read -r POST; do
    LOC_STRING="${LOC_STRING}\tlocation ${PROXY} {\n\t\tproxy_pass http://${IPADDR}:${PORT}\n\t}\n"
done <<EOF
$tmp
EOF
LOC_STRING=${LOC_STRING//\//\\\/}

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

person KamilCuk    schedule 08.01.2020

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

IFS=';' read -ra ALL_ADDR << EOF
${SERVER}
EOF

Обратите внимание, что вам действительно не следует кричать, и это было бы лучше написать:

IFS=';' read -ra all_addr << EOF
${server}
EOF
person William Pursell    schedule 08.01.2020
comment
Однако ash также не поддерживает массивы. - person chepner; 08.01.2020
comment
@chepner Да, отсюда и первое предложение! Возможно, я должен быть более явным. - person William Pursell; 08.01.2020

Возможно, я не отвечаю прямо на ваш вопрос, но как насчет решения sed:

SERVER="/api/v1:127.0.0.1:1337;/api/v2:127.0.0.1:1338"
LOC_STRING=$(echo "$SERVER" | tr ';' '\n' | sed -e 's#\([^:]*\):\([^:]*\):\([^:]*\)#\tlocation \1 {\n\t\tproxy_pass http://\2:\3\n\t}#' -e 's#/#\\/#g')

Выход echo "$LOC_STRING":

    location \/api\/v1 {
        proxy_pass http:\/\/127.0.0.1:1337
    }
    location \/api\/v2 {
        proxy_pass http:\/\/127.0.0.1:1338
    }

Обратите внимание, что слово \n в двойных кавычках декодируется как символ новой строки в ash, тогда как в bash оно остается без изменений. В зависимости от ваших требований может потребоваться небольшая настройка.

person tshiono    schedule 08.01.2020