Como urlencode dados para o comando curl?

Eu estou tentando escrever um script bash para testes que leva um parâmetro e envia através de curl para o site. Eu preciso url codificar o valor para se certificar de que os caracteres especiais são processados ​​corretamente. Qual é a melhor maneira de fazer isso?

Aqui está o meu script básico até agora:

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

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

Isso lança dados, semelhantes às outras opções --data , com a exceção de que isso executa a codificação de URL. Para ser compatível com CGI, a parte deve começar com um nome seguido por um separador e uma especificação de conteúdo.

Exemplo de uso:

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

Veja a man page para mais informações.

Isso requer o curl 7.18.0 ou mais recente (lançado em janeiro de 2008) . Use curl -V para verificar qual versão você possui.

Aqui está a pura resposta BASH.

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

Você pode usá-lo de duas maneiras:

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

[editado]

Aqui está a function rawurldecode () correspondente, que - com toda a modéstia - é incrível.

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

Com o conjunto correspondente, podemos agora realizar alguns testes simples:

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

E se você realmente sente que precisa de uma ferramenta externa (bem, ela vai muito mais rápido, e pode fazer arquivos binários e tal ...) Eu achei isso no meu roteador OpenWRT ...

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

Em que url_escape.sed era um arquivo que continha estas regras:

 # 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 

Use o módulo URI::Escape do Perl e a function uri_escape na segunda linha do seu script bash:

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

Editar: corrigir problemas de citação, como sugerido por Chris Johnsen nos comentários. Obrigado!

por uma questão de completude, muitas soluções que usam sed ou awk traduzem apenas um conjunto especial de caracteres e, portanto, são bastante grandes em tamanho de código e também não traduzem outros caracteres especiais que devem ser codificados.

Uma forma segura de urlencode seria simplesmente codificar cada byte – mesmo aqueles que seriam permitidos.

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

xxd está tomando cuidado aqui que a input é tratada como bytes e não caracteres.

editar:

O xxd vem com o pacote vim-common no Debian e eu estava em um sistema onde ele não estava instalado e eu não queria instalá-lo. O altornative é usar o hexdump do pacote bsdmainutils no Debian. De acordo com o gráfico a seguir, bsdmainutils e vim-common devem ter uma probabilidade quase igual de serem instalados:

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

mas mesmo assim aqui uma versão que usa hexdump invés de xxd e permite evitar a chamada tr :

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

Eu acho mais legível em python:

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

o triplo garante que as aspas simples em valor não prejudiquem. O urllib está na biblioteca padrão. Ele funciona por exemplo para este URL louco (mundo real):

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

Uma das variantes, pode ser feia, mas simples:

 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 } 

Aqui está a versão de uma linha, por exemplo (como sugerido por Bruno ):

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

Eu encontrei o seguinte trecho útil para colocá-lo em uma cadeia de chamadas de programa, onde URI :: Escape pode não ser instalado:

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

( fonte )

Se você deseja executar o pedido GET e usar o curl puro, apenas adicione a solução @ Jacob.

Aqui está um exemplo:

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

Outra opção é usar o jq :

 jq -s -R -r @uri 

-s ( --slurp ) lê as linhas de input em um array e -s -R ( --slurp --raw-input ) lê a input em uma única string. -r ( --raw-output ) gera o conteúdo de strings em vez de literais de strings JSON.

Ou este percentual codifica todos os bytes:

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

Link direto para a versão do awk: http://www.shelldorado.com/scripts/cmds/urlencode
Eu usei por anos e funciona como um 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 "" } ' "$@" 

Este pode ser o melhor:

 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') 

Isso codificará a string dentro de $ 1 e a saída em $ url. embora você não precise colocá-lo em um var se quiser. BTW não incluiu o sed para tab pensei que iria transformá-lo em espaços

Para aqueles que estão procurando por uma solução que não precisa de perl, aqui está uma que só precisa de hexdump e 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 '` } 

Costurados juntos a partir de um par de lugares através da rede e algumas tentativas e erros locais. Isso funciona muito bem!

Usando php de um 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

O uni2ascii é muito útil:

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

Se você não quer depender do Perl, você também pode usar o sed. É um pouco confuso, pois cada personagem tem que ser escapado individualmente. Faça um arquivo com o seguinte conteúdo e chame-o 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 usá-lo faça o seguinte.

 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" 

Isso dividirá a string em uma parte que precisa de codificação, e a parte que está bem, codifica a parte que precisa dela e, em seguida, volta a juntar os pontos.

Você pode colocar isso em um script sh por conveniência, talvez ele tenha um parâmetro para codificar, coloque em seu caminho e então você pode simplesmente chamar:

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

fonte

Você pode emular o encodeURIComponent do javascript em perl. Aqui está o comando:

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

Você poderia definir isso como um alias bash em .bash_profile :

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

Agora você pode canalizar para o encodeURIComponent :

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

Opção PHP simples:

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

Outra abordagem php:

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

Aqui está a versão do nó:

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

A questão é sobre fazer isso no bash e não há necessidade de python ou perl, pois há de fato um único comando que faz exatamente o que você quer – “urlencode”.

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

Isso também é muito melhor, pois a resposta perl acima, por exemplo, não codifica todos os caracteres corretamente. Experimente com o longo traço do Word e você obtém a codificação errada.

Note que você precisa de “gridsite-clients” instalados para fornecer este comando.

Ruby, pela perfeição

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

Aqui está uma solução Bash que não invoca nenhum programa 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" } 

Aqui está uma function POSIX para fazer isso:

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

Exemplo:

 value=$(encodeURIComponent "$2") 

Fonte

Aqui está uma conversão de uma linha usando Lua, similar à resposta de blueyed, exceto com todos os RFC 3986 Unreserved Characters não codificados (como esta resposta ):

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

Além disso, você pode precisar assegurar que as novas linhas na sua string sejam convertidas de LF para CRLF, nesse caso você pode inserir um gsub("\r?\n", "\r\n") na cadeia antes da porcentagem codificação.

Aqui está uma variante que, no estilo de aplicação não padronizado / x-www-form-urlencoded , faz essa normalização de nova linha, assim como codifica espaços como ‘+’ em vez de ‘% 20’ (o que provavelmente poderia ser adicionado ao Snippet Perl usando uma 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") 

Tendo o php instalado eu uso assim:

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

Esta é a versão ksh da resposta da orwellophile contendo as funções rawurlencode e rawurldecode (link: Como codificar urlencode dados para o comando curl? ). Eu não tenho representante suficiente para postar um comentário, daí o novo post ..

 #!/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++ 

Aqui está a minha versão para o shell do busybox ash para um sistema embarcado, eu originalmente adotei a variante da 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}" } 

O seguinte é baseado na resposta de Orwellophile, mas resolve o bug multibyte mencionado nos comentários, definindo LC_ALL = C (um truque de vte.sh). Eu escrevi na forma de function adequada PROMPT_COMMAND, porque é assim que eu uso.

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

O que analisar URLs melhor do que o javascript?

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