前に0がついた整数(移植可能)?

前に0がついた整数(移植可能)?

シェルの「機能」の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))動作しません。必要です(または根拠が間違った苦情との互換性のため)。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移植可能なスクリプトを作成するときは、どちらも役に立ちません。この場合、示されているように先行ゼロを削除する必要があります。

答え2

次の操作を1行で実行できます。

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

1000回繰り返しに0.0482しかかかりません。これは外部プログラムを使用するよりも100倍少ない数値です。

これは2つのパラメータ拡張に基づいています。

  1. シンボル抽出:
    • ${a#[+-]}最初の文字を削除します(記号の場合)。
    • ${a%"${a#[+-]}"}フラグの場合は、最初のフラグを保持します。
  2. すべての先行記号および/または0を削除します。
    • ${a%%[!0+-]*}任意の場所(0、+、または-を除く)から開始と終了を削除します。
    • ${a#"${a%%[!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

したがって、私のシステムでは、速度はまったく同じであり、騒々しくありません。

関連情報