Làm thế nào để urlencode dữ liệu cho lệnh curl?


319

Tôi đang cố gắng viết một tập lệnh bash để kiểm tra lấy tham số và gửi nó thông qua curl đến trang web. Tôi cần mã hóa url giá trị để đảm bảo rằng các ký tự đặc biệt được xử lý đúng cách. Cách tốt nhất để làm việc này là gì?

Đây là kịch bản cơ bản của tôi cho đến nay:

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


Câu trả lời:


396

Sử dụng curl --data-urlencode; từ man curl:

Điều này đăng dữ liệu, tương tự như các --datatùy chọn khác ngoại trừ việc này thực hiện mã hóa URL. Để tuân thủ CGI, <data>phần này phải bắt đầu bằng một tên theo sau là dấu phân cách và đặc tả nội dung.

Ví dụ sử dụng:

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

Xem trang người đàn ông để biết thêm.

Điều này đòi hỏi curl 7.18.0 hoặc mới hơn (phát hành tháng 1 năm 2008) . Sử dụng curl -Vđể kiểm tra phiên bản bạn có.

Bạn cũng có thể mã hóa chuỗi truy vấn :

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
Có vẻ như chỉ hoạt động cho http POST. Tài liệu ở đây: curl.haxx.se/docs/manpage.html#--data-urlencode
Stan James

82
@StanJames Nếu bạn sử dụng nó như vậy thì curl cũng có thể thực hiện mã hóa cho yêu cầu GET. curl -G --data-urlencode "blah=df ssdf sdf" --data-urlencode "blah2=dfsdf sdfsd " http://whatever.com/whatever
kberg

13
@kberg thực sự, điều này sẽ chỉ hoạt động cho dữ liệu truy vấn. curl sẽ nối thêm một '?' theo sau là các thông số urlencoding. Nếu bạn muốn urlencode một số postfix url (chẳng hạn như CouchDB GET cho một số id tài liệu), thì '--data-urlencode' sẽ không hoạt động.
Bokeh

