Как урленкодировать данные для команды curl? - PullRequest
274 голосов
/ 17 ноября 2008

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

Вот мой основной сценарий:

#!/bin/bash
host=${1:?'bad host'}
value=$2
shift
shift
curl -v -d "param=${value}" http://${host}/somepath $@

Ответы [ 30 ]

330 голосов
/ 08 января 2010

Использование curl --data-urlencode; от man curl:

Публикует данные, аналогично другим опциям --data, за исключением того, что выполняется кодирование URL. Чтобы быть CGI-совместимым, часть <data> должна начинаться с имени, за которым следует разделитель и спецификация содержимого.

Пример использования:

curl \
    --data-urlencode "paramName=value" \
    --data-urlencode "secondParam=value" \
    http://example.com

Для получения дополнительной информации см. справочную страницу .

Для этого требуется curl 7.18.0 или новее (выпущено в январе 2008 г.) . Используйте curl -V, чтобы проверить, какая у вас версия.

157 голосов
/ 19 мая 2012

Вот чистый ответ BASH.

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}"    # You can either set a return variable (FASTER) 
  REPLY="${encoded}"   #+or echo the result (EASIER)... or both... :p
}

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

easier:  echo http://url/q?=$( rawurlencode "$args" )
faster:  rawurlencode "$args"; echo http://url/q?${REPLY}

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

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

# Returns a string in which the sequences with percent (%) signs followed by
# two hex digits have been replaced with literal characters.
rawurldecode() {

  # This is perhaps a risky gambit, but since all escape characters must be
  # encoded, we can replace %NN with \xNN and pass the lot to printf -b, which
  # will decode hex for us

  printf -v REPLY '%b' "${1//%/\\x}" # You can either set a return variable (FASTER)

  echo "${REPLY}"  #+or echo the result (EASIER)... or both... :p
}

Имея соответствующий набор, мы можем выполнить несколько простых тестов:

$ diff rawurlencode.inc.sh \
        <( rawurldecode "$( rawurlencode "$( cat rawurlencode.inc.sh )" )" ) \
        && echo Matched

Output: Matched

И если вы действительно чувствуете, что вам нужен внешний инструмент (ну, он будет работать намного быстрее, и может делать двоичные файлы и тому подобное ...), я нашел это на своем маршрутизаторе 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
89 голосов
/ 18 ноября 2008

Используйте модуль Perl URI::Escape и функцию uri_escape во второй строке вашего bash-скрипта:

...

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

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

57 голосов
/ 22 сентября 2011

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

безопасный способ urlencode - просто кодировать каждый байт - даже те, которые были бы разрешены.

echo -ne 'some random\nbytes' | xxd -plain | tr -d '\n' | sed 's/\(..\)/%\1/g'

xxd позаботится о том, чтобы ввод обрабатывался как байты, а не как символы.

редактирование:

xxd поставляется с пакетом vim-common в Debian, и я просто находился в системе, где он не был установлен, и я не хотел его устанавливать. Альтернативой является использование hexdump из пакета bsdmainutils в Debian. Согласно следующему графику bsdmainutils и vim-common должны иметь примерно равную вероятность установки:

http://qa.debian.org/popcon-png.php?packages=vim-common%2Cbsdmainutils&show_installed=1&want_legend=1&want_ticks=1

но тем не менее здесь версия, которая использует hexdump вместо xxd и позволяет избежать вызова tr:

echo -ne 'some random\nbytes' | hexdump -v -e '/1 "%02x"' | sed 's/\(..\)/%\1/g'
48 голосов
/ 29 мая 2012

Один из вариантов, может быть некрасивым, но простым:

