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


319

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

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

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

Смотрите также: Как декодировать строку в кодировке URL в оболочке? для не скручиваемых решений.
Кенорб

Ответы:


396

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

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

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

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

Смотрите man-страницу для получения дополнительной информации.

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

Вы также можете закодировать строку запроса :

curl -G \
    --data-urlencode "p1=value 1" \
    --data-urlencode "p2=value 2" \
    http://example.com
    # http://example.com?p1=value%201&p2=value%202

5
Кажется, работает только для HTTP POST. Документация здесь: curl.haxx.se/docs/manpage.html#--data-urlencode
Стэн Джеймс

82
@StanJames Если вы используете его так, curl также может выполнять кодирование для запроса GET. curl -G --data-urlencode "blah=df ssdf sdf" --data-urlencode "blah2=dfsdf sdfsd " http://whatever.com/whatever
kberg

13
@kberg на самом деле, это будет работать только для данных запроса. curl добавит '?' с последующим параметром urlencoded. Если вы хотите urlencode некоторого постфикса url (например, CouchDB GET для некоторого идентификатора документа), то --data-urlencode не будет работать.
Bokeh

1
Не работает curl --data-urlencode "description=![image]($url)" www.example.com. Есть идеи почему? `
Хуршид Алам

1
@NadavB Ускользающий "
BlackJack

179

Вот чистый ответ 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

4
К сожалению, этот скрипт завершается ошибкой на некоторых символах, таких как 'é' и '½', выводя 'e% FFFFFFFFFFFFFFCC' и '% FFFFFFFFFFFFFFC2' соответственно (я полагаю, b / c цикла для каждого символа).
Математика

1
Это не работает для меня в Bash 4.3.11 (1). Строка Jogging «à l'Hèze»генерирует Jogging%20%abà%20l%27Hèze%bbчто не может быть decodeURIComponent
передано

2
В этом первом блоке кода что означает последний параметр для printf? То есть, почему это двойная кавычка, одинарная кавычка, знак доллара, буква с, двойная кавычка? Одинаковая кавычка делает?
Колин Фрейзер

1
@dmcontador - это простой скромный bash-скрипт, в котором нет концепции многобайтовых символов или юникода. Когда он видит такой символ, как ń ( \u0144), он наивно выдает% 144, ╡ ( \u2561) будет выводить как% 2561. Правильные ответы с кодировкой rawurlended для них будут% C5% 84% 0A и% E2% 95% A1 соответственно.
Орвеллофил

1
@ColinFraizer одиночная кавычка служит для преобразования следующего символа в его числовое значение. ссылка pubs.opengroup.org/onlinepubs/9699919799/utilities/…
Сэм,

94

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

...

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

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


2
URI :: Escape может быть не установлен, проверьте мой ответ в этом случае.
синеватый

Я исправил это (use echo, pipe и <>), и теперь он работает, даже когда $ 2 содержит апостроф или двойные кавычки. Спасибо!
Дубек

9
Вы также покончили с собой echo:value="$(perl -MURI::Escape -e 'print uri_escape($ARGV[0]);' "$2")"
Крис Джонсен

1
Версия Криса Джонсена лучше. У меня было $ {True} в моем тестовом выражении, и использование этого через echo вызвало расширение переменной uri_escape / Perl.
mm2001

1
@ jrw32982 да, оглядываясь назад, есть другой язык, на котором можно выполнить эту задачу - это хорошо. Если бы я мог, я бы забрал свое пониженное голосование, но, увы, оно сейчас заблокировано.
thecoshman

69

Другим вариантом является использование jq(в качестве фильтра):

jq -sRr @uri

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

Если ввод не является выводом другой команды, вы можете сохранить его в jqстроковой переменной:

jq -nr --arg v "my shell string" '$v|@uri'

-n( --null-input) не читает ввод и --arg name valueсохраняет valueпеременную nameкак строку. В фильтре $name(в одинарных кавычках, чтобы избежать расширения оболочкой) ссылается на переменнуюname .

Обернутый как функция Bash, это становится:

function uriencode { jq -nr --arg v "$1" '$v|@uri'; }

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

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

3
<3 это ... должно быть лучшим и приемлемым IMO (да, если вы можете сказать, curlчтобы кодировать это работает, и если bash имеет встроенную функцию, которая была бы приемлемой - но jqкажется подходящей, хотя я далек от достижения уровня комфорта с этот инструмент)
Nhed

5
для всех, кто интересуется тем же, что и я: @uriне какая-то переменная, а литеральный фильтр jq, используемый для форматирования строк и экранирования; подробности см. в руководстве jq (извините, прямой ссылки нет, нужно искать @uriна странице ...)
ssc

версия xxd - это именно то, что я искал. Даже если он немного грязный, он короткий и не имеет никаких зависимостей
Rian Sanderson

1
Пример использования jq для кодирования url:printf "http://localhost:8082/" | jq -sRr '@uri'
Ашутош Джиндал

67

для полноты картины многие решения, использующие 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'

1
xxd -plainдолжно произойти ПОСЛЕ tr -d '\n'!
QDII

3
@qdii почему? это не только сделало бы невозможным urlencode новых строк, но также неправильно вставило бы новые строки, созданные xxd, в вывод.
Йош

1
@josch. Это просто неправильно. Во-первых, любые \nсимволы будут переведены xxd -plainв 0a. Не верьте мне на слово, попробуйте сами: echo -n -e '\n' | xxd -plainэто доказывает, что вы tr -d '\n'здесь бесполезны, так как \nпосле xxd -plain Second их не может быть , echo foobarдобавляется собственный \nсимвол в конец строки символов, поэтому xxd -plainон подается не так, foobarкак ожидалось, а с foobar\n. затем xxd -plain переводит его в некоторую символьную строку, которая заканчивается 0a, что делает его непригодным для пользователя. Вы могли бы добавить -nк echoее решить.
QDII

6
@qdii действительно -n отсутствует для эха, но xxdвызов принадлежит перед tr -dвызовом. Он принадлежит там так, что любой foobarперевод строки в xxd. tr -dПосле xxdвызова для удаления символов новой строки, XXD производит. Кажется, у вас никогда не было достаточно длинного foobar, чтобы он xxdсоздавал переводы строк, но для длинных входов это будет. Так что tr -dэто необходимо. В отличие от вашего предположения tr -dбыло НЕ убирать переводы строк с ввода, а с xxdвывода. Я хочу сохранить переводы строк во входных данных. Ваш единственный верный аргумент в том, что эхо добавляет ненужный перевод строки.
Йош

1
@qdii и не обижайся - я просто думаю, что ты не прав, за исключением того, echo -nчего я действительно пропустил
josch

62

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

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
}

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

date | curl -Gso /dev/null -w %{url_effective} --data-urlencode @- "" | cut -c 3-

# If you experience the trailing %0A, use
date | curl -Gso /dev/null -w %{url_effective} --data-urlencode @- "" | sed -E 's/..(.*).../\1/'

1
Я думаю, что это очень умный способ повторно использовать кодировку URL cURL.
solidsnack

13
Это абсолютно великолепно! Мне бы очень хотелось, чтобы вы оставили это в одной строке, чтобы люди могли видеть, насколько все просто на самом деле. Чтобы URL кодировал результат dateкоманды… date | curl -Gso /dev/null -w %{url_effective} --data-urlencode @- "" | cut -c 3-(Вы должны cutотключить первые 2 символа, потому что выходные данные curl - это технически относительный URL со строкой запроса.)
Бруно Броноски,

2
@BrunoBronosky Ваш однострочный вариант хорош, но, похоже, добавляет «% 0A» в конец кодировки. Пользователи остерегаются. Версия функции, похоже, не имеет этой проблемы.
левигрокер

7
Чтобы избежать %0Aв конце, используйте printfвместо echo.
Кенорб

2
один лайнер фантастический
Стивен Блюм

49

Я нахожу это более читабельным в 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

2
У меня были некоторые проблемы с кавычками и специальными символами с тройным кавычком, это работало практически для всего: encoded_value = "$ (echo -n" $ {data} "| python -c" import urllib; import sys; sys.stdout. write (urllib.quote (sys.stdin.read ())) ")";
Хватит клеветать на Монику Челлио

Версия Python 3 будет encoded_value=$(python3 -c "import urllib.parse; print (urllib.parse.quote('''$value'''))").
Creshal

1
python -c 'import urllib, sys; sys.stdout.writelines(urllib.quote_plus(l, safe="/\n") for l in sys.stdin)'почти не имеет проблем с цитированием и должен быть эффективным с точки зрения памяти / скорости (не проверял, за исключением косоглазия)
Alois Mahdal

2
Было бы гораздо безопаснее сослаться на строку, sys.argvа не подставлять ее $valueв строку, которая затем анализируется как код. Что если valueсодержится ''' + __import__("os").system("rm -rf ~") + '''?
Чарльз Даффи

2
python -c "import urllib;print urllib.quote(raw_input())" <<< "$data"
Rockallite

30

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

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

( источник )


4
работал на меня. Я изменил его на perl-lpe ... (буква ell). Это убрало завершающий перевод строки, который мне был нужен для моих целей.
JohnnyLambada

2
К вашему сведению, чтобы сделать обратное, используйте perl -pe 's/\%(\w\w)/chr hex $1/ge'(источник: unix.stackexchange.com/questions/159253/… )
Шридхар Сарнобат

2
В зависимости от того, какие конкретно символы необходимо кодировать, вы можете упростить это, чтобы perl -pe 's/(\W)/sprintf("%%%02X", ord($1))/ge'разрешить буквы, цифры и подчеркивания, но кодировать все остальное.
Робру

23

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

Вот пример:

curl -v --get --data-urlencode "access_token=$(cat .fb_access_token)" https://graph.facebook.com/me/feed

15

Прямая ссылка на версию 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 ""
    }
' "$@"

Есть ли простой вариант получить кодировку UTF-8 вместо ASCII?
avgvstvs

15

Это может быть лучшим:

after=$(echo -e "$before" | od -An -tx1 | tr ' ' % | xargs printf "%s")

Это работает для меня с двумя дополнениями: 1. замените -e на -n, чтобы избежать добавления новой строки в конец аргумента, и 2. добавьте «%%» в строку printf, чтобы поместить% перед каждой парой шестнадцатеричные цифры.
Роб Фаген

работает после добавления $ вперед скобки after=$(echo -e ...
Роман Ррн Нестеров

1
Пожалуйста, объясните, как это работает. Команда odне является общей.
Марк Стосберг

Это не работает с OS X, odпотому что он использует другой формат вывода, чем GNU od. Например, printf aa|od -An -tx1 -v|tr \ -печатает -----------61--61--------------------------------------------------------с OS X odи -61-61с GNU od. Вы можете использовать od -An -tx1 -v|sed 's/ */ /g;s/ *$//'|tr \ %|tr -d \\nс OS X odили GNU od. xxd -p|sed 's/../%&/g'|tr -d \\nделает то же самое, хотя xxdне в POSIX, но odесть.
nisetama

2
Хотя это может сработать, оно ускользает от каждого персонажа
Чарли

11

Вот решение Bash, которое не вызывает никаких внешних программ:

uriencode() {
  s="${1//'%'/%25}"
  s="${s//' '/%20}"
  s="${s//'"'/%22}"
  s="${s//'#'/%23}"
  s="${s//'$'/%24}"
  s="${s//'&'/%26}"
  s="${s//'+'/%2B}"
  s="${s//','/%2C}"
  s="${s//'/'/%2F}"
  s="${s//':'/%3A}"
  s="${s//';'/%3B}"
  s="${s//'='/%3D}"
  s="${s//'?'/%3F}"
  s="${s//'@'/%40}"
  s="${s//'['/%5B}"
  s="${s//']'/%5D}"
  printf %s "$s"
}

4
Это ведет себя по-разному между версиями Bash. На RHEL 6.9 bash равен 4.1.2 и включает одинарные кавычки. В то время как Debian 9 и bash 4.4.12 хороши с одинарными кавычками. Для меня удаление одинарных кавычек заставило его работать на обоих. s = "$ {s // ',' /% 2C}"
muni764

1
Я обновил ответ, чтобы отразить ваши выводы, @ muni764.
Дэвид Чамберс

Просто предупреждение ... это не будет кодировать такие вещи, как персонажá
diogovk

10
url=$(echo "$1" | sed -e 's/%/%25/g' -e 's/ /%20/g' -e 's/!/%21/g' -e 's/"/%22/g' -e 's/#/%23/g' -e 's/\$/%24/g' -e 's/\&/%26/g' -e 's/'\''/%27/g' -e 's/(/%28/g' -e 's/)/%29/g' -e 's/\*/%2a/g' -e 's/+/%2b/g' -e 's/,/%2c/g' -e 's/-/%2d/g' -e 's/\./%2e/g' -e 's/\//%2f/g' -e 's/:/%3a/g' -e 's/;/%3b/g' -e 's//%3e/g' -e 's/?/%3f/g' -e 's/@/%40/g' -e 's/\[/%5b/g' -e 's/\\/%5c/g' -e 's/\]/%5d/g' -e 's/\^/%5e/g' -e 's/_/%5f/g' -e 's/`/%60/g' -e 's/{/%7b/g' -e 's/|/%7c/g' -e 's/}/%7d/g' -e 's/~/%7e/g')