1
Không làm việc cho curl --data-urlencode "description=![image]($url)" www.example.com. Bất cứ ý tưởng tại sao? `
Khurshid Alam

1
@NadavB Thoát "khỏi‽
BlackJack

179

Đây là câu trả lời BASH thuần túy.

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
}

Bạn có thể sử dụng nó theo hai cách:

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

[đã chỉnh sửa]

Đây là hàm rawurldecode () phù hợp, với tất cả sự khiêm tốn - là tuyệt vời.

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

Với bộ kết hợp, bây giờ chúng ta có thể thực hiện một số thử nghiệm đơn giản:

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

Output: Matched

Và nếu bạn thực sự cảm thấy rằng bạn cần một công cụ bên ngoài (tốt, nó sẽ nhanh hơn rất nhiều và có thể thực hiện các tệp nhị phân và như vậy ...) Tôi đã tìm thấy điều này trên bộ định tuyến OpenWRT của mình ...

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

Trong đó url_escape.sed là một tệp chứa các quy tắc này:

# 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
Thật không may, tập lệnh này thất bại ở một số ký tự, chẳng hạn như 'é' và '½', xuất ra 'e% FFFFFFFFFFFFFFCC' và '% FFFFFFFFFFFFFFC2', tương ứng (b / c của vòng lặp cho mỗi ký tự, tôi tin).
Matthemattics

1
Nó không hoạt động đối với tôi trong Bash 4.3.11 (1). Chuỗi Jogging «à l'Hèze»tạo ra Jogging%20%abà%20l%27Hèze%bbkhông thể được cung cấp cho JS decodeURIComponent:(
dmcontador

2
Trong khối mã đầu tiên, tham số cuối cùng để printf có nghĩa là gì? Đó là, tại sao nó là trích dẫn kép, trích dẫn đơn, ký hiệu đô la, chữ-c, trích dẫn kép? Liệu trích dẫn duy nhất làm gì?
lừa đảo Colin

1
@dmcontador - nó chỉ là một tập lệnh bash khiêm tốn, nó không có khái niệm về các ký tự nhiều byte hoặc unicode. Khi thấy một ký tự như ( \u0144), nó sẽ xuất ra% 144 một cách ngây thơ, ( \u2561) sẽ được xuất thành% 2561. Các câu trả lời được mã hóa chính xác cho các câu trả lời này sẽ lần lượt là% C5% 84% 0A và% E2% 95% A1.
Orwellophile

1
@ColinFraizer trích dẫn duy nhất phục vụ để chuyển đổi ký tự sau thành giá trị số của nó. tham chiếu pubs.opengroup.org/onlinepub/9699919799/utilities/
Sam

94

Sử dụng URI::Escapemô-đun và uri_escapechức năng của Perl trong dòng thứ hai của tập lệnh bash của bạn:

...

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

Chỉnh sửa: Khắc phục sự cố trích dẫn, theo đề xuất của Chris Johnsen trong các bình luận. Cảm ơn!


2
URI :: Escape có thể không được cài đặt, kiểm tra câu trả lời của tôi trong trường hợp đó.
xanh lam

Tôi đã sửa lỗi này (sử dụng echo, đường ống và <>), và bây giờ nó hoạt động ngay cả khi $ 2 có dấu nháy đơn hoặc dấu ngoặc kép. Cảm ơn!
dubek

9
Bạn cũng đi cùng echo:value="$(perl -MURI::Escape -e 'print uri_escape($ARGV[0]);' "$2")"
Chris Johnsen

1
Phiên bản của Chris Johnsen là tốt hơn. Tôi đã có $ {True} trong biểu thức kiểm tra của mình và sử dụng điều này thông qua echo tăng gấp ba lần mở rộng biến uri_escape / Perl.
mm2001

1
@ jrw32982 yeah, nhìn lại nó, có một ngôn ngữ khác để hoàn thành nhiệm vụ này là tốt. Nếu tôi có thể, tôi sẽ lấy lại downvote của mình, nhưng than ôi hiện tại nó đang bị khóa.
thecoshman

69

Một tùy chọn khác là sử dụng jq(dưới dạng bộ lọc):

jq -sRr @uri

-R( --raw-input) coi các dòng đầu vào là các chuỗi thay vì phân tích chúng dưới dạng JSON và -sR( --slurp --raw-input) đọc đầu vào thành một chuỗi. -r( --raw-output) xuất nội dung của chuỗi thay vì bằng chuỗi ký tự JSON.

Nếu đầu vào không phải là đầu ra của một lệnh khác, bạn có thể lưu nó trong một jqbiến chuỗi:

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

-n( --null-input) không đọc đầu vào và --arg name valuelưu trữ valuetrong biến namedưới dạng chuỗi. Trong bộ lọc, $name(trong dấu ngoặc đơn, để tránh mở rộng bằng vỏ), tham chiếu biến name.

Được gói như một hàm Bash, điều này trở thành:

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

Hoặc phần trăm này mã hóa tất cả các byte:

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

3
<3 nó ... nên là IMO hàng đầu và được chấp nhận (vâng nếu bạn có thể yêu curlcầu mã hóa hoạt động đó và nếu bash có tích hợp có thể chấp nhận được - nhưng jqcó vẻ như phù hợp với mức độ thoải mái của tôi công cụ này)
nhed 16/11/17

5
cho bất cứ ai thắc mắc điều tương tự như tôi: @urikhông phải là một số biến, mà là một bộ lọc jq bằng chữ được sử dụng để định dạng chuỗi và thoát; xem hướng dẫn sử dụng jq để biết chi tiết (xin lỗi, không có liên kết trực tiếp, cần tìm kiếm @uritrên trang ...)
ssc

phiên bản xxd chỉ là thứ tôi đang tìm kiếm. Ngay cả khi nó hơi bẩn, nó ngắn và không có sự phụ thuộc
Rian Sanderson

1
Một cách sử dụng mẫu của jq để mã hóa url:printf "http://localhost:8082/" | jq -sRr '@uri'
Ashutosh Jindal

67

để hoàn thiện, nhiều giải pháp sử dụng sedhoặc awkchỉ dịch một bộ ký tự đặc biệt và do đó khá lớn theo kích thước mã và cũng không dịch các ký tự đặc biệt khác cần được mã hóa.

một cách an toàn để urlencode sẽ chỉ là mã hóa từng byte đơn - ngay cả những byte đã được cho phép.

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

xxd đang quan tâm ở đây rằng đầu vào được xử lý dưới dạng byte và không phải là ký tự.

biên tập:

xxd đi kèm với gói phổ biến vim trong Debian và tôi chỉ ở trên một hệ thống không được cài đặt và tôi không muốn cài đặt nó. Việc thay thế là sử dụng hexdumptừ gói bsdmainutils trong Debian. Theo biểu đồ sau, bsdmainutils và vim-common nên có khả năng tương đương để được cài đặt:

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

nhưng tuy nhiên ở đây một phiên bản sử dụng hexdumpthay vì xxdvà cho phép tránh trcuộc gọi:

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

1
xxd -plainnên xảy ra SAU tr -d '\n'!
qdii

3
@qdii tại sao? điều đó không chỉ khiến urlencode không thể tạo ra các dòng mới mà còn chèn sai các dòng mới được tạo bởi xxd vào đầu ra.
josch

1
@josch. Đây chỉ là một lỗi bình thường. Đầu tiên, bất kỳ \nký tự sẽ được dịch xxd -plainsang 0a. Đừng hiểu ý tôi, hãy tự mình thử: echo -n -e '\n' | xxd -plainĐiều này chứng tỏ rằng bạn tr -d '\n'vô dụng ở đây vì không thể có bất kỳ thứ gì \nsau xxd -plainThứ hai, echo foobarthêm \nký tự của chính nó vào cuối chuỗi ký tự, vì vậy xxd -plainkhông được nuôi dưỡng foobarnhư mong đợi nhưng với foobar\n. sau đó xxd -plain chuyển nó thành một số chuỗi ký tự kết thúc 0a, làm cho nó không phù hợp với người dùng. Bạn có thể thêm -nvào echođể giải quyết nó.
qdii

6
@qdii thực sự -n đã bị mất vì tiếng vang nhưng xxdcuộc gọi thuộc về phía trước của tr -dcuộc gọi. Nó thuộc về đó để bất kỳ dòng mới trong foobarđược dịch bởi xxd. Các tr -dsau khi xxdgọi là để loại bỏ các dòng mới mà xxd sản xuất. Có vẻ như bạn không bao giờ có foobar đủ lâu để xxdtạo ra dòng mới nhưng đối với đầu vào dài thì nó sẽ như vậy. Vì vậy, tr -dlà cần thiết. Ngược lại với giả định của bạn, tr -dKHÔNG được xóa dòng mới khỏi đầu vào mà khỏi xxdđầu ra. Tôi muốn giữ các dòng mới trong đầu vào. Điểm hợp lệ duy nhất của bạn là, tiếng vang đó thêm một dòng mới không cần thiết.
josch

1
@qdii và không có hành vi phạm tội - Tôi chỉ nghĩ rằng bạn đã sai, ngoại trừ echo -nđiều tôi thực sự đã mất tích
josch

62

Một trong những biến thể, có thể xấu, nhưng đơn giản:

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
}

Dưới đây là phiên bản một lớp lót (như được đề xuất bởi Bruno ):

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
Tôi nghĩ rằng đây là một cách rất thông minh để sử dụng lại mã hóa URL của cURL.
solidsnack

13
Điều này là hoàn toàn xuất sắc! Tôi thực sự ước bạn đã để lại cho nó một dòng để mọi người có thể thấy nó thực sự đơn giản như thế nào. Để URL mã hóa kết quả của datelệnh Cấm date | curl -Gso /dev/null -w %{url_effective} --data-urlencode @- "" | cut -c 3-(Bạn phải cuttắt 2 ký tự đầu tiên, vì đầu ra của curl là một URL tương đối có chuỗi truy vấn.)
Bruno Bronosky

2
@BrunoBronosky Biến thể một lớp của bạn là tốt nhưng dường như thêm "% 0A" vào cuối mã hóa. Người dùng hãy cẩn thận. Phiên bản chức năng dường như không có vấn đề này.
levigroker

7
Để tránh %0Aở cuối, sử dụng printfthay vì echo.
kenorb

2
một trong những lót là tuyệt vời
Stephen Blum

49

Tôi thấy nó dễ đọc hơn ở python:

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

bộ ba 'đảm bảo rằng các trích dẫn đơn trong giá trị sẽ không bị tổn thương. urllib là trong thư viện tiêu chuẩn. Nó hoạt động để kiểm tra cho url (thế giới thực) điên rồ này:

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

2
Tôi đã gặp một số rắc rối với dấu ngoặc kép và ký tự đặc biệt với bộ ba, điều này dường như hoạt động cho tất cả mọi thứ về cơ bản: encoding_value = "$ (echo -n" $ {data} "| python -c" nhập urllib; import sys; sys.stdout. viết (urllib.quote (sys.stdin.read ())) ")";
Ngừng nói xấu Monica Cellio

Phiên bản Python 3 sẽ là 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)'hầu như không có vấn đề trích dẫn nào và phải là bộ nhớ / tốc độ hiệu quả (chưa được kiểm tra, tiết kiệm cho việc nheo mắt)
Alois Mahdal

2
Sẽ an toàn hơn nhiều khi tham khảo sys.argvthay vì thay thế $valuethành một chuỗi sau đó được phân tích cú pháp dưới dạng mã. Nếu valuechứa ''' + __import__("os").system("rm -rf ~") + '''thì sao?
Charles Duffy

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

30

Tôi đã tìm thấy đoạn mã sau hữu ích để gắn nó vào chuỗi các cuộc gọi chương trình, trong đó URI :: Escape có thể không được cài đặt:

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

( nguồn )


4
đã làm cho tôi. Tôi đã thay đổi nó thành perl -lpe ... (chữ ell). Điều này đã loại bỏ dòng mới, mà tôi cần cho mục đích của mình.
JohnnyLambada

2
FYI, để thực hiện nghịch đảo điều này, hãy sử dụng perl -pe 's/\%(\w\w)/chr hex $1/ge'(nguồn: unix.stackexchange.com/questions/159253/ít )
Sridhar Sarnobat

2
Tùy thuộc vào cụ thể những ký tự bạn cần mã hóa, bạn có thể đơn giản hóa ký tự này để perl -pe 's/(\W)/sprintf("%%%02X", ord($1))/ge'cho phép các chữ cái, số và dấu gạch dưới, nhưng mã hóa mọi thứ khác.
cướp

23

Nếu bạn muốn chạy GETyêu cầu và sử dụng curl thuần túy, chỉ cần thêm--get vào giải pháp của @ Jacob.

Đây là một ví dụ:

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

15

Liên kết trực tiếp đến phiên bản awk: http://www.shelldorado.com/scripts/cmds/urlencode
Tôi đã sử dụng nó trong nhiều năm và nó hoạt động như một bùa mê

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

Có một biến thể đơn giản để có được mã hóa UTF-8 thay vì ASCII không?
avgvstvs

15

Đây có thể là một trong những tốt nhất:

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

Điều này hoạt động với tôi với hai bổ sung: 1. thay thế -e bằng -n để tránh thêm một dòng mới vào cuối đối số và 2. thêm '%%' vào chuỗi printf để đặt% trước mỗi cặp chữ số hex.
Rob Fagen

hoạt động sau khi thêm $ phía trước khung after=$(echo -e ...
Roman Rhrn Nesterov

1
Hãy giải thích làm thế nào điều này hoạt động. Các odlệnh là không phổ biến.
Mark Stosberg

Điều này không hoạt động với OS X odvì nó sử dụng định dạng đầu ra khác với GNU od. Ví dụ, printf aa|od -An -tx1 -v|tr \ -in -----------61--61--------------------------------------------------------bằng OS X od-61-61với GNU od. Bạn có thể sử dụng od -An -tx1 -v|sed 's/ */ /g;s/ *$//'|tr \ %|tr -d \\nvới OS X's odhoặc GNU od. xxd -p|sed 's/../%&/g'|tr -d \\nthực hiện điều tương tự, mặc dù xxdkhông có trong POSIX nhưng odlà.
nisetama

2
Mặc dù điều này có thể hoạt động, nó thoát khỏi mọi nhân vật
Charlie

11

Đây là một giải pháp Bash không gọi bất kỳ chương trình bên ngoài nào:

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
Điều này hành xử khác nhau giữa các phiên bản bash. Trên RHEL 6.9, bash là 4.1.2 và nó bao gồm các dấu ngoặc đơn. Trong khi Debian 9 và bash 4.4.12 vẫn ổn với các trích dẫn đơn. Đối với tôi loại bỏ các trích dẫn duy nhất làm cho nó hoạt động trên cả hai. s = "$ {s // ',' /% 2C}"
muni764

1
Tôi đã cập nhật câu trả lời để phản ánh phát hiện của bạn, @ muni764.
davidchambers

Chỉ là một cảnh báo ... điều này sẽ không mã hóa những thứ như nhân vậtá
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')

điều này sẽ mã hóa chuỗi bên trong $ 1 và xuất ra chuỗi $ url. mặc dù bạn không phải đặt nó trong một var nếu bạn muốn. BTW không bao gồm sed cho tab nghĩ rằng nó sẽ biến nó thành không gian


5
Tôi có cảm giác đây không phải là cách được khuyến nghị để làm điều này.
Cody Grey

2
xin vui lòng giải thích cảm giác của bạn .... bởi vì tôi những gì tôi đã nêu tác phẩm và tôi đã sử dụng nó trong một số kịch bản để tôi biết nó hoạt động cho tất cả các ký tự tôi liệt kê. vì vậy, vui lòng giải thích lý do tại sao ai đó sẽ không sử dụng mã của tôi và sử dụng perl vì tiêu đề của mã này là "URLEncode từ tập lệnh bash" không phải là tập lệnh perl.
manoflinux

đôi khi không cần giải pháp ngọc trai để điều này có thể có ích
Yuval Rimar

3
Đây không phải là cách được đề xuất để làm điều này bởi vì danh sách đen là thực tiễn tồi và dù sao đây cũng là unicode không thân thiện.
Ekevoo

Đây là giải pháp thân thiện nhất tương thích với tập tin mèo
mrwaim


7

Đối với những người bạn đang tìm kiếm một giải pháp không cần perl, đây là một giải pháp chỉ cần hexdump và 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
   '`
}

