função urlencode

4

Eu preciso de uma maneira de codificar as strings de URL com um script de shell em um dispositivo OpenWRT executando a versão antiga do busybox. Agora acabei com o seguinte código:

urlencode() {
echo "$@" | awk -v ORS="" '{ gsub(/./,"&\n") ; print }' | while read l
do
  c="'echo "$l" | grep '[^-._~0-9a-zA-Z]''"
  if [ "$l" == "" ]
  then
    echo -n "%20"
  else
    if [ -z "$c" ]
    then
      echo -n "$l"
    else
      printf %%%02X \'"$c"
    fi
  fi
done
echo ""
}

Isso funciona mais ou menos bem, mas há algumas falhas:

  1. Alguns caracteres são ignorados, como "\", por exemplo.
  2. O resultado é um caractere de retorno por caractere, por isso é extremamente lento. Demora cerca de 20 segundos para codificar url apenas algumas strings dentro de um lote.

Minha versão do bash não suporta substring como este $ {var: x: y}.

    
por Serg 08.01.2013 / 18:35

1 resposta

6

[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.

    
por 09.01.2013 / 03:36