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

use*_*717 37 bash shell awk sed urldecode

我有一个文件,其中包含编码的用户代理列表.例如:

Mozilla%2F5.0%20%28Macintosh%3B%20U%3B%20Intel%20Mac%20OS%20X%2010.6%3B%20en
Run Code Online (Sandbox Code Playgroud)

我想要一个shell脚本,它可以读取该文件并写入带有解码字符串的新文件.

Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en
Run Code Online (Sandbox Code Playgroud)

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

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

我的脚本看起来像:

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

小智 47

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

$ urldecode() { : "${*//+/ }"; echo -e "${_//%/\\x}"; }
Run Code Online (Sandbox Code Playgroud)

它可能看起来像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
Run Code Online (Sandbox Code Playgroud)

或者在变量赋值中,如下:

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

  • @JustinPutney` $ {*// + /}`将用空格替换*all*`+`,`$ {_ //%/ \\ x}`将用'\ x`替换*all*`%`. (3认同)
  • 只想提一下这对我来说太慢了;对于 50k url,`bash: 0m3.767s python: 0m0.200s`(python 下面一行:/sf/answers/1518542161/) (3认同)
  • 想要更多关于模式替换的解释。这个函数对我有用,但它以一种使文件路径不能与解压缩函数一起工作的方式改变了字符。 (2认同)
  • @nhed – `:` 在 bash 中是一个无操作,但是这段代码使用了 `$_` 的值,它“扩展到前一个简单命令的最后一个参数”(也就是说,这是一个 perl-级别混淆)。它会更清晰,如 `urldecode() { local i="${*//+/ }"; echo -e "${i//%/\\x}"; (将每个“+”替换为空格,然后将每个“%”替换为“\x”,以便 bash 知道正确解释转义序列)。 (2认同)

小智 18

GNU awk

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

要么

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

使用awk printf来urldecode文本


use*_*717 11

这似乎对我有用.

#!/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
Run Code Online (Sandbox Code Playgroud)

用空格替换'+',用'\ 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

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


Jay*_*Jay 11

如果你是一个python开发人员,这可能是优先考虑的

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

urllib专业处理它


bre*_*dan 11

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

while read; do echo -e ${REPLY//%/\\x}; done
Run Code Online (Sandbox Code Playgroud)

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

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

while read; do echo -e ${REPLY//%/\\x}; done < file
Run Code Online (Sandbox Code Playgroud)

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

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

以上不会将'+'更改为''.将'+'更改为'',就像客人的回答一样:

while read; do : "${REPLY//%/\\x}"; echo -e ${_//+/ }; done
Run Code Online (Sandbox Code Playgroud)
  • :是BASH内置命令.在这里它只需要一个参数并且不做任何事情.
  • 双引号使一切都在一个参数内.
  • _是一个特殊参数,在参数扩展后等于上一个命令的最后一个参数.这是REPLY'%'的所有实例都替换为'\ x'的值.
  • ${_//+/ } 用''替换'+'的所有实例.

这仅使用BASH并且不启动任何其他进程,类似于guest的答案.


Rob*_*ade 8

用于 url 解码的 bash 习惯用法

\n

下面是一个 bash 习惯用法,用于对 variabe 中保存的字符串进行 url 解码x并将结果分配给变量y

\n
: "${x//+/ }"; printf -v y \'%b\' "${_//%/\\\\x}"\n
Run Code Online (Sandbox Code Playgroud)\n

与接受的答案不同,它在分配期间保留尾随换行符。(尝试将 url 解码的结果分配v%0A%0A%0A给变量。)

\n

它也很快。将 url 解码结果分配给变量比接受的答案快6700% 。

\n

警告:bash 变量不可能包含 NUL。例如,任何尝试解码%00并将结果分配给变量的 bash 解决方案都将不起作用。

\n

基准详情

\n

函数.sh

\n
: "${x//+/ }"; printf -v y \'%b\' "${_//%/\\\\x}"\n
Run Code Online (Sandbox Code Playgroud)\n

成语.sh

\n
#!/bin/bash\nurldecode() { : "${*//+/ }"; echo -e "${_//%/\\\\x}"; }\nx=%21%20\nfor (( i=0; i<5000; i++ )); do\n  y=$(urldecode "$x")\ndone\n
Run Code Online (Sandbox Code Playgroud)\n
#!/bin/bash\nx=%21%20\nfor (( i=0; i<5000; i++ )); do\n  : "${x//+/ }"; printf -v y \'%b\' "${_//%/\\\\x}"\ndone\n
Run Code Online (Sandbox Code Playgroud)\n

如果你真的想要一个功能......

\n

如果你真的想要一个函数,比如说出于可读性的原因,我建议如下:

\n
$ hyperfine --warmup 5 ./function.sh ./idiom.sh\nBenchmark #1: ./function.sh\n  Time (mean \xc2\xb1 \xcf\x83):      2.844 s \xc2\xb1  0.036 s    [User: 1.728 s, System: 1.494 s]\n  Range (min \xe2\x80\xa6 max):    2.801 s \xe2\x80\xa6  2.907 s    10 runs\n \nBenchmark #2: ./idiom.sh\n  Time (mean \xc2\xb1 \xcf\x83):      42.4 ms \xc2\xb1   1.0 ms    [User: 40.7 ms, System: 1.1 ms]\n  Range (min \xe2\x80\xa6 max):    40.5 ms \xe2\x80\xa6  44.8 ms    64 runs\n \nSummary\n  \'./idiom.sh\' ran\n   67.06 \xc2\xb1 1.76 times faster than \'./function.sh\'\n
Run Code Online (Sandbox Code Playgroud)\n

示例 1:将结果打印到 stdout

\n
x=\'v%0A%0A%0A\'\nurldecode "$x" | od -An -tx1\n
Run Code Online (Sandbox Code Playgroud)\n

结果:

\n
 76 0a 0a 0a\n
Run Code Online (Sandbox Code Playgroud)\n

示例 2:将解码结果分配给 shell 变量:

\n
x=\'v%0A%0A%0A\'\nurldecode -v y "$x"\necho -n "$y" | od -An -tx1\n
Run Code Online (Sandbox Code Playgroud)\n

(相同的结果)

\n

该函数虽然不如上面的习惯用法那么快,但由于不涉及子 shell,因此在执行作业时仍然比接受的答案快 1300%。此外,如示例的输出所示,由于不涉及命令替换,它保留了尾随换行符。

\n

  • @F.Hauri 这非常聪明!是的,这是我提供的功能的一个很好的替代方案。两者都为用户提供了分配变量或打印到标准输出的灵活性。 (2认同)

Ste*_*las 7

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

通过作为备份扩展,-i将文件就地更新(某些sed实现已从中借用perl).back.

s/x/y/ex用perl代码的e估值替代y.

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

另一种方法pack是使用chr(hex($1)):

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

如果有的话,你也可以使用uri_unescape()来自URI::Escape:

perl -pi.back -MURI::Escape -e 'y/+/ /;$_=uri_unescape$_' ./*.log
Run Code Online (Sandbox Code Playgroud)

  • 这个例子如果再加上几句话解释就更好了。 (2认同)

Joh*_*web 6

正如@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
Run Code Online (Sandbox Code Playgroud)

我不会混合使用 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')
Run Code Online (Sandbox Code Playgroud)


Jan*_*sen 6

用于在本机Bash(原始源)中执行此操作的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}"
}
Run Code Online (Sandbox Code Playgroud)

如果要对文件内容进行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
Run Code Online (Sandbox Code Playgroud)

  • @StephaneChazelas:我相信在正确的 % 编码字符串中不允许使用反斜杠 (2认同)

frc*_*rcn 6

基于其他一些答案,但对于 POSIX 世界,可以使用以下函数:

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

它使用printf '%b\n'因为没有echo -e并中断sed调用以使其更易于阅读,强制-E能够使用\1. 它还强制接下来的内容%看起来像一些十六进制代码。


小智 5

如果您在服务器上安装了php,则可以使用URL编码的字符串轻松地“捕获”或什至“拖尾”任何文件。

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


jam*_*amp 5

只是想分享另一个解决方案,纯 bash:

encoded_string="Mozilla%2F5.0%20%28Macintosh%3B%20U%3B%20Intel%20Mac%20OS%20X%2010.6%3B%20en"
printf -v decoded_string "%b" "${encoded_string//\%/\\x}"
echo $decoded_string
Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en
Run Code Online (Sandbox Code Playgroud)

  • 你在 `\x` 之前漏掉了一个反斜杠!: `printf -v Updated_string "%b" "${encoded_string//\%/\\x}"`!但你是第一个建议 `printf -v` 的人! (2认同)