Khâu lại với nhau từ một vài nơi trên mạng và một số thử nghiệm và lỗi cục bộ. Nó hoạt động rất tốt!


7

uni2ascii rất tiện dụng:

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

2
Điều này không hoạt động đối với các ký tự trong phạm vi ASCII, cần trích dẫn, thích %và khoảng -s
trắng

7

Nếu bạn không muốn phụ thuộc vào Perl, bạn cũng có thể sử dụng sed. Có một chút lộn xộn, vì mỗi nhân vật phải được trốn thoát riêng lẻ. Tạo một tệp có nội dung sau đây và gọi nó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

Để sử dụng nó làm như sau.

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"

Điều này sẽ phân tách chuỗi thành một phần cần mã hóa, và phần đó là tốt, mã hóa phần cần nó, sau đó khâu lại với nhau.

Bạn có thể đặt nó vào một tập lệnh sh để thuận tiện, có thể yêu cầu nó lấy tham số để mã hóa, đặt nó trên đường dẫn của bạn và sau đó bạn chỉ cần gọi:

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

nguồn


7

Bạn có thể mô phỏng javascript encodeURIComponenttrong perl. Đây là lệnh:

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

Bạn có thể đặt cái này làm bí danh bash trong .bash_profile:

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

Bây giờ bạn có thể chuyển sang encodeURIComponent:

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

