¿Cómo urlencode datos para el comando curl?

Estoy tratando de escribir un script bash para probar que toma un parámetro y lo envía a curl al sitio web. Necesito url codificar el valor para asegurarme de que los caracteres especiales se procesen correctamente. ¿Cuál es la mejor manera de hacer esto?

Aquí está mi script básico hasta ahora:

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

Use curl --data-urlencode ; del man curl :

Esto publica datos, similares a las otras opciones de --data , con la excepción de que esto realiza la encoding URL. Para ser compatible con CGI, la parte debe comenzar con un nombre seguido de un separador y una especificación de contenido.

Ejemplo de uso:

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

Vea la página de manual para más información.

Esto requiere curl 7.18.0 o más reciente (lanzado en enero de 2008) . Use curl -V para verificar qué versión tiene.

Aquí está la respuesta pura de BASH.

 rawurlencode() { local string="${1}" local strlen=${#string} local encoded="" local pos co for (( pos=0 ; pos 

Puedes usarlo de dos maneras:

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

[editado]

Aquí está la función matching rawurldecode (), que - con toda modestia - es increíble.

 # 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 } 

Con el conjunto correspondiente, ahora podemos realizar algunas pruebas simples:

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

Y si realmente siente que necesita una herramienta externa (bueno, irá mucho más rápido, y podría hacer archivos binarios y cosas así ...) Encontré esto en mi enrutador OpenWRT ...

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

Donde url_escape.sed era un archivo que contenía estas reglas:

 # 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 

Usa el módulo URI::Escape Perl y la función uri_escape en la segunda línea de tu script bash:

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

Editar: Soluciona los problemas de las citas, como lo sugirió Chris Johnsen en los comentarios. ¡Gracias!

en aras de la exhaustividad, muchas soluciones que usan sed o awk solo traducen un conjunto especial de caracteres y, por lo tanto, son bastante grandes por el tamaño del código y tampoco traducen otros caracteres especiales que deberían codificarse.

una forma segura de urlencode sería codificar cada byte individual, incluso aquellos que hubieran sido permitidos.

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

xxd se está ocupando aquí de que la entrada se maneje como bytes y no como caracteres.

editar:

xxd viene con el paquete vim-common en Debian y estaba solo en un sistema donde no estaba instalado y no quería instalarlo. El altornative es usar hexdump del paquete bsdmainutils en Debian. De acuerdo con el siguiente gráfico, bsdmainutils y vim-common deberían tener aproximadamente la misma probabilidad de ser instalados:

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

pero sin embargo aquí una versión que usa hexdump lugar de xxd y permite evitar la llamada tr :

 echo -ne 'some random\nbytes' | hexdump -v -e '/1 "%02x"' | sed 's/\(..\)/%\1/g' 

Lo encuentro más legible en Python:

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

el triple ‘asegura que las comillas simples en valor no dañarán. urllib está en la biblioteca estándar. Funciona por ejemplo para esta url loca (del mundo real):

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

Una de las variantes, puede ser fea, pero simple:

 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 } 

Aquí está la versión de una línea por ejemplo (como lo sugiere Bruno ):

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

He encontrado el siguiente fragmento útil para pegarlo en una cadena de llamadas de progtwig, donde URI :: Escape podría no estar instalado:

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

( fuente )

Si desea ejecutar la solicitud GET y usar curl puro, simplemente agregue – --get a la solución de @ Jacob.

Aquí hay un ejemplo:

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

Otra opción es usar jq :

 jq -s -R -r @uri 

-s ( --slurp ) lee las líneas de entrada en una matriz y -s -R ( --slurp --raw-input ) lee la entrada en una sola cadena. -r ( --raw-output ) genera los contenidos de cadenas en lugar de literales de cadena JSON.

O este porcentaje de encoding de todos los bytes:

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

Enlace directo a la versión de awk: http://www.shelldorado.com/scripts/cmds/urlencode
Lo utilicé durante años y funciona como un encanto

 : ########################################################################## # 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 ie 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 ABCDEF", 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 "" } ' "$@" 

Esta puede ser la mejor:

 after=$(echo -e "$before" | od -An -tx1 | tr ' ' % | xargs printf "%s") 
 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') 

esto codificará la cadena dentro de $ 1 y la generará en $ url. aunque no tienes que ponerlo en una var si lo deseas. Por cierto, no incluía el sed de tabulación, pensó que lo convertiría en espacios

Para aquellos de ustedes que buscan una solución que no necesita perl, aquí hay uno que solo necesita hexdump y 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 '` } 

Unidas desde un par de lugares a través de la red y algunas pruebas y errores locales. ¡Funciona genial!

Usando php desde un script de shell:

 value="http://www.google.com" encoded=$(php -r "echo rawurlencode('$value');") # encoded = "http%3A%2F%2Fwww.google.com" echo $(php -r "echo rawurldecode('$encoded');") # returns: "http://www.google.com" 
  1. http://www.php.net/manual/en/function.rawurlencode.php
  2. http://www.php.net/manual/en/function.rawurldecode.php

uni2ascii es muy útil:

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

Si no quieres depender de Perl, también puedes usar sed. Es un poco desordenado, ya que cada personaje tiene que escapar de forma individual. Crea un archivo con los siguientes contenidos y llámalo 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 

Para usarlo haz lo siguiente.

 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" 

Esto dividirá la cadena en una parte que necesita encoding, y la parte que está bien, codificará la parte que lo necesita y luego unirá las puntadas.

Puedes poner eso en un guión sh por conveniencia, tal vez tener que tomar un parámetro para codificar, ponerlo en tu camino y luego simplemente puedes llamar:

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

fuente

La pregunta es acerca de hacer esto en bash y no hay necesidad de Python o Perl, ya que de hecho hay un solo comando que hace exactamente lo que quieres: “urlencode”.

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

Esto también es mucho mejor, ya que la respuesta perl anterior, por ejemplo, no codifica todos los caracteres correctamente. Pruébalo con el largo scope que obtienes de Word y obtienes la encoding incorrecta.

Tenga en cuenta que necesita “clientes de gridsite” instalados para proporcionar este comando.

Puedes emular el encodeURIComponent de javascript en perl. Aquí está el comando:

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

Puede establecer esto como un alias bash en .bash_profile :

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

Ahora puede encodeURIComponent en encodeURIComponent :

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

Opción simple de PHP:

 echo 'part-that-needs-encoding' | php -R 'echo urlencode($argn);' 

Otro enfoque php:

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

Aquí está la versión del nodo:

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

Ruby, para completar

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

Aquí hay una solución Bash que no invoca ningún progtwig externo:

 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" } 

Aquí hay una función POSIX para hacer eso:

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

Ejemplo:

 value=$(encodeURIComponent "$2") 

Fuente

Aquí hay una conversión de una línea usando Lua, similar a la respuesta de blueyed excepto que todos los caracteres sin reserva de RFC 3986 quedan sin codificar (como esta respuesta ):

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

Además, es posible que deba asegurarse de que las líneas nuevas en su cadena se convierten de LF a CRLF, en cuyo caso puede insertar un gsub("\r?\n", "\r\n") en la cadena antes del porcentaje. encoding

Aquí hay una variante que, en el estilo no estándar de application / x-www-form-urlencoded , hace que la normalización de la nueva línea, así como los espacios de encoding como ‘+’ en lugar de ‘% 20’ (que probablemente podrían agregarse al Fragmento Perl usando una técnica similar).

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

Tener instalado php lo uso de esta manera:

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

Esta es la versión ksh de la respuesta de orwellophile que contiene las funciones rawurlencode y rawurldecode (enlace: ¿Cómo urlencode data para el comando curl? ). No tengo suficientes representantes para publicar un comentario, de ahí la nueva publicación …

 #!/bin/ksh93 function rawurlencode { typeset string="${1}" typeset strlen=${#string} typeset encoded="" for (( pos=0 ; pos C%2b%2b print $(rawurldecode "C%2b%2b") # --> C++ 

Aquí está mi versión para busybank ash shell para un sistema integrado, originalmente adopté la variante de 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  local url_encoded="${1//+/ }" printf '%b' "${url_encoded//%/\\x}" } 

Lo siguiente se basa en la respuesta de Orwellophile, pero resuelve el error multibyte mencionado en los comentarios configurando LC_ALL = C (un truco de vte.sh). Lo escribí en forma de función adecuada PROMPT_COMMAND, porque así es como lo uso.

 print_path_url() { local LC_ALL=C local string="$PWD" local strlen=${#string} local encoded="" local pos co for (( pos=0 ; pos 

¿Qué analizaría las URL mejor que javascript?

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