앞에 0이 붙은 정수(이식 가능)?

앞에 0이 붙은 정수(이식 가능)?

쉘의 "기능" 중 하나는 앞에 0이 있는 숫자를 8진수로 해석하는 것입니다.

$ echo "$((00100))"
64

그러나 많은 쉘에서는 이 "기능"을 비활성화할 수 있는 방법이 없으므로 일련의 숫자를 십진수(또는 기타 기수)로 해석하는 것이 어려워집니다.

단일 숫자만 변환해야 하는 경우 트리밍을 수행할 수 있는 여러 외부 프로그램이 있습니다.

expr "00100" + 0 
echo "00100" | sed 's/^0*//'
echo "00100" | grep -o '[^0].*$'
echo "00100" | awk '{print int($0)}'
echo "00100" | perl -pe '$_=int."\n";'

하지만 필요할 때마다 실행하려면 시간이 좀 걸립니다. 여러 호출을 통해 이러한 외부 도구를 누적적으로 사용하면 대기 시간이 상당히 길어질 수 있습니다. 발생한 지연을 측정하기 위해 호출을 1000회 이상 반복하면 다음과 같은 결과를 얻을 수 있습니다(초 단위).

expr      1.934
sed       3.450
grep      3.775
awk       5.291
perl      5.064

물론(expr 제외) 대부분의 도구는 1000줄의 파일을 처리할 수 있습니다.

sed  file 0.004
grep file 0.003
awk  file 0.007
perl file 0.006

1000개의 개별 값을 모두 동일한 시점에 사용할 수 있는 경우입니다.
그러나 실제로는 그렇지 않습니다. 그렇다면 남은 답변은 다음과 같습니다.

파일의 목록이 아닌 각 개별 정수에 대해 외부 도구를 호출하는 것보다 더 빠른 정수를 추출하는 기본(셸용) 방법이 있습니까?

각 호출이 합산되고 대기 시간이 상당히 길어집니다.

숫자에 선행 기호가 있을 수 있고 잘못된 숫자를 거부하려는 경우 처리가 더 복잡해집니다.

답변1

POSIX에서는 8 확장이 필요 하지만 $((010))일부 쉘은 일관성 모드가 아닌 한 기본적으로(또는 특정 컨텍스트에서만) 이를 수행하지 않습니다.특징당신은 보통 원하지 않습니다.

사용되면 zsh옵션에 의해 제어됩니다 octalzeroes(sh/ksh 에뮬레이션을 제외하고 기본적으로 꺼짐).

$ zsh -c 'echo $((010))'
10
$ zsh -o octalzeroes -c 'echo $((010))'
8
$ (exec -a sh zsh -c 'echo "$((010))"')
8

에서는 mkshposix옵션이 제어됩니다(기본적으로 꺼져 있음).

$ mksh -c 'echo "$((010))"'
10
$ mksh -o posix -c 'echo "$((010))"'
8

Bash에는 이를 끌 수 있는 옵션이 없지만 ksh 구문을 사용하여 10진수로 강제 해석할 수 있지만 (ksh 및 zsh에서도 작동) 및 ( 확장에서 볼 수 있듯이) Yield 에서는 $((10#010))작동하지 않습니다 . 필요합니다 (또는 근거가 잘못된 불만사항과의 호환성을 위해).bashmksh -o posix$((10#-010))10#0 - 010$((-10#-010))-8$((-10#010))$((- 10#010))zsh-10

$ bash -c 'echo "$((10#010))"'
10

다음과 비교해보세요 ksh93:

$ ksh93 -c 'echo "$((010))"'
8
$ ksh93 -c '((a = 010)); echo "$a"'
8

그리고:

$ ksh93 -c 'a=010; echo "$((a))"'
10
$ ksh93 -c 'printf "%d\n" 010'
10
$ ksh93 -c 'let a=010; echo "$a"'
10
$ ksh93 -c 'echo "$((010e0))"'
10
$ ksh93 -o letoctal -c 'let a=010; echo "$a"'
8

따라서 적어도 이러한 쉘에 대해 특별히 코딩하는 경우 "버그가 있는 기능"을 해결할 수 있는 몇 가지 방법이 있습니다.

그러나 POSIX 이식 가능한 스크립트를 작성할 때 이 중 어느 것도 도움이 되지 않습니다. 이 경우 표시된 대로 선행 0을 제거해야 합니다.

답변2

다음과 같은 작업을 한 줄로 수행할 수 있습니다.

$ a=-00100; a=${a%"${a#[+-]}"}${a#"${a%%[!0+-]*}"}; a=${a:-0}
$ echo "$a"
-100

1000회 반복에 0.0482밖에 걸리지 않습니다. 이는 외부 프로그램을 사용하는 것보다 100배 적은 수치입니다.

이는 두 개의 매개변수 확장을 기반으로 합니다.

  1. 기호 추출:
    • ${a#[+-]}첫 번째 문자를 제거합니다(기호인 경우).
    • ${a%"${a#[+-]}"}플래그인 경우 첫 번째 플래그를 유지합니다.
  2. 모든 선행 기호 및/또는 0을 제거합니다.
    • ${a%%[!0+-]*}임의의 위치(0, + 또는 - 제외)에서 시작 및 끝을 삭제합니다.
    • ${a#"${a%%[!0+-]*}"}위의 내용, 즉 모든 선행 0과 기호를 제거합니다.

그러면 기호가 선택되고 앞에 오는 0이 모두 제거됩니다. 그러나 (오류 없이) 다음이 허용됩니다.

  1. 몇 가지 주요 징후.
  2. 선행 기호 및 0 이후의 모든 문자입니다.
  3. "범위를 벗어난"(너무 큰) 숫자입니다.

이러한 테스트가 필요하다면 계속 읽어보세요.


플래그 수는 다음을 사용하여 테스트할 수 있습니다.

signs=${a%%[!+-]*} 
[ ${#signs} -gt 1 ] && echo "$0: Invalid number $a: Too many signs"

허용되는 문자 유형은 다음 명령을 사용하여 확인할 수 있습니다.

num=${a#"${a%%[!0+-]*}"}

any=${num%%[!0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@_]*}
[ "$any" != "$num" ] && echo "$0: Invalid number $a"

hex=${num%%[!0123456789abcdefABCDEF]*}
[ "$hex" != "$num" ] && echo "$0: Invalid hexadecimal number $a"

dec=${num%%[!0123456789]*}
[ "$dec" != "$num" ] && echo "$0: Invalid decimal number $a"

마지막으로 "범위를 벗어났습니다" 숫자 경고를 인쇄하는 기능을 활용할 수 있습니다 printf(printf가 이해하는 베이스에 대해서만).

printf '%d' $sign$dec >/dev/null                            # for a decimal number
printf '%d' "${sign}0x$hex" >/dev/null                      # for hex numbers

예, 모든 printf가 사용하는 %d것은 오타가 아닙니다.

예, 위의 모든 사항은 printf.

답변3

내 시스템의 x1000 예는 다음과 같습니다.

$ cat shell.sh
#!/bin/dash
q=1
while [ "$q" -le 1000 ]
do
  z=-00100
  z=${z%"${z#[+-]}"}${z#"${z%%[!0+-]*}"}
  z=${z:-0}
  echo "$z"
  q=$((q + 1))
done

결과:

$ time ./shell.sh >/dev/null
real    0m0.047s

이제 sed 예제에 대한 질문이 있습니다. 파일에 대한 예제를 보았지만 파일 사용이 허용되지 않는 명확한 이유를 알 수 없습니다. 또한 파이프를 사용한 예는 파이프가 필요하지 않고 sed를 1000번 호출할 필요도 없기 때문에 문제가 됩니다. 어떤 이유로든 파일을 사용할 수 없는 경우 문서는 다음과 같습니다.

cat > sed.sh <<alfa
sed 's/^0*//' <<bravo
$(yes 00100 | head -1000)
bravo
alfa

결과:

$ time ./sed.sh >/dev/null
real    0m0.047s

따라서 내 시스템에서는 속도가 정확히 동일하며 소란스럽지 않습니다.

관련 정보