6

Đây là phiên bản nút:

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

1
Sẽ không phá vỡ nếu có bất kỳ ký tự nào khác trong chuỗi không hợp lệ giữa các dấu ngoặc đơn, như dấu gạch chéo đơn hoặc dòng mới?
Stuart P. Bentley

Điểm tốt. Nếu chúng ta gặp rắc rối trong việc thoát khỏi tất cả các nhân vật có vấn đề trong Bash, chúng ta cũng có thể thực hiện thay thế trực tiếp và tránh nodehoàn toàn. Tôi đã đăng một giải pháp chỉ dành cho Bash. :)
davidchambers

1
Biến thể này được tìm thấy ở nơi khác trên trang để tránh vấn đề trích dẫn bằng cách đọc giá trị từ STDIN:node -p 'encodeURIComponent(require("fs").readFileSync(0))'
Mark Stosberg

6

Câu hỏi là về việc thực hiện điều này trong bash và không cần python hay perl vì trên thực tế có một lệnh duy nhất thực hiện chính xác những gì bạn muốn - "urlencode".

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

Điều này cũng tốt hơn nhiều, ví dụ như câu trả lời perl ở trên, không mã hóa chính xác tất cả các ký tự. Hãy thử nó với dấu gạch ngang dài bạn nhận được từ Word và bạn nhận được mã hóa sai.

