[TL, DR: use a versão urlencode_grouped_case
no último bloco de código.]
O Awk pode fazer a maior parte do trabalho, exceto que, irritantemente, não é possível converter um caractere em seu número. Se od
estiver presente no seu dispositivo, você pode usá-lo para converter todos os caracteres (mais precisamente, bytes) no número correspondente (escrito em decimal, para que o awk possa lê-lo) e usar o awk para converter caracteres válidos literais e caracteres citados na forma apropriada.
urlencode_od_awk () {
echo "$1" | od -t d1 | awk '{
for (i = 2; i <= NF; i++) {
printf(($i>=48 && $i<=57) || ($i>=65 &&$i<=90) || ($i>=97 && $i<=122) ||
$i==45 || $i==46 || $i==95 || $i==126 ?
"%c" : "%%%02x", $i)
}
}'
}
Se o seu dispositivo não tiver od
, você poderá fazer tudo dentro do shell; Isso ajudará significativamente no desempenho (menos chamadas para o programa externo - nenhum se printf
for incorporado) e será mais fácil de escrever corretamente. Acredito que todas as shells do Busybox suportam a construção ${VAR#PREFIX}
para cortar um prefixo de uma string; use-o para remover repetidamente o primeiro caractere da string.
urlencode_many_printf () {
string=$1
while [ -n "$string" ]; do
tail=${string#?}
head=${string%$tail}
case $head in
[-._~0-9A-Za-z]) printf %c "$head";;
*) printf %%%02x "'$head"
esac
string=$tail
done
echo
}
Se printf
não for um utilitário interno, mas um utilitário externo, você ganhará novamente o desempenho invocando-o apenas uma vez para toda a função, em vez de uma vez por caractere. Crie o formato e os parâmetros e faça uma única chamada para printf
.
urlencode_single_printf () {
string=$1; format=; set --
while [ -n "$string" ]; do
tail=${string#?}
head=${string%$tail}
case $head in
[-._~0-9A-Za-z]) format=$format%c; set -- "$@" "$head";;
*) format=$format%%%02x; set -- "$@" "'$head";;
esac
string=$tail
done
printf "$format\n" "$@"
}
Isso é ótimo em termos de chamadas externas (existe uma única, e você não pode fazê-lo com construções de shell puro, a menos que esteja disposto a enumerar todos os caracteres que precisam ser escapados). Se a maioria dos caracteres no argumento deve ser passada inalterada, você pode processá-los em um lote.
urlencode_grouped_literals () {
string=$1; format=; set --
while
literal=${string%%[!-._~0-9A-Za-z]*}
if [ -n "$literal" ]; then
format=$format%s
set -- "$@" "$literal"
string=${string#$literal}
fi
[ -n "$string" ]
do
tail=${string#?}
head=${string%$tail}
format=$format%%%02x
set -- "$@" "'$head"
string=$tail
done
printf "$format\n" "$@"
}
Dependendo das opções de compilação, [
(a.k.a. test
) pode ser um utilitário externo. Estamos usando apenas para correspondência de strings, o que também pode ser feito dentro do shell com a construção case
. Aqui estão as duas últimas abordagens reescritas para evitar que test
seja incorporado, primeiro caractere por caractere:
urlencode_single_fork () {
string=$1; format=; set --
while case "$string" in "") false;; esac do
tail=${string#?}
head=${string%$tail}
case $head in
[-._~0-9A-Za-z]) format=$format%c; set -- "$@" "$head";;
*) format=$format%%%02x; set -- "$@" "'$head";;
esac
string=$tail
done
printf "$format\n" "$@"
}
e copiando cada segmento literal em um lote:
urlencode_grouped_case () {
string=$1; format=; set --
while
literal=${string%%[!-._~0-9A-Za-z]*}
case "$literal" in
?*)
format=$format%s
set -- "$@" "$literal"
string=${string#$literal};;
esac
case "$string" in
"") false;;
esac
do
tail=${string#?}
head=${string%$tail}
format=$format%%%02x
set -- "$@" "'$head"
string=$tail
done
printf "$format\n" "$@"
}
Eu testei no meu roteador (processador MIPS, distribuição baseada em DD-WRT, BusyBox com ash,% externo printf
e [
). Cada versão é uma melhoria de velocidade notável em relação à anterior. Mover-se para um único garfo é a melhoria mais significativa; é o que faz a função responder quase instantaneamente (em termos humanos), ao contrário de alguns segundos para um parâmetro de URL longo e realista.