シェルの「機能」の1つは、前にゼロの数字を8進数として解釈することです。
$ echo "$((00100))"
64
ただし、多くのシェルではこの「機能」を無効にする方法がないため、一連の数字を10進数(または他の基数)として解釈することは困難になります。
単一の数値のみを変換する必要がある場合は、トリミングを実行できるいくつかの外部プログラムがあります。
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移植可能なスクリプトを作成するときは、どちらも役に立ちません。この場合、示されているように先行ゼロを削除する必要があります。
答え2
次の操作を1行で実行できます。
$ a=-00100; a=${a%"${a#[+-]}"}${a#"${a%%[!0+-]*}"}; a=${a:-0}
$ echo "$a"
-100
1000回繰り返しに0.0482しかかかりません。これは外部プログラムを使用するよりも100倍少ない数値です。
これは2つのパラメータ拡張に基づいています。
- シンボル抽出:
${a#[+-]}
最初の文字を削除します(記号の場合)。${a%"${a#[+-]}"}
フラグの場合は、最初のフラグを保持します。
- すべての先行記号および/または0を削除します。
${a%%[!0+-]*}
任意の場所(0、+、または-を除く)から開始と終了を削除します。${a#"${a%%[!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
したがって、私のシステムでは、速度はまったく同じであり、騒々しくありません。