Lưu ý, bạn cần cài đặt "gridsite-client" để cung cấp lệnh này.


1
Phiên bản bash của tôi (GNU 3.2) không có urlencode. Phiên bản nào bạn đang sử dụng?
Sridhar Sarnobat

1
Tôi có 4.3.42, nhưng lệnh urlencode được cung cấp bởi "gridsite-client". Hãy thử cài đặt nó và bạn sẽ ổn thôi.
Dylan

5
Vì vậy, câu trả lời của bạn không tốt hơn bất kỳ câu hỏi nào được cài đặt (python, perl, lua, khắc)
Cyrille Pontvieux 27/07/17

Ngoại trừ việc nó chỉ yêu cầu cài đặt một tiện ích duy nhất thay vì toàn bộ ngôn ngữ (và thư viện), cộng với cực kỳ đơn giản và rõ ràng để xem những gì nó đang làm.
Dylan

Một liên kết đầu tiên cho trang gói / dự án cung cấp lệnh này sẽ có ích.
Doron Behar

6

Tùy chọn PHP đơn giản:

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

4

Ruby, cho sự hoàn chỉnh

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

4

Một cách tiếp cận php khác:

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

2
echosẽ nối thêm một ký tự dòng mới (hex 0xa). Để ngăn chặn nó làm điều đó, sử dụng echo -n.
Hội trường Mathew

