I want to decode URL encoding, is there any built-in tool for doing this or could anyone provide me with a sed code that will do this?

I did search a bit through unix.stackexchange.com and on the internet but I couldn't find any command line tool for decoding url encoding.

What I want to do is simply in place edit a txt file so that:

  • %21 becomes !
  • %23 becomes #
  • %24 becomes $
  • %26 becomes &
  • %27 becomes '
  • %28 becomes (
  • %29 becomes )

And so on.


19 Answers 19


Found these Python one liners that do what you want:


$ alias urldecode='python -c "import sys, urllib as ul; \
    print ul.unquote_plus(sys.argv[1])"'

$ alias urlencode='python -c "import sys, urllib as ul; \
    print ul.quote_plus(sys.argv[1])"'


$ alias urldecode='python3 -c "import sys, urllib.parse as ul; \

$ alias urlencode='python3 -c "import sys, urllib.parse as ul; \
    print (ul.quote_plus(sys.argv[1]))"'


$ urldecode 'q+werty%3D%2F%3B'
q werty=/;

$ urlencode 'q werty=/;'


  • I know this very late, but is there any way i can do this with in place editing? Commented Nov 4, 2014 at 11:18
  • @DisplayName - sounds like a new Q to me. I'd ask it, and reference this one.
    – slm
    Commented Nov 4, 2014 at 12:18
  • 24
    streaming: cat your_lovely_file.csv| python -c "import sys, urllib as ul; [sys.stdout.write(ul.quote_plus(l)) for l in sys.stdin]" Commented Dec 2, 2015 at 15:14
  • See @DIG mbl's answer beblow for one that works with stdin. Commented Sep 3, 2018 at 14:44
  • Traceback (most recent call last): File "<string>", line 1, in <module> UnicodeEncodeError: 'ascii' codec can't encode character '\ufffd' in position 0: ordinal not in range(128) I added export PYTHONIOENCODING=utf8 to fix it. Commented Jan 16, 2023 at 13:19


Try the following command line:

$ sed 's@+@ @g;s@%@\\x@g' file | xargs -0 printf "%b"

or the following alternative using echo -e:

$ sed -e's/%\([0-9A-F][0-9A-F]\)/\\\\\x\1/g' file | xargs echo -e

Note: The above syntax may not convert + to spaces, and can eat all the newlines.

You may define it as alias and add it to your shell rc files:

$ alias urldecode='sed "s@+@ @g;s@%@\\\\x@g" | xargs -0 printf "%b"'

Then every time when you need it, simply go with:

$ echo "http%3A%2F%2Fwww" | urldecode


When scripting, you can use the following syntax:

decoded=$(printf '%b' "${input//%/\\x}")

However above syntax won't handle pluses (+) correctly, so you've to replace them with spaces via sed or as suggested by @isaac, use the following syntax:

