Urlencode 기능

Urlencode 기능

이전 버전의 busybox를 실행하는 OpenWRT 장치에서 쉘 스크립트를 사용하여 문자열을 URL 인코딩하는 방법이 필요합니다. 이제 다음 코드를 얻었습니다.

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 ""
}

이는 다소 잘 작동하지만 몇 가지 결함이 있습니다.

  1. "\"와 같은 특정 문자는 건너뜁니다.
  2. 결과는 한 문자씩 반환되므로 매우 느립니다. 일괄 처리에서 몇 개의 문자열만 URL 인코딩하는 데 약 20초가 걸립니다.

내 bash 버전은 ${var:x:y}와 같은 하위 문자열을 지원하지 않습니다.

답변1

[TL,DR: urlencode_grouped_case마지막 코드 블록의 버전을 사용하세요. ]

awk는 대부분의 작업을 수행할 수 있지만 짜증나게도 문자를 숫자로 변환하는 방법이 부족합니다. od장치에 존재하는 경우 이를 사용하여 모든 문자(보다 정확하게는 바이트)를 해당 숫자(awk가 읽을 수 있도록 10진수로 기록됨)로 변환한 다음 awk를 사용하여 유효한 문자를 다시 리터럴로 변환하고 인용된 문자를 올바른 형식으로 변환합니다.

urlencode_od_awk () {
  echo -n "$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)
      }
    }'
}

장치에 가 없으면 od셸 내부에서 모든 작업을 수행할 수 있습니다. 이렇게 하면 성능이 크게 향상되고(외부 프로그램에 대한 호출 수가 줄어들고 printf내장 프로그램인 경우 없음) 올바르게 작성하기가 더 쉬워집니다. 나는 모든 Busybox 쉘이 ${VAR#PREFIX}문자열에서 접두어를 잘라내는 구문을 지원한다고 생각합니다. 이를 사용하여 문자열의 첫 번째 문자를 반복적으로 제거합니다.

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
}

printf내장 유틸리티가 아니고 외부 유틸리티인 경우 문자당 한 번이 아닌 전체 기능을 한 번만 호출하면 다시 성능을 얻을 수 있습니다 . 형식과 매개변수를 작성한 다음 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" "$@"
}

외부 호출에 관한 한 이는 최적입니다(호출은 하나만 있으며 이스케이프해야 하는 모든 문자를 기꺼이 열거하지 않는 한 순수 쉘 구성으로는 이를 수행할 수 없습니다). 매개변수의 문자 대부분을 변경하지 않고 전달해야 하는 경우 이를 일괄 처리할 수 있습니다.

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" "$@"
}

컴파일 옵션에 따라 [(일명 test)은 외부 유틸리티일 수 있습니다. 문자열 일치에만 사용하며 구문을 사용하여 셸에서도 수행할 수 있습니다 case. test내장 메소드를 피하기 위해 재정의된 마지막 두 메소드는 문자별로 먼저입니다:

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" "$@"
}

그리고 각 텍스트 필드를 일괄 복사합니다.

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" "$@"
}

내 라우터(MIPS 프로세서, DD-WRT 기반 배포판, printfAsh가 포함된 BusyBox, 외부 및 [. 각 버전은 이전 버전에 비해 속도가 크게 향상되었습니다. 단일 포크로 이동하는 것은 가장 중요한 개선 사항입니다. 이는 몇 초 후에 실제 긴 URL 매개변수에 응답하는 대신 함수가 거의 즉각적으로(사람의 관점에서) 응답하도록 만듭니다.

위의 코드는 이국적인 지역에서는 실패할 수 있습니다(라우터에서는 거의 발생하지 않음). export LC_ALL=C기본이 아닌 로케일을 사용하는 경우 이는 필요할 수 있습니다.

답변2

busybox유틸리티는 일반적으로 해당 POSIX 유틸리티의 버전을 제거한 반면 (종종 GNU 확장 포함), busybox는 실제로 확장 기능이 포함된 경우에도 awk거의 호환되는 POSIX API의 본격적인 버전입니다 .awk

awk따라서 한 번의 호출로 모든 작업을 수행 할 수 있어야 합니다 .

urlencode() {
  LC_ALL=C awk -- '
    BEGIN {
      for (i = 1; i <= 255; i++) hex[sprintf("%c", i)] = sprintf("%%%02X", i)
    }
    function urlencode(s,  c,i,r,l) {
      l = length(s)
      for (i = 1; i <= l; i++) {
        c = substr(s, i, 1)
        r = r "" (c ~ /^[-._~0-9a-zA-Z]$/ ? c : hex[c])
      }
      return r
    }
    BEGIN {
      for (i = 1; i < ARGC; i++)
        print urlencode(ARGV[i])
    }' "$@"
}

그러면 각 매개변수의 URL 인코딩이 별도의 출력 줄에 인쇄됩니다.

답변3

게시물을 기반으로여기, 보다 우아한 솔루션:

(가벼우면서도 유연한 명령줄 JSON 프로세서)와 Perl이 설치되어 있는 경우 jq다음 방법을 사용할 수 있습니다.참고하세요-n명령 매개변수 echo:

url="http://example.com/resource?param1=value&param2=foo/bar"
encoded_url=$(echo -n "$url" | jq -s -R -r @uri)

관련 정보