3

Đây là phiên bản của tôi cho vỏ tro busybox cho một hệ thống nhúng, ban đầu tôi chấp nhận biến thể của 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

Đây là chức năng POSIX để làm điều đó:

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

Thí dụ:

value=$(encodeURIComponent "$2")

Nguồn


2

Đây là một chuyển đổi một dòng bằng Lua, tương tự như câu trả lời của blueyed ngoại trừ tất cả các ký tự không được kiểm soát RFC 3986 còn lại chưa được mã hóa (như câu trả lời này ):

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

Ngoài ra, bạn có thể cần đảm bảo rằng các dòng mới trong chuỗi của bạn được chuyển đổi từ LF sang CRLF, trong trường hợp đó bạn có thể chèn một gsub("\r?\n", "\r\n") chuỗi trong chuỗi trước mã hóa phần trăm.

Đây là một biến thể, theo kiểu phi tiêu chuẩn của ứng dụng / x-www-form-urlencoding , có chuẩn hóa dòng mới đó, cũng như mã hóa các không gian như '+' thay vì '% 20' (có thể được thêm vào Đoạn trích Perl sử dụng một kỹ thuật tương tự).

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

1

Có cài đặt php tôi sử dụng cách này:

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

1

Đây là phiên bản ksh của câu trả lời của orwellophile chứa các hàm rawurlencode và rawurldecode (liên kết: Làm thế nào để urlencode dữ liệu cho lệnh curl? ). Tôi không có đủ đại diện để đăng bình luận, vì vậy bài đăng mới ..

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

Điều gì sẽ phân tích URL tốt hơn javascript?

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

Ra khỏi phạm vi câu hỏi op. Không bash, không cong. Ngay cả khi tôi chắc chắn hoạt động rất tốt nếu nút có sẵn.
Cyrille Pontvieux

Tại sao bỏ phiếu này và không phải là câu trả lời python / perl? Hơn nữa, làm thế nào điều này không trả lời câu hỏi ban đầu "Làm thế nào để urlencode dữ liệu cho lệnh curl?". Điều này có thể được sử dụng từ một tập lệnh bash và kết quả có thể được đưa ra cho một lệnh curl.
Nestor Urquiza

Tôi đã bỏ phiếu cho những người khác quá. Câu hỏi là làm thế nào để làm điều này trong một kịch bản bash. Nếu một ngôn ngữ khác được sử dụng như nút / js, python hoặc perl, thì không cần phải sử dụng curl trực tiếp.
Cyrille Pontvieux

2
Mặc dù tôi không bận tâm đến downvote, nhưng vấn đề với lệnh này là nó yêu cầu dữ liệu phải được thoát đúng để sử dụng trong javascript. Giống như thử nó với dấu ngoặc đơn và một số dấu gạch chéo điên. Nếu bạn muốn sử dụng nút, tốt hơn hết bạn nên đọc nội dung từ stdin nhưnode -p 'encodeURIComponent(require("fs").readFileSync(0))'
Michael Krelin - hacker

1
Hãy cẩn thận với giải pháp của @ MichaelKrelin-hacker nếu bạn đang truyền dữ liệu từ STDIN, đảm bảo không bao gồm một dòng mới. Ví dụ, echo | ...là sai, trong khi echo -n | ...ngăn chặn dòng mới.
Mark Stosberg

0

Sau đây là dựa trên câu trả lời của Orwellophile, nhưng giải quyết lỗi đa bào được đề cập trong các nhận xét bằng cách đặt LC_ALL = C (một mẹo từ vte.sh). Tôi đã viết nó dưới dạng chức năng phù hợp PROMPT_COMMAND, vì đó là cách tôi sử dụng nó.

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}"
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.