decoded=$(input=${input//+/ }; printf "${input//%/\\x}")

You can also use the following urlencode() and urldecode() functions:

urlencode() {
    # urlencode <string>
    local length="${#1}"
    for (( i = 0; i < length; i++ )); do
        local c="${1:i:1}"
        case $c in
            [a-zA-Z0-9.~_-]) printf "$c" ;;
            *) printf '%%%02X' "'$c" ;;
urldecode() {
    # urldecode <string>
    local url_encoded="${1//+/ }"
    printf '%b' "${url_encoded//%/\\x}"

Note that above urldecode() assumes the data contains no backslash.

Here is similar Joel's version found at: https://github.com/sixarm/urldecode.sh

bash + xxd

Bash function with xxd tool:

urlencode() {
  local length="${#1}"
  for (( i = 0; i < length; i++ )); do
    local c="${1:i:1}"
    case $c in
      [a-zA-Z0-9.~_-]) printf "$c" ;;
    *) printf "$c" | xxd -p -c1 | while read x;do printf "%%%s" "$x";done

Found in cdown's gist file, also at stackoverflow.


Using PHP you can try the following command:

$ echo oil+and+gas | php -r 'echo urldecode(fgets(STDIN));' // Or: php://stdin
oil and gas

or just:

php -r 'echo urldecode("oil+and+gas");'

Use -R for multiple line input.


In Perl you can use URI::Escape.

decoded_url=$(perl -MURI::Escape -e 'print uri_unescape($ARGV[0])' "$encoded_url")

Or to process a file:

perl -i -MURI::Escape -e 'print uri_unescape($ARGV[0])' file


Try anon solution:

awk -niord '{printf RT?$0chr("0x"substr(RT,2)):$0}' RS=%..

Note: Parameter -n is specific to GNU awk.

Try Stéphane Chazelas urlencode solution:

awk -v RS='&#[0-9]+;' -v ORS= '1;RT{printf("%%%02X", substr(RT,3))}'

See: Using awk printf to urldecode text.

decoding file names

If you need to remove url encoding from the file names, use deurlname tool from renameutils (e.g. deurlname *.*).

See also:


  • 1
    awk: As this makes use of a library function, chr(), there is a high probability it will solely work on GNU awk (gawk). However, in this case there will be hardly any equivalent for POSIX awk, because the -n option (allowing non-decimal arguments) IS a GNU awk specialty. Commented Jun 27, 2015 at 15:07
  • 3
    Your solutions involving printf do not take into account that the url might contain escaped percentage signs like %25. You pass these to printf without escaping them for printf with another percentage sign like %%.
    – josch
    Commented Sep 13, 2016 at 19:57
  • 1
    The bash version requires local LC_ALL=C at the top, otherwise all wide characters (i.e Japanese, Chinese, etc.) are not properly broken into bytes.
    – Phernost
    Commented Aug 27, 2018 at 16:31
  • 1
    – Pysis
    Commented Apr 24, 2019 at 21:24
  • 1
    The printf versions don't work when using BSD version of printf (eg macOS), however it works fine when using the GNU Coreutils version. Commented Apr 30, 2019 at 18:07

There is a built-in function for that in the Python standard library. In Python 2, it's urllib.unquote.

decoded_url=$(python2 -c 'import sys, urllib; print urllib.unquote(sys.argv[1])' "$encoded_url")

Or to process a file:

python2 -c 'import sys, urllib; print urllib.unquote(sys.stdin.read())' <file >file.new &&
mv -f file.new file

In Python 3, it's urllib.parse.unquote.

decoded_url=$(python3 -c 'import sys, urllib.parse; print(urllib.parse.unquote(sys.argv[1]))' "$encoded_url")

Or to process a file:

python3 -c 'import sys, urllib.parse; print(urllib.parse.unquote(sys.stdin.read()))' <file >file.new &&
mv -f file.new file

In Perl you can use URI::Escape.

decoded_url=$(perl -MURI::Escape -e 'print uri_unescape($ARGV[0])' "$encoded_url")

Or to process a file:

perl -pli -MURI::Escape -e '$_ = uri_unescape($_)' file

If you want to stick to POSIX portable tools, it's awkward, because the only serious candidate is awk, which doesn't parse hexadecimal numbers. See Using awk printf to urldecode text for examples with common awk implementations, including BusyBox.


Perl one liner:

$ perl -pe 's/\%(\w\w)/chr hex $1/ge'


$ echo '%21%22' |  perl -pe 's/\%(\w\w)/chr hex $1/ge'

or if you want to ignore non-hex sequences like %zz (which the above mangles)

$ perl -pe 's/\%([[:xdigit:]]{2})/chr hex $1/ge'
  • 3
    This answer is attractive when you don't want to deal with installing perl modules. Commented Nov 10, 2015 at 19:36
  • 3
    Only one that worked elegantly for me on MacOS. Commented Mar 27, 2019 at 13:02
  • If you want to resolve all of the levels of URL encoding at once, there's also perl -pe 's/\%([[:xdigit:]]{2})/chr hex $1/ge while (/\%[[:xdigit:]]{2}/);' which will decode all the %25xx nested encodings
    – scruss
    Commented Oct 6, 2020 at 18:04

If you want to use a simple-minded sed command, then use the following:

sed -e 's/%21/!/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'

But it is more convenient to create a script like (say sedscript):


Then run sed -f sedscript < old > new, which will output as you desired.

For an ease, the command urlencode is also available directly in gridsite-clients package can be installed from (by sudo apt-get install gridsite-clients in Ubuntu/Debian system).


    urlencode - convert strings to or from URL-encoded form

    urlencode [-m|-d] string [string ...]


    urlencode encodes strings according to RFC 1738.

    That is, characters A-Z a-z 0-9 . _ and - are passed through unmodified, but all other characters are represented as %HH, where HH is their two-digit upper-case hexadecimal ASCII representation. For example, the URL http://www.gridpp.ac.uk/ becomes http%3A%2F%2Fwww.gridpp.ac.uk%2F

    urlencode converts each character in all the strings given on the command line.  If multiple strings are given, they are concatenated with separating spaces before conversion.

      Instead of full conversion, do GridSite "mild URL encoding" in which A-Z a-z 0-9 . = - _ @ and / are passed through unmodified.  This results in slightly more human-readable strings but the application must be prepared to create or simulate the directories implied by any slashes.
      Do URL-decoding rather than encoding, according to RFC 1738.  %HH and %hh strings are converted and other characters are passed through unmodified, with the exception that + is converted to space.

Example of decoding URL:

$ urlencode -d "http%3a%2f%2funix.stackexchange.com%2f"

$ urlencode -d "Example: %21, %22, . . . , %29 etc"
Example: !, ", . . . , ) etc
  • For tutorial on sed visit
    – Pandya
    Commented Oct 4, 2014 at 14:27
  • 6
    This is a bad solution, because it requires hardcoding every character. This problem is exemplified by your code missing the often used %20 escape sequence.
    – Overv
    Commented Oct 5, 2014 at 13:43
  • @Overv I've just Revised
    – Pandya
    Commented Oct 5, 2014 at 14:02
  • Also, you might want to double-check what s/%26/&/g does.  (I fixed it.) Commented Jul 16, 2015 at 6:42
  • Not up on my RFCs, but RFC-1738 appears superceded by RFC-3986: rfc-editor.org/rfc/rfc3986 Commented May 17, 2023 at 21:01

I can't comment on best answer in this thread, so here is mine.

Personally, I use these aliases for URL encoding and decoding:

alias urlencode='python -c "import urllib, sys; print urllib.quote(  sys.argv[1] if len(sys.argv) > 1 else sys.stdin.read()[0:-1])"'

alias urldecode='python -c "import urllib, sys; print urllib.unquote(sys.argv[1] if len(sys.argv) > 1 else sys.stdin.read()[0:-1])"'

Both commands allow you to convert data, passed as a command line argument or read it from standard input, because both one-liners check whether there are command line arguments (even empty ones) and process them or just read standard input otherwise.

update 2017-05-23 (slash encoding)

In response to the @Bevor's comment.

If you also need to encode the slash, just add an empty second argument to the quote function, then the slash will also be encoded.

So, finally urlencode alias in bash looks like this:

alias urlencode='python -c "import urllib, sys; print urllib.quote(sys.argv[1] if len(sys.argv) > 1 else sys.stdin.read()[0:-1], \"\")"'


$ urlencode "Проба пера/Pen test"

$ echo "Проба пера/Pen test" | urlencode

$ urldecode %D0%9F%D1%80%D0%BE%D0%B1%D0%B0%20%D0%BF%D0%B5%D1%80%D0%B0%2FPen%20test
Проба пера/Pen test

$ echo "%D0%9F%D1%80%D0%BE%D0%B1%D0%B0%20%D0%BF%D0%B5%D1%80%D0%B0%2FPen%20test" | urldecode
Проба пера/Pen test

$ urlencode "Проба пера/Pen test" | urldecode
Проба пера/Pen test

$ echo "Проба пера/Pen test" | urlencode | urldecode
Проба пера/Pen test
  • 1
    Does not encode slashes.
    – Bevor
    Commented May 15, 2017 at 11:41
  • @Bevor: Example?
    – DIG mbl
    Commented May 22, 2017 at 3:40
  • Add a slash to urlencode "Проба пера" -> result: Slash is not encoded.
    – Bevor
    Commented May 22, 2017 at 9:21
  • 1
    @Bevor: You are right. Thank you for your comment. I will also change my answer to reflect your comment in it.
    – DIG mbl
    Commented May 23, 2017 at 13:49
  • Very slight improvement. Join argv with spaces. Ex: alias urlencode='python -c "import urllib, sys; print urllib.quote( \" \".join(sys.argv[1:]) if len(sys.argv) > 1 else sys.stdin.read()[0:-1])"'.
    – user164386
    Commented Aug 24, 2021 at 16:54


#!/usr/bin/awk -fn
@include "ord"
   RS = "%.."
   printf "%s", $0
   if (RT != "") {
      printf "%s", chr("0x" substr(RT, 2)) 

And another Perl approach:

#!/usr/bin/env perl
use URI::Encode;
my $uri     = URI::Encode->new( { encode_reserved => 0 } );
while (<>) {

    print $uri->decode($_)

You will need to install the URI::Encode module. On my Debian, I could simply run

sudo apt-get install liburi-encode-perl

Then, I ran the script above on a test file containing:


The result was (I had saved the script as foo.pl):

$ ./foo.pl

Another solution using ruby (accepted python answer wasn't working for me)

alias urldecode='ruby -e "require \"cgi\"; puts CGI.unescape(ARGV[0])"'
alias urlencode='ruby -e "require \"cgi\"; puts CGI.escape(ARGV[0])"'


$ urldecode 'q+werty%3D%2F%3B'
q werty=/;

$ urlencode 'q werty=/;'
  • 1
    Just used to ruby, and these statements look smaller. Also, I changed to ARGF.read so I can pipe it in like I do with many other utilities!
    – Pysis
    Commented Feb 13, 2020 at 15:24
  • The command can be simplified by passing the flag -r cgi instead of embedding require \"cgi\"; in the code. Also, I like @Pysis’s idea above but suggest $stdin.read.chomp because ARGF’s extra features don’t make sense for this use case and because we don’t want to process the ending newline. After these changes: ruby -r cgi -e "puts CGI.unescape($stdin.read.chomp)" Commented Apr 30, 2023 at 17:16

An answer in (mostly Posix) shell:

$ input='%21%22'
$ printf "`printf "%s\n" "$input" | sed -e 's/+/ /g' -e 's/%\(..\)/\\\\x\1/g'`"


  • -e 's/+/ /g transforms each + in space (as described in url-encode norm)
  • -e 's/%\(..\)/\\\\x\1/g' transform each %XX in \\xXX. Notice one of \ will be removed by quoting rules.
  • The inner printf is just there to pass input to sed. We may replace it by any other mechanism
  • The outer printf interpret \\xXX sequences and display result.


Since % should always been interpreted in URLs, it is possible to simplify this answer. In add, I think it is cleaner to use xargs instead of backquotes (thanks to @josch).

$ input='%21%22+%25'
$ printf "%s\n" "$input" | sed -e 's/+/ /g; s/%/\\x/g' | xargs -0 printf
!" %

Unfortunately, (as @josch noticed) none of these solutions are Posix compliant since \x escape sequence is not defined in Posix.

  • Welcome to U&L. Perhaps you could explain this answer and how it works. We generally prefer our answers to be long form with details, not just code snippets.
    – slm
    Commented Dec 5, 2014 at 12:41
  • I really like this answer because it's comprehensive, portable and doesn't require extra heavier external programs like perl or python. Works well for me. Commented Jan 15, 2015 at 17:05
  • 1
    Great solution. And even shorter and smarter: ... | sed 's/+/ /g;s/%\(..\)/\\\\x\1/g'. The -e option can be omitted here in fact... Commented Jun 27, 2015 at 16:09
  • 1
    @josch You are right, printf is a built-in in dash and it doesn't recognize \x escaping. You can use /usr/bin/printf instead of printf in order to make it work. Normally, you should be able to use command printf, but it seems to not work as it should. It continue to use built-in. Commented Sep 14, 2016 at 11:43
  • 1
    @Jezz indeed support for \x escaping is not part of POSIX: pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html During my tests I saw another problem. You might want to replace your .. regex by [a-zA-Z0-9][a-zA-Z0-9] because otherwise input like '%%%' will fail. I also added s/%/%%/g at the end to make sure to escape percentages for printf.
    – josch
    Commented Sep 15, 2016 at 6:03

Here is a BASH function to do exactly that:

function urldecode() {
        echo -ne $(echo -n "$1" | sed -E "s/%/\\\\x/g")
  • works like charm
    – abd3lraouf
    Commented Jun 21, 2018 at 14:59
  • If you want to convert + characters to spaces and not spawn any processes, you can use this: : "${1//+/ }" && echo -e "${_//%/\\x}" Commented Jul 25, 2020 at 11:48
  • I tried loads and this was the only one that worked, I had to move it out of the function and changed $1 to my variable though.
    – malhal
    Commented Aug 2, 2023 at 22:41


$ x='a%20%25%e3%81%82';printf "${x//\%/\\x}"
a %あ

Add -- or %b to prevent arguments that start with a dash from being treated as options.

In zsh ${x//%/a} adds a to the end but ${x//\%/a} replaces % with a.


Here are the relevant bits from another script (that I just shamelessly stole from my youtube.com download script from another answer) I've written before. It uses sed and the shell to build up a working urldecode.

set \! \" \# \$ \% \& \' \( \) \* \ \+ \, \/ \: \; \= \? \@ \[ \]
for c do set "$@" "'$c" "$c"; shift; done
curl -s "$url" | sed 's/\\u0026/\&/g;'"$(
    printf 's/%%%X/\\%s/g;' "$@"

I wont swear it's comprehensive - and in fact I doubt it - but it handled youtube surely enough.


Using Raku (formerly known as Perl_6)

Using Raku's URI::Encode module, which purports to be RFC3986 compliant (just like Perl5's URI::Encode and/or URI::Escape modules):

~$ raku -MURI::Encode -ne 'put uri_decode($_);'  file

Sample Input:


Sample Output:

http://www.example.com/?name=john doe&age=54

Note, if you're looking for a more full-blown URL parser, try Raku's URL module. Sample Output (below) with same input as above:

~$ raku -MURL -ne 'my $url = URL.new($_); .raku.put for $url;'  file
URL.new(scheme => "http", username => Str, password => Str, hostname => "www.example.com", port => Int, path => [], query => {:age("54"), :name("john\%20doe")}, fragment => Str)

With the second approach, you can extract only the elements you really need decoded, like the URL query subcomponent, and decode as key/value pairs:

~$ raku -MURL -MURI::Encode -ne 'my $url = URL.new($_); for $url.query.kv -> $k,$v {say $k => uri_decode($v)};'  file
age => 54
name => john doe


  • 1
    Using $*IN instead of $_ allows for reading from standard input, like so: echo 'name=john%20doe&age=54' | raku -M URI::Escape -e 'put uri_unescape($*IN.get)' Commented Jan 30 at 0:44

The simple solution for short strings (shell is slowwww):

$ str='q+werty%3D%2F%3B'

$ a=${str//+/ };printf "$(echo "${a//%/\\x}")\n"

q werty=/;

From my laymen research of the topic, it appears that the implementations of the percent-encoding are susceptible to ambiguity in edge cases, such as character encoding potentially being different than expected, characters not escaped, query part being encoded differently, potential presence of binary and non-ASCII characters, etc. So, some analysis of and assumptions about the input data are necessary.

The closest to a dedicated tool are respective functions in programming languages, such as Python's functions from urllib module, which makes some sane assumptions about the URL data, as evidenced by the comments in cpython's code. That's why I find the current top answer being good.

As a matter of exercise, I implemented a similar alias with GNU Guile, since it is in path by default on a GNU Guix system with Python not necessarily being present in path. I cannot comment on reliability in comparison to Python, Perl, or other solutions. The documentation suggests that one should preferably split the URL on ?, &, and =, and process the query separately from the path, as well as split the path into segments with a dedicated function, and still be ready for errors. However, I am satisfied with the results on full URL strings copied from a browser.

alias urldecode='guile -c "(use-modules (web uri))
                           (display (uri-decode (cadr (command-line))))

(web uri) module provides uri-decode function for decoding URIs. command-line passes the arguments. cadr picks the second item in the list (which is the URL being the first argument after the executable name itself, i.e. guile).

$ urldecode "http://ephsheir.uhsp.edu.ua/bitstream/handle/8989898989/2850/%d0%9c%d0%b0%d0%ba%d0%b5%d1%82%20%d0%9d%d0%b0%d1%80%d0%be%d0%b4%d0%bd%d0%b8%20%d0%bd%d0%b0%d0%b7%d0%b2%d0%b8.pdf?sequence=2&isAllowed=y"
http://ephsheir.uhsp.edu.ua/bitstream/handle/8989898989/2850/Макет Народни назви.pdf?sequence=2&isAllowed=y

A one-liner when not having an alias:

$ guile -c "(use-modules (web uri)) (display (uri-decode (cadr (command-line)))) (newline)" "http://ephsheir.uhsp.edu.ua/bitstream/handle/8989898989/2850/%d0%9c%d0%b0%d0%ba%d0%b5%d1%82%20%d0%9d%d0%b0%d1%80%d0%be%d0%b4%d0%bd%d0%b8%20%d0%bd%d0%b0%d0%b7%d0%b2%d0%b8.pdf?sequence=2&isAllowed=y"
http://ephsheir.uhsp.edu.ua/bitstream/handle/8989898989/2850/Макет Народни назви.pdf?sequence=2&isAllowed=y
  • The encoding algorithm itself is unambiguous, of course, but as you note, there is no way to indicate which encoding to use for the resulting text. The RFCs have evolved over the years so that the current default assumption should usually be UTF-8, but legacy systems previously used ISO-8859-1 or CP1252 in accordance with previous specs as well as various ad-hoc conventions.
    – tripleee
    Commented Jan 5, 2023 at 12:43


This recently came up again and I wanted a non-pythonic version that'd work on AIX/Solaris etc.

for C in `echo "${INPUTSTRING}" | sed 's/%\(..\)/ %\1 /g'`
  case "$C" in
      echo $C | sed 's/%//' | (echo 16i; tr '[:lower:]' '[:upper:]'; echo P) | dc
      printf "%s" "$C"

In essence, tokenizes the string and then for each token, if it's not a % sequence, just print it.  Otherwise trim the % and run it through dc with 16i radix.

Relies on dc, sed and POSIX features of printf (no \x encoding).

Here it is as a "1-liner":

for C in `echo "test%20%21%22%23%24%25%3f%2f%2e%5ctest" | sed 's/%\(..\)/ %\1 /g'`; do case "$C" in %*) echo $C | sed 's/%//' | (echo 16i; tr '[:lower:]' '[:upper:]'; echo P) | dc ;; *) printf "%s" "$C" ;; esac; done

This solution doesn't use sed but zsh (specifically about .ohmyzsh plugin) you can use the function omz_urldecode to parse any %## to a readable string:

omz_urldecode 'http://example.com/some%23'

You can test your examples %## with a for:

for ((i=21; i<=29; i++)); do 
  omz_urldecode "http://example.com/exa_%${i}mple"



If you have ohmyzsh installed in your system you can use this command in zsh to figure out where is defined that function:

type -a omz_urldecode
omz_urldecode is a shell function from /home/user/.oh-my-zsh/lib/functions.zsh

And to see what that function has:

type -f omz_urldecode
omz_urldecode () {
        emulate -L zsh
        local encoded_url=$1 
        local caller_encoding=$langinfo[CODESET] 
        local LC_ALL=C 
        export LC_ALL
        local tmp=${encoded_url:gs/+/ /} 
        local decoded="$(printf -- "$tmp")" 
        local -a safe_encodings
        safe_encodings=(UTF-8 utf8 US-ASCII) 
        if [[ -z ${safe_encodings[(r)$caller_encoding]} ]]
                decoded=$(echo -E "$decoded" | iconv -f UTF-8 -t $caller_encoding) 
                if [[ $? != 0 ]]
                        echo "Error converting string from UTF-8 to $caller_encoding" >&2
                        return 1
        echo -E "$decoded"

Must have changed my approach from my earlier comment, probably for a quick tool install instead of coding and/or any manual setup.

Now I use this:

npm i -g url-cli
xout | url -dp | xio; # Linux + Aliases
gc; gc | tr -d '\n' | url -dp | pc; gc; # Windows-Cygwin + Aliases

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .