5

Как выполнить кодирование URL для команды curl?

12

Я пытаюсь написать bash-скрипт для тестирования, который принимает параметр и отправляет его с помощью curl на веб-сайт. Мне необходимо закодировать значение в URL, чтобы специальные символы обрабатывались корректно. Какой самый лучший способ сделать это?

Вот мой базовый скрипт на данный момент:

#!/bin/bash
host=${1:?'неверный хост'}
value=$2
shift
shift
curl -v -d "param=${value}" http://${host}/somepath $@

5 ответ(ов)

3

Другой вариант — использовать jq:

$ printf %s 'input text' | jq -sRr @uri
input%20text
$ jq -rn --arg x 'input text' '$x | @uri'
input%20text

Флаг -r (--raw-output) выводит необработанные строки вместо их JSON-литералов. Флаг -n (--null-input) не считывает входные данные из STDIN.

Флаг -R (--raw-input) обрабатывает строки ввода как обычные строки, не разбирая их как JSON, а -sR (--slurp --raw-input) считывает входные данные в одну строку. Вы можете заменить -sRr на -Rr, если ваш ввод состоит только из одной строки или если вы не хотите заменять переносы строк на %0A:

$ printf %s\\n 'multiple lines of text' | jq -Rr @uri
multiple%20lines
of%20text
$ printf %s\\n 'multiple lines of text' | jq -sRr @uri
multiple%20lines%0Aof%20text%0A

Или вот способ, который процент-кодирует все байты:

xxd -p | tr -d \\n | sed 's/../%&/g'
2

Вот чистое решение на BASH.

Обновление: Поскольку обсуждалось много изменений, я разместил это на https://github.com/sfinktah/bash/blob/master/rawurlencode.inc.sh, чтобы любой мог внести свой вклад.

Примечание: Это решение не предназначено для кодирования юникода или многобайтовых символов — это выходит за скромные родные возможности BASH. Оно предназначено лишь для кодирования символов, которые иначе могут испортить передачу аргументов в POST или GET запросах, например, '&', '=' и так далее.

Очень важное примечание: НИКОГДА НЕ ПЫТАЙТЕСЬ НАПИСАТЬ СВОЮ СОБСТВЕННУЮ ФУНКЦИЮ ПРЕОБРАЗОВАНИЯ ЮНИКОДА, НИ НА КАКОМ ЯЗЫКЕ, НИКОГДА. Смотрите в конце ответа.

rawurlencode() {
  local string="${1}"
  local strlen=${#string}
  local encoded=""
  local pos c o

  for (( pos=0 ; pos<strlen ; pos++ )); do
     c=${string:$pos:1}
     case "$c" in
        [-_.~a-zA-Z0-9] ) o="${c}" ;;
        * )               printf -v o '%%%02x' "'$c"
     esac
     encoded+="${o}"
  done
  echo "${encoded}"    # Вы можете либо установить переменную возврата (БЫСТРЕЕ),
  REPLY="${encoded}"   # или вывести результат ( ПРОЩЕ ) ... или и то, и другое... :p
}

Вы можете использовать его двумя способами:

попроще:  echo http://url/q?=$( rawurlencode "$args" )
побыстрее:  rawurlencode "$args"; echo http://url/q?${REPLY}

[редактировано]

Вот соответствующая функция rawurldecode(), которая, с определенной скромностью, потрясающая.

# Возвращает строку, в которой последовательности со знаками процентов (%)
# и двумя шестнадцатеричными цифрами были заменены на буквенные символы.
rawurldecode() {

  # Это, возможно, рискованный шаг, но так как все символы экранирования должны быть
  # закодированы, мы можем заменить %NN на \xNN и передать все это в printf -b, который
  # декодирует шестнадцатеричный для нас

  printf -v REPLY '%b' "${1//%/\\x}" # Вы можете либо установить переменную возврата (БЫСТРЕЕ)

  echo "${REPLY}"  # либо вывести результат ( ПРОЩЕ ) ... или и то, и другое... :p
}

С этим набором мы можем теперь провести несколько простых тестов:

$ diff rawurlencode.inc.sh \
        <( rawurldecode "$( rawurlencode "$( cat rawurlencode.inc.sh )" )" ) \
        && echo Совпало

Вывод: Совпало

Если вы все-таки ощущаете необходимость в инструменте стороннего производителя (он действительно будет работать намного быстрее и может обрабатывать бинарные файлы и подобное...), я нашел это на своем маршрутизаторе OpenWRT...

replace_value=$(echo $replace_value | sed -f /usr/lib/ddns/url_escape.sed)

Где url_escape.sed был файлом, содержащим эти правила:

# sed url escaping
s:%:%25:g
s: :%20:g
s:<:%3C:g
s:>:%3E:g
s:#:%23:g
s:{:%7B:g
s:}:%7D:g
s:|:%7C:g
s:\\:%5C:g
s:\^:%5E:g
s:~:%7E:g
s:\[:%5B:g
s:\]:%5D:g
s:`:%60:g
s:;:%3B:g
s:/:%2F:g
s:?:%3F:g
s^:^%3A^g
s:@:%40:g
s:=:%3D:g
s:&:%26:g
s:\$:%24:g
s:\!:%21:g
s:\*:%2A:g

Хотя не невозможно написать такой скрипт на BASH (наверное, используя xxd и очень длинный набор правил), способный обрабатывать UTF-8 ввод, есть более быстрые и надежные способы. Попытки декодировать UTF-8 в UTF-32 — это нетривиальная задача, которую сложно выполнить с точностью, хотя ее легко выполнить неточно, что создаст иллюзию работоспособности до тех пор, пока она не сломается.

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

Стандарт Юникода постоянно развивается и становится крайне сложным. Любая реализация, которую вы сможете собрать, не будет должным образом соответствовать требованиям, и если вам каким-то образом удастся это сделать, она не останется соответствующей.

1

Чтобы использовать модуль URI::Escape в Perl и функцию uri_escape во второй строке вашего bash-скрипта, вы можете сделать это следующим образом:

...

value="$(perl -MURI::Escape -e 'print uri_escape($ARGV[0]);' "$2")"
...

Обратите внимание на то, что мы передаем аргумент $2 в скрипт Perl, обернув его в двойные кавычки. Это важно для правильной обработки значений с пробелами или специальными символами.

Edit: Исправлены проблемы с кавычками, как было предложено Крисом Джонсеном в комментариях. Спасибо!

0

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

Вот пример вашего кода на Python:

encoded_value=$(python3 -c "import urllib.parse; print(urllib.parse.quote('''$value'''))")

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

"http://www.rai.it/dl/audio/" "1264165523944Ho servito il re d'Inghilterra - Puntata 7"

Таким образом, использование urllib.parse.quote идеально подходит для URL-кодирования, и ваш подход позволяет избежать возможных проблем с синтаксисом.

0

Если вы хотите выполнить GET запрос и использовать чистый curl, просто добавьте --get к решению @Jacob.

Вот пример:

curl -v --get --data-urlencode "access_token=$(cat .fb_access_token)" https://graph.facebook.com/me/feed
Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь