쉘의 "기능" 중 하나는 앞에 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
에서는 mksh
이 posix
옵션이 제어됩니다(기본적으로 꺼져 있음).
$ mksh -c 'echo "$((010))"'
10
$ mksh -o posix -c 'echo "$((010))"'
8
Bash에는 이를 끌 수 있는 옵션이 없지만 ksh 구문을 사용하여 10진수로 강제 해석할 수 있지만 (ksh 및 zsh에서도 작동) 및 ( 확장에서 볼 수 있듯이) Yield 에서는 $((10#010))
작동하지 않습니다 . 필요합니다 (또는 근거가 잘못된 불만사항과의 호환성을 위해).bash
mksh -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배 적은 수치입니다.
이는 두 개의 매개변수 확장을 기반으로 합니다.
- 기호 추출:
${a#[+-]}
첫 번째 문자를 제거합니다(기호인 경우).${a%"${a#[+-]}"}
플래그인 경우 첫 번째 플래그를 유지합니다.
- 모든 선행 기호 및/또는 0을 제거합니다.
${a%%[!0+-]*}
임의의 위치(0, + 또는 - 제외)에서 시작 및 끝을 삭제합니다.${a#"${a%%[!0+-]*}"}
위의 내용, 즉 모든 선행 0과 기호를 제거합니다.
그러면 기호가 선택되고 앞에 오는 0이 모두 제거됩니다. 그러나 (오류 없이) 다음이 허용됩니다.
- 몇 가지 주요 징후.
- 선행 기호 및 0 이후의 모든 문자입니다.
- "범위를 벗어난"(너무 큰) 숫자입니다.
이러한 테스트가 필요하다면 계속 읽어보세요.
플래그 수는 다음을 사용하여 테스트할 수 있습니다.
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
따라서 내 시스템에서는 속도가 정확히 동일하며 소란스럽지 않습니다.