это закодирует строку внутри $ 1 и выведет ее в $ url. хотя вам не нужно помещать его в переменную, если хотите. Кстати, не включает sed для табуляции, думал, что это превратит его в пробелы


5
У меня такое ощущение, что это не рекомендуемый способ сделать это.
Коди Грей

2
объясните, пожалуйста, ваши чувства ... потому что я, что я изложил, работает, и я использовал это в нескольких сценариях, поэтому я знаю, что это работает для всех символов, которые я перечислил. поэтому, пожалуйста, объясните, почему кто-то не будет использовать мой код и использовать perl, так как заголовок «URLEncode из сценария bash», а не сценарий perl.
manoflinux

иногда не нужно жемчужного раствора, так что это может пригодиться
Yuval Rimar

3
Это не рекомендуемый способ сделать это, потому что черный список - это плохая практика, и в любом случае это unicode.
Ekevoo

Это было наиболее дружественное решение, совместимое с cat file.txt
mrwaim


7

Для тех из вас, кто ищет решение, не требующее Perl, вот то, которое требует только hexdump и awk:

url_encode() {
 [ $# -lt 1 ] && { return; }

 encodedurl="$1";

 # make sure hexdump exists, if not, just give back the url
 [ ! -x "/usr/bin/hexdump" ] && { return; }

 encodedurl=`
   echo $encodedurl | hexdump -v -e '1/1 "%02x\t"' -e '1/1 "%_c\n"' |
   LANG=C awk '
     $1 == "20"                    { printf("%s",   "+"); next } # space becomes plus
     $1 ~  /0[adAD]/               {                      next } # strip newlines
     $2 ~  /^[a-zA-Z0-9.*()\/-]$/  { printf("%s",   $2);  next } # pass through what we can
                                   { printf("%%%s", $1)        } # take hex value of everything else
   '`
}

Сшитые из нескольких мест в сети и некоторых локальных проб и ошибок. Работает отлично!


7

uni2ascii очень удобен:

$ echo -ne '你好世界' | uni2ascii -aJ
%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C

2
Это не работает для символов внутри диапазона ASCII, которые нуждаются в кавычках, как %и пробел (последнее можно исправить с помощью -sфлага)
Boldewyn

7

Если вы не хотите зависеть от Perl, вы также можете использовать sed. Это немного грязно, так как каждый персонаж должен избегаться индивидуально. Создайте файл со следующим содержимым и назовите егоurlencode.sed

s/%/%25/g
s/ /%20/g
s/ /%09/g
s/!/%21/g
s/"/%22/g
s/#/%23/g
s/\$/%24/g
s/\&/%26/g
s/'\''/%27/g
s/(/%28/g
s/)/%29/g
s/\*/%2a/g
s/+/%2b/g
s/,/%2c/g
s/-/%2d/g
s/\./%2e/g
s/\//%2f/g
s/:/%3a/g
s/;/%3b/g
s//%3e/g
s/?/%3f/g
s/@/%40/g
s/\[/%5b/g
s/\\/%5c/g
s/\]/%5d/g
s/\^/%5e/g
s/_/%5f/g
s/`/%60/g
s/{/%7b/g
s/|/%7c/g
s/}/%7d/g
s/~/%7e/g
s/      /%09/g

Чтобы использовать это, сделайте следующее.

STR1=$(echo "https://www.example.com/change&$ ^this to?%checkthe@-functionality" | cut -d\? -f1)
STR2=$(echo "https://www.example.com/change&$ ^this to?%checkthe@-functionality" | cut -d\? -f2)
OUT2=$(echo "$STR2" | sed -f urlencode.sed)
echo "$STR1?$OUT2"

Это разделит строку на часть, которая нуждается в кодировании, и часть, которая в порядке, закодирует часть, которая нуждается в этом, затем сшит обратно.

Вы можете поместить это в сценарий sh для удобства, возможно, он будет принимать параметр для кодирования, поместить его на ваш путь, а затем вы можете просто вызвать:

urlencode https://www.exxample.com?isThisFun=HellNo

источник


7

Вы можете эмулировать JavaScript encodeURIComponentв Perl. Вот команда:

perl -pe 's/([^a-zA-Z0-9_.!~*()'\''-])/sprintf("%%%02X", ord($1))/ge'

Вы можете установить это как псевдоним bash в .bash_profile:

alias encodeURIComponent='perl -pe '\''s/([^a-zA-Z0-9_.!~*()'\''\'\'''\''-])/sprintf("%%%02X",ord($1))/ge'\'

Теперь вы можете перейти на encodeURIComponent:

$ echo -n 'hèllo wôrld!' | encodeURIComponent
h%C3%A8llo%20w%C3%B4rld!

6

Вот версия узла:

uriencode() {
  node -p "encodeURIComponent('${1//\'/\\\'}')"
}

1
Не нарушится ли это, если в строке есть другие символы, которые недопустимы между одинарными кавычками, например, обратная косая черта или перевод строки?
Стюарт П. Бентли

Хорошая точка зрения. Если мы хотим избежать побега всех проблемных персонажей в Bash, мы могли бы также выполнить замены напрямую и nodeвообще избежать . Я разместил решение только для Bash. :)
Давидчамберс

1
Этот вариант, найденный в другом месте на странице, позволяет избежать проблемы цитирования, считывая значение из STDIN:node -p 'encodeURIComponent(require("fs").readFileSync(0))'
Марк Стосберг

6

Вопрос в том, чтобы сделать это в bash, и нет необходимости в python или perl, так как на самом деле есть одна команда, которая делает именно то, что вы хотите - «urlencode».

value=$(urlencode "${2}")

Это также намного лучше, так как приведенный выше ответ на Perl, например, не правильно кодирует все символы. Попробуйте это с длинной чертой, которую вы получаете от Word, и вы получите неправильную кодировку.

Обратите внимание, что для обеспечения этой команды вам необходимо установить «gridsite-clients».


1
Моя версия bash (GNU 3.2) не имеет urlencode. Какую версию ты используешь?
Шридхар Сарнобат

1
У меня 4.3.42, но команда urlencode предоставляется "gridsite-clients". Попробуйте установить это, и все будет в порядке.
Дилан

5
Таким образом, ваш ответ не лучше, чем любой, требующий установки других вещей (python, perl, lua,…)
Cyrille Pontvieux

За исключением того, что требуется только установка одной утилиты вместо всего языка (и библиотек), плюс очень просто и понятно, что она делает.
Дилан

Ссылка сначала на страницу пакета / проекта с этой командой была бы полезна.
Дорон Бехар


4

Рубин, для полноты

value="$(ruby -r cgi -e 'puts CGI.escape(ARGV[0])' "$2")"

4

Другой подход php:

echo "encode me" | php -r "echo urlencode(file_get_contents('php://stdin'));"

2
echoдобавит символ новой строки (hex 0xa). Чтобы остановить это, используйте echo -n.
Мэтью Холл

3

Вот моя версия для busybox ash shell для встроенной системы, я изначально принял вариант Orwellophile:

urlencode()
{
    local S="${1}"
    local encoded=""
    local ch
    local o
    for i in $(seq 0 $((${#S} - 1)) )
    do
        ch=${S:$i:1}
        case "${ch}" in
            [-_.~a-zA-Z0-9]) 
                o="${ch}"
                ;;
            *) 
                o=$(printf '%%%02x' "'$ch")                
                ;;
        esac
        encoded="${encoded}${o}"
    done
    echo ${encoded}
}

urldecode() 
{
    # urldecode <string>
    local url_encoded="${1//+/ }"
    printf '%b' "${url_encoded//%/\\x}"
}

2

Вот функция POSIX, чтобы сделать это:

encodeURIComponent() {
  awk 'BEGIN {while (y++ < 125) z[sprintf("%c", y)] = y
  while (y = substr(ARGV[1], ++j, 1))
  q = y ~ /[[:alnum:]_.!~*\47()-]/ ? q y : q sprintf("%%%02X", z[y])
  print q}' "$1"
}

Пример:

value=$(encodeURIComponent "$2")

Источник


2

Вот преобразование в одну строку с использованием Lua, похожее на ответ blueyed, за исключением того, что все незарезервированные символы RFC 3986 остались незакодированными (как этот ответ ):

url=$(echo 'print((arg[1]:gsub("([^%w%-%.%_%~])",function(c)return("%%%02X"):format(c:byte())end)))' | lua - "$1")

Кроме того, вам может потребоваться убедиться, что переводы строки в вашей строке преобразуются из LF в CRLF, и в этом случае вы можете вставить gsub("\r?\n", "\r\n")в цепочку перед кодированием процента.

Вот вариант, который в нестандартном стиле application / x-www-form-urlencoded выполняет нормализацию новой строки, а также кодирует пробелы как «+» вместо «% 20» (что, вероятно, можно добавить к Фрагмент Perl с использованием аналогичной техники).

url=$(echo 'print((arg[1]:gsub("\r?\n", "\r\n"):gsub("([^%w%-%.%_%~ ]))",function(c)return("%%%02X"):format(c:byte())end):gsub(" ","+"))' | lua - "$1")

1

После установки php я использую этот способ:

URL_ENCODED_DATA=`php -r "echo urlencode('$DATA');"`

1

Это ksh-версия ответа orwellophile, содержащая функции rawurlencode и rawurldecode (ссылка: Как urlencode данных для команды curl? ). У меня недостаточно репутации, чтобы оставить комментарий, отсюда и новый пост ..

#!/bin/ksh93

function rawurlencode
{
    typeset string="${1}"
    typeset strlen=${#string}
    typeset encoded=""

    for (( pos=0 ; pos<strlen ; pos++ )); do
        c=${string:$pos:1}
        case "$c" in
            [-_.~a-zA-Z0-9] ) o="${c}" ;;
            * )               o=$(printf '%%%02x' "'$c")
        esac
        encoded+="${o}"
    done
    print "${encoded}"
}

function rawurldecode
{
    printf $(printf '%b' "${1//%/\\x}")
}

print $(rawurlencode "C++")     # --> C%2b%2b
print $(rawurldecode "C%2b%2b") # --> C++

1

Что будет разбирать URL лучше, чем JavaScript?

node -p "encodeURIComponent('$url')"

Вне сферы действия вопроса. Не баш, не локон. Даже если я уверен, что работает очень хорошо, если узел доступен.
Сирил Понтьё

Почему это голосование с понижением, а не с ответами на python / perl? Кроме того, как это не отвечает на оригинальный вопрос «Как urlencode данных для команды curl?». Это может быть использовано из скрипта bash, а результат может быть передан команде curl.
Нестор

Я тоже проголосовал против остальных. Вопрос был в том, как сделать это в скрипте bash. Если используется другой язык, такой как node / js, python или perl, тогда нет необходимости использовать curl напрямую.
Сирил Понтвье

2
В то время как я не удосужился понизить голос, проблема с этой командой состоит в том, что она требует, чтобы данные были должным образом экранированы для использования в javascript. Как попробовать это с одинарными кавычками и некоторым безумием с обратной косой чертой. Если вы хотите использовать нод, вам лучше читать материал из stdin, напримерnode -p 'encodeURIComponent(require("fs").readFileSync(0))'
Michael Krelin - хакер

1
Будьте осторожны с решением @ MichaelKrelin-hacker, если вы передаете данные из STDIN, убедитесь, что не добавляете завершающий символ новой строки. Например, echo | ...неправильно, хотя echo -n | ...подавляет перевод строки.
Марк Стосберг

0

Следующее основано на ответе Орвеллофила, но решает многобайтовую ошибку, упомянутую в комментариях, путем установки LC_ALL = C (трюк из vte.sh). Я написал это в форме функции, подходящей PROMPT_COMMAND, потому что именно так я ее использую.

print_path_url() {
  local LC_ALL=C
  local string="$PWD"
  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
  printf "\033]7;file://%s%s\007" "${HOSTNAME:-}" "${encoded}"
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.