如何在shell中解码URL编码的字符串?

时间:2011-06-06 10:28:34

标签: bash shell awk sed urldecode

我有一个文件,其中包含已编码的用户代理列表。 E.g:

Mozilla%2F5.0%20%28Macintosh%3B%20U%3B%20Intel%20Mac%20OS%20X%2010.6%3B%20en

我想要一个可以读取此文件的shell脚本,并使用已解码的字符串写入新文件。

Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en

我一直在尝试使用这个例子来实现它,但到目前为止还没有用。

$ echo -e "$(echo "%31+%32%0A%33+%34" | sed 'y/+/ /; s/%/\\x/g')"

我的脚本如下:

#!/bin/bash
for f in *.log; do
  echo -e "$(cat $f | sed 'y/+/ /; s/%/\x/g')" > y.log
done

22 个答案:

答案 0 :(得分:36)

这是一个简单的单行解决方案。

$ urldecode() { : "${*//+/ }"; echo -e "${_//%/\\x}"; }

它可能看起来像perl :)但它只是纯粹的bash。没有问题,没有seds ......没有开销。使用:builtin,特殊参数,模式替换和echo builtin的-e选项将十六进制代码转换为字符。有关更多详细信息,请参阅bash的联机帮助页。您可以将此功能用作单独的命令

$ urldecode https%3A%2F%2Fgoogle.com%2Fsearch%3Fq%3Durldecode%2Bbash
https://google.com/search?q=urldecode+bash

或在变量赋值中,如下:

$ x="http%3A%2F%2Fstackoverflow.com%2Fsearch%3Fq%3Durldecode%2Bbash"
$ y=$(urldecode "$x")
$ echo "$y"
http://stackoverflow.com/search?q=urldecode+bash

答案 1 :(得分:18)

GNU awk

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

或者

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

Using awk printf to urldecode text

答案 2 :(得分:10)

使用BASH,从标准输入读取百分比编码的URL并解码:

while read; do echo -e ${REPLY//%/\\x}; done

CTRL - D 发出文件结束信号(EOF)并正常退出。

您可以通过将文件设置为标准文件来解码文件内容:

while read; do echo -e ${REPLY//%/\\x}; done < file

您可以解码来自管道的输入,例如:

echo 'a%21b' | while read; do echo -e ${REPLY//%/\\x}; done
  • read built in命令读取标准输入,直到看到换行符。它设置一个名为REPLY的变量,它等于刚才读取的文本行。
  • ${REPLY//%/\\x}替换了&#39;%&#39;的所有实例与&#39; \ x&#39;。
  • echo -e\xNN解释为具有十六进制值NN的ASCII字符。
  • 重复此循环,直到读取命令失败,例如。已达到EOF。

以上内容并没有改变&#39; +&#39;到&#39; &#39 ;.改变&#39; +&#39;到&#39; &#39;也像客人answer

一样
while read; do : "${REPLY//%/\\x}"; echo -e ${_//+/ }; done
  • :是BASH内置命令。在这里它只需要一个参数并且不做任何事情。
  • 双引号将所有内容都放在一个参数中。
  • _是一个特殊参数,在参数扩展后等于上一个命令的最后一个参数。这是REPLY的值,包含&#39;%&#39;的所有实例。替换为&#39; \ x&#39;。
  • ${_//+/ }替换了&#39; +&#39;的所有实例用&#39; &#39;

这仅使用BASH,并且不会启动任何其他过程,类似于访客的回答。

答案 3 :(得分:10)

如果您是 python 开发人员,这可能是优先选择

echo "%21%20" | python -c "import sys, urllib as ul; print ul.unquote(sys.stdin.read());"

urllib专业处理

答案 4 :(得分:9)

这似乎对我有用。

#!/bin/bash
urldecode(){
  echo -e "$(sed 's/+/ /g;s/%\(..\)/\\x\1/g;')"
}

for f in /opt/logs/*.log; do
    name=${f##/*/}
    cat $f | urldecode > /opt/logs/processed/$HOSTNAME.$name
done

用空格替换'+',用'\ x'表示%符号转义,并且使用'-e'选项让回声解释\ x转义符不起作用。出于某种原因,cat命令将%符号打印为其自己的编码形式%25。所以sed只是用\ x25替换%25。当使用-e选项时,它只是将\ x25评估为%,输出与原始输出相同。

<强>跟踪:

原文: Mozilla%2F5.0%20%28Macintosh%3B%20U%3B%20Intel%20Mac%20OS%20X%2010.6%3B%20en

sed: Mozilla \ x252F5.0 \ x2520 \ x2528Macintosh \ x253B \ x2520U \ x253B \ x2520Intel \ x2520Mac \ x2520OS \ x2520X \ x252010.6 \ x253B \ x2520en

echo -e: Mozilla%2F5.0%20%28Macintosh%3B%20U%3B%20Intel%20Mac%20OS%20X%2010.6%3B%20en

修复:基本上忽略sed中%后的2个字符。

sed: Mozilla \ x2F5.0 \ x20 \ x28Macintosh \ x3B \ x20U \ x3B \ x20Intel \ x20Mac \ x20OS \ x20X \ x2010.6 \ x3B \ x20en

echo -e: Mozilla / 5.0(Macintosh; U; Intel Mac OS X 10.6; en

在经过大量测试后,不确定会导致什么并发症,但现在可以使用。

答案 5 :(得分:7)

perl -pi.back -e 'y/+/ /;s/%([\da-f]{2})/pack H2,$1/gie' ./*.log

-i就地更新文件(某些sed实施从perl借用了.back作为备用扩展名。

s/x/y/ex替换为y perl代码的 e 评估。

在这种情况下,perl代码使用pack$1中捕获的十六进制数(正则表达式中的第一个括号对)打包为相应的字符。

pack的替代方法是使用chr(hex($1))

perl -pi.back -e 'y/+/ /;s/%([\da-f]{2})/chr hex $1/gie' ./*.log

如果可用,您还可以使用uri_unescape()中的URI::Escape

perl -pi.back -MURI::Escape -e 'y/+/ /;$_=uri_unescape$_' ./*.log

答案 6 :(得分:6)

使用本机Bash(original source)执行此操作的Bash脚本:

LANG=C

urlencode() {
    local l=${#1}
    for (( i = 0 ; i < l ; i++ )); do
        local c=${1:i:1}
        case "$c" in
            [a-zA-Z0-9.~_-]) printf "$c" ;;
            ' ') printf + ;;
            *) printf '%%%.2X' "'$c"
        esac
    done
}

urldecode() {
    local data=${1//+/ }
    printf '%b' "${data//%/\x}"
}

如果要对文件内容进行urldecode,只需将文件内容作为参数。

如果解码的编码文件内容不同(如果它运行几秒钟,脚本可能正常工作),这将是一个暂停的测试:

while true
  do cat /dev/urandom | tr -d '\0' | head -c1000 > /tmp/tmp;
     A="$(cat /tmp/tmp; printf x)"
     A=${A%x}
     A=$(urlencode "$A")
     urldecode "$A" > /tmp/tmp2
     cmp /tmp/tmp /tmp/tmp2
     if [ $? != 0 ]
       then break
     fi
done

答案 7 :(得分:4)

如果您的服务器上安装了php,则可以“轻松”或“尾随”任何文件,并且可以非常轻松地使用网址编码字符串。

tail -f nginx.access.log | php -R 'echo urldecode($argn)."\n";'

答案 8 :(得分:4)

正如@barti_ddu在评论中所说,\x“应该[双重]转义”。

% echo -e "$(echo "Mozilla%2F5.0%20%28Macintosh%3B%20U%3B%20Intel%20Mac%20OS%20X%2010.6%3B%20en" | sed 'y/+/ /; s/%/\\x/g')"
Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en

我不是把Bash和sed混合在一起,而是用Python完成所有这些。以下是一个粗略的方法:

#!/usr/bin/env python

import glob
import os
import urllib

for logfile in glob.glob(os.path.join('.', '*.log')):
    with open(logfile) as current:
        new_log_filename = logfile + '.new'
        with open(new_log_filename, 'w') as new_log_file:
            for url in current:
                unquoted = urllib.unquote(url.strip())
                new_log_file.write(unquoted + '\n')

答案 9 :(得分:3)

使用GNU awk

gawk -vRS='%[0-9a-fA-F]{2}' 'RT{sub("%","0x",RT);RT=sprintf("%c",strtonum(RT))}
                             {gsub(/\+/," ");printf "%s", $0 RT}'

答案 10 :(得分:2)

这是一个在纯bash中完成的解决方案,其中输入和输出是bash变量。它将'+'解码为空格并处理'%20'空格以及其他%编码字符。

#!/bin/bash
#here is text that contains both '+' for spaces and a %20
text="hello+space+1%202"
decoded=$(echo -e `echo $text | sed 's/+/ /g;s/%/\\\\x/g;'`)
echo decoded=$decoded

答案 11 :(得分:2)

$ uenc='H%C3%B6he %C3%BCber%20dem%20Meeresspiegel'
$ utf8=$(echo -e "${uenc//%/\\x}")
$ echo $utf8
Höhe über dem Meeresspiegel
$

答案 12 :(得分:2)

为Python 3.5+更新Jay's答案:
echo "%31+%32%0A%33+%34" | python -c "import sys; from urllib.parse import unquote ; print(unquote(sys.stdin.read()))"

仍然,brendan's带有解释的bash解决方案似乎更加直接和优雅。

答案 13 :(得分:1)

使用 sed:

#!/bin/bash
URL_DECODE="$(echo "$1" | sed -E 's/%([0-9a-fA-F]{2})/\\x\1/g;s/\+/ /g'"
echo -e "$URL_DECODE"
  • s/%([0-9a-fA-F]{2})/\\x\1/g 用 \x 替换 % 以将 urlencoded 转换为十六进制
  • s/\+/ /g 将 + 替换为空格 ' ',以防在查询字符串中使用 +

只需将其保存到 decodeurl.sh 并使用 chmod +x decodeurl.sh 使其可执行

如果你也需要一种编码方式,这个完整的代码会有所帮助:

#!/bin/bash
#
# Enconding e Decoding de URL com sed
#
# Por Daniel Cambría
# daniel.cambria@bureau-it.com
#
# jul/2021

function url_decode() {
echo "$@" \
    | sed -E 's/%([0-9a-fA-F]{2})/\\x\1/g;s/\+/ /g'
}

function url_encode() {
    # Conforme RFC 3986
    echo "$@" \
    | sed \
    -e 's/ /%20/g' \
    -e 's/:/%3A/g' \
    -e 's/,/%2C/g' \
    -e 's/\?/%3F/g' \
    -e 's/#/%23/g' \
    -e 's/\[/%5B/g' \
    -e 's/\]/%5D/g' \
    -e 's/@/%40/g' \
    -e 's/!/%41/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/;/%3B/g' \
    -e 's/=/%3D/g'
}

echo -e "URL decode: " $(url_decode "$1")
echo -e "URL encode: " $(url_encode "$1")

答案 14 :(得分:1)

以其他一些答案为基础,但是对于POSIX世界,可以使用以下功能:

url_decode() {
    printf '%b\n' "$(sed -E -e 's/\+/ /g' -e 's/%([0-9a-fA-F]{2})/\\x\1/g')"
}

它使用printf '%b\n'是因为没有echo -e并中断了sed调用以使其更易于阅读,从而迫使-E能够将引用与{{ 1}}。它还会迫使\1之后的内容看起来像一些十六进制代码。

答案 15 :(得分:0)

python,用于 zshrc

# Usage: decodeUrl %3A%2F%2F
function decodeUrl(){
    echo "$1" | python3 -c "import sys; from urllib.parse import unquote; print(unquote(sys.stdin.read()));"    
}

# Usage: encodeUrl https://google.com/search?q=urldecode+bash
#          return: https://google.com/search\?q\=urldecode+bash
function encodeUrl(){
    echo "$1" | python3 -c "import sys; from urllib.parse import quote; print(quote(sys.stdin.read()));"
}

答案 16 :(得分:0)

使用zsh外壳程序(而不是bash),唯一的外壳程序的变量可以保存任何字节值,包括NUL(编码为%00):

set -o extendedglob +o multibyte
string='Mozilla%2F5.0%20%28Macintosh%3B%20U%3B%20Intel%20Mac%20OS%20X%2010.6%3B%20en'
decoded=${${string//+/ }//(#b)%([[:xdigit:]](#c2))/${(#):-0x$match[1]}}
  • ${var//pattern/replacement}:ksh样式的参数扩展运算符,用于将匹配$var的每个字符串替换为pattern的情况下扩展为replacement的值。
  • (#b)激活向后引用,因此在替换中,方括号内的每个部分都可以作为相应的$match[n]来访问。
  • (#c2):等同于ERE {2}
  • ${(#)param-expansion}:参数扩展,其中#标志使结果解释为算术表达式,并返回相应的字节值。
  • ${var:-value}:如果value为空,则扩展为$var,这里根本不应用于任何变量,因此我们可以指定任意字符串作为参数扩展的主题。 / li>

使其成为可就地解码变量内容的函数:

uridecode_var() {
  emulate -L zsh
  set -o extendedglob +o multibyte
  eval $1='${${'$1'//+/ }//(#b)%([[:xdigit:]](#c2))/${(#):-0x$match[1]}}'
}
$ string='Mozilla%2F5.0%20%28Macintosh%3B%20U%3B%20Intel%20Mac%20OS%20X%2010.6%3B%20en'
$ uridecode_var string
$ print -r -- $string
Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en

答案 17 :(得分:0)

扩展到 https://stackoverflow.com/a/37840948/8142470
使用HTML实体

  

$ htmldecode(){:&#34; $ {* // + /}&#34 ;; echo -e&#34; $ {_ //&amp; #x / \ x}&#34; | tr -d   &#39 ;;&#39 ;; } $ htmldecode   &#34; HTTP&安培;#X3A;&安培;#X2F;&安培;#X2F; google.com&安培;#X2F;搜索和安培;&安培;#X3F; Q&安培;#X3D; urldecode&安培;#X2B;的bash&#34; http://google.com/search&?q=urldecode+bash

(必须引用参数)

答案 18 :(得分:-1)

Python答案的略微修改版本,它接受单行中的输入和输出文件。

cat inputfile.txt | python -c "import sys, urllib as ul; print ul.unquote(sys.stdin.read());" > ouputfile.txt

答案 19 :(得分:-1)

只是想分享这个其他解决方案,纯粹的bash:

encoded_string="Mozilla%2F5.0%20%28Macintosh%3B%20U%3B%20Intel%20Mac%20OS%20X%2010.6%3B%20en"
printf -v encoded_string "%b" "${encoded_string//\%/\x}"
echo $encoded_string

答案 20 :(得分:-1)

面对类似的问题,我最初的想法是在一个脚本中使用来自PHP的urldecode来读取stdin或类似的东西,但后来我遇到了这个想法。所有答案似乎都有很多文字,但没有真正的解决方案。这个想法虽然合理,但非常容易上班:

$ mpc | sed -e '1! d'
http://e.org/play.php?name=/Black%20Sun%20Empire%20-%20Sideways%20%28Feat.%20Illy%20Emcee%29

$ basename "$(echo -e `mpc | sed -e '1! d' -e 's/%/\\\\x/g'`)"
Black Sun Empire - Sideways (Feat. Illy Emcee)

使其工作的关键是双重逃避\ x(已经提到过这一点)。

答案 21 :(得分:-3)

$ uenc='H%C3%B6he %C3%BCber%20dem%20Meeresspiegel'
$ utf8=$(printf "${uenc//%/\\x}")
$ echo $utf8
Höhe über dem Meeresspiegel
$