urlencode() {
    local data
    if [[ $# != 1 ]]; then
        echo "Usage: $0 string-to-urlencode"
        return 1
    fi
    data="$(curl -s -o /dev/null -w %{url_effective} --get --data-urlencode "$1" "")"
    if [[ $? != 3 ]]; then
        echo "Unexpected error" 1>&2
        return 2
    fi
    echo "${data##/?}"
    return 0
}

Вот, например, однострочная версия (как предложено Bruno ):

date | curl -Gso /dev/null -w %{url_effective} --data-urlencode @- "" | cut -c 3-
44 голосов
/ 10 февраля 2010

Я нахожу это более читабельным в python:

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

тройка 'гарантирует, что одинарные кавычки в значении не повредят. urllib находится в стандартной библиотеке. Это работает, например, для этого сумасшедшего (реального мира) URL:

"http://www.rai.it/dl/audio/" "1264165523944Ho servito il re d'Inghilterra - Puntata 7
28 голосов
/ 22 декабря 2015

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

jq -sRr @uri

-R (--raw-input) обрабатывает входные строки как строки, а не анализирует их как JSON, а -sR (--slurp --raw-input) считывает ввод в одну строку. -r (--raw-output) выводит содержимое строк вместо строковых литералов JSON.

Если ввод не содержит перевода строки (или вы не хотите экранировать их как %0A), вы можете использовать просто jq -Rr @uri без опции -s.

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

xxd -p|tr -d \\n|sed 's/../%&/g'
28 голосов
/ 10 ноября 2009

Я нашел следующий фрагмент кода полезным, чтобы вставить его в цепочку вызовов программы, где URI :: Escape может быть не установлен:

perl -p -e 's/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg'

( источник )

19 голосов
/ 25 февраля 2011

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

Вот пример:

curl -v --get --data-urlencode "access_token=$(cat .fb_access_token)" https://graph.facebook.com/me/feed
14 голосов
/ 01 декабря 2008

Прямая ссылка на версию awk: http://www.shelldorado.com/scripts/cmds/urlencode
Я использовал его в течение многих лет, и он работает как шарм

:
##########################################################################
# Title      :  urlencode - encode URL data
# Author     :  Heiner Steven (heiner.steven@odn.de)
# Date       :  2000-03-15
# Requires   :  awk
# Categories :  File Conversion, WWW, CGI
# SCCS-Id.   :  @(#) urlencode  1.4 06/10/29
##########################################################################
# Description
#   Encode data according to
#       RFC 1738: "Uniform Resource Locators (URL)" and
#       RFC 1866: "Hypertext Markup Language - 2.0" (HTML)
#
#   This encoding is used i.e. for the MIME type
#   "application/x-www-form-urlencoded"
#
# Notes
#    o  The default behaviour is not to encode the line endings. This
#   may not be what was intended, because the result will be
#   multiple lines of output (which cannot be used in an URL or a
#   HTTP "POST" request). If the desired output should be one
#   line, use the "-l" option.
#
#    o  The "-l" option assumes, that the end-of-line is denoted by
#   the character LF (ASCII 10). This is not true for Windows or
#   Mac systems, where the end of a line is denoted by the two
#   characters CR LF (ASCII 13 10).
#   We use this for symmetry; data processed in the following way:
#       cat | urlencode -l | urldecode -l
#   should (and will) result in the original data
#
#    o  Large lines (or binary files) will break many AWK
#       implementations. If you get the message
#       awk: record `...' too long
#        record number xxx
#   consider using GNU AWK (gawk).
#
#    o  urlencode will always terminate it's output with an EOL
#       character
#
# Thanks to Stefan Brozinski for pointing out a bug related to non-standard
# locales.
#
# See also
#   urldecode
##########################################################################

PN=`basename "$0"`          # Program name
VER='1.4'

: ${AWK=awk}

Usage () {
    echo >&2 "$PN - encode URL data, $VER
usage: $PN [-l] [file ...]
    -l:  encode line endings (result will be one line of output)

The default is to encode each input line on its own."
    exit 1
}

Msg () {
    for MsgLine
    do echo "$PN: $MsgLine" >&2
    done
}

Fatal () { Msg "$@"; exit 1; }

set -- `getopt hl "$@" 2>/dev/null` || Usage
[ $# -lt 1 ] && Usage           # "getopt" detected an error

EncodeEOL=no
while [ $# -gt 0 ]
do
    case "$1" in
        -l) EncodeEOL=yes;;
    --) shift; break;;
    -h) Usage;;
    -*) Usage;;
    *)  break;;         # First file name
    esac
    shift
done

LANG=C  export LANG
$AWK '
    BEGIN {
    # We assume an awk implementation that is just plain dumb.
    # We will convert an character to its ASCII value with the
    # table ord[], and produce two-digit hexadecimal output
    # without the printf("%02X") feature.

    EOL = "%0A"     # "end of line" string (encoded)
    split ("1 2 3 4 5 6 7 8 9 A B C D E F", hextab, " ")
    hextab [0] = 0
    for ( i=1; i<=255; ++i ) ord [ sprintf ("%c", i) "" ] = i + 0
    if ("'"$EncodeEOL"'" == "yes") EncodeEOL = 1; else EncodeEOL = 0
    }
    {
    encoded = ""
    for ( i=1; i<=length ($0); ++i ) {
        c = substr ($0, i, 1)
        if ( c ~ /[a-zA-Z0-9.-]/ ) {
        encoded = encoded c     # safe character
        } else if ( c == " " ) {
        encoded = encoded "+"   # special handling
        } else {
        # unsafe character, encode it as a two-digit hex-number
        lo = ord [c] % 16
        hi = int (ord [c] / 16);
        encoded = encoded "%" hextab [hi] hextab [lo]
        }
    }
    if ( EncodeEOL ) {
        printf ("%s", encoded EOL)
    } else {
        print encoded
    }
    }
    END {
        #if ( EncodeEOL ) print ""
    }
' "$@"
...