0埋め
日付とかをファイル名に付ける際、桁数を合わせるために
file_name=${year}$(printf "%02d" ${month})$(printf "%02d" ${day})
みたいにすると、year=2019、month=9、day=8だとすると
file_name=20190908
となります。"%02d"はdが整数値で02で2文字分確保、足りない分は左側を0で埋める、という意味になります。
%2dだと足りない分は空白になります。
dの代わりにsなら通常の文字列を扱うようになります。
0始まりの文字の8進法扱い
シェルスクリプトでは変数としては全て単なる文字列で、
printfなどではそのコマンドの中で整数などを判断しますが、
整数でも8進法や16進法を認識してくれるものもあります。
printfとかだと、
0で始まる数字: 8進法0xで始まる数字: 16進法
と判断されます。
$ printf "%d\n" 10
10
$ printf "%d\n" 010
8
$ printf "%d\n" 0x10
16
この時、09などは8進法でありえない数字なのでエラーになり、00を返します。
$ printf "%02d" 09
bash: printf: 09: invalid number
00
単にaとか文字列を渡した場合も同様のエラーが出ます。
逆に、printfの出力整形で、dは10進法の出力を表していて、oだと8進法、xで16進法を表示することも出来ます。
$ printf "%d\n" 10
10
$ printf "%o\n" 10
12
$ printf "%x\n" 10
a
これ以外にも算術式でも0/0xが使えます。
$ echo $((010+0x10))
24
ただし、exprやbcでは通常0がついてても0を除いた整数値として扱うので注意。
$ expr 010 + 010
20
$ expr 010 + 0x10
expr: not a decimal number: '0x10'
$ echo 010 + 010|bc
20
$ echo 010 + 0x10|bc
(standard_in) 1: parse error
bcではibase=8とかすると入力を8進法にすることができますが、この場合は0などを付けずに入力します。
$ echo "ibase=8;010 + 010"|bc
16
ibase=16なら16進法でこの場合も入力数字に0xとかを付けずに。
(付けるとエラーになります。)
逆に出力を16進法とかに変えたければobase=16とかを付けます。
$ echo "obase=16;5+5"|bc
A
起こりうる問題
この様な数字の扱いですが、Bashなどのシェルスクリプトでは 変数自体は単なる文字列なのできちんと確認しないとちょっと問題が起こります。
例えば月を最終的に0埋めの文字列にしたい時。
整形するには
month=$(printf "%02d" {$month})
とか変換すれば出来ます。
ただし、 月をスクリプトの引数で設定するようにしたりする時、
人によっては親切に09などと0を付けて入力してくれるかもしれません。
この場合、上の式で
bash: printf: 09: invalid number
というエラーが出てしまいます。
また、dateコマンドとかで
date +"%m"
とかすると
09
と0埋めした状態で返してくれます。
これを上の式に渡せば上の様にエラーになります。
ただ、この問題が起こるのは8月、9月だけなので
それ以外の月に作ってテストして大丈夫だと思っていると
8月に入った段階でエラーになってしまいます。
また、エラーになってもbash -eとかで終了させる様な設定にしてないと
00という文字列を使って実行してしまい予期せぬ動作をするので危険です。
月の指定であれば08, 09のみが問題で、その他の数字はそのまま正しく出るので良いのですが、
3桁以上の場合とかなら012は
$ printf "%03d" 012
010
となるのでこれも予期せぬ結果になってしまいます。
なのでこの辺りの0埋め数字を扱う際にはそれぞれのタイミングできちんと確認する必要があります。
対処法
0がついてる場合に外してあげてから渡せば問題がなくなります。
上の月の例であれば0が1つつくだけなので
$ month=${month#0}
とすれば0が付いてれば消されます。
もうちょっと一般的にやりたければ
$ month=$(echo $month|sed 's/^0*//')
みたいに左の0をすべて消すようにsedとかを使えばOK。
ちょっと別の話になりますが、Linux(GNU)だと
$ month=$(echo $month|sed 's/^0\+//')
とすれば0の1回以上の繰り返しに対応しますが、 Mac(BSD)だとこの正規表現は使えません。
今回の場合は0*として0の0回以上の繰り返しに対応させても同じことなので
こちらにしておいたほうが移植性があがります。
これらの様に数字をきちんと処理しても良いですが、 そもそもこの例では数字を計算しているわけではないので 単に文字列として
$ printf "%02s" $month
09
とすればmonthが0でも09でも09という出力になります。
12とかもそのまま12になります。
単に数字を整形したいだけ、というのであればこ方法が一番確実かと思います。
その他の言語での8進法などの扱い
Pythonだと
0b始まりで2進法0o始まりで8進法0x始まりで16進法
を表します。
>>> 10
10
>>> 0b10
2
>>> 0o10
8
>>> 0x10
16
10進法を各進法で表示したければbin、oct、hexという組み込み関数が使えます。
>>> bin(10)
'0b1010'
>>> oct(10)
'012'
>>> hex(10)
'0xa'
これらの関数で出力されるのは文字列です。(数字じゃないので注意。)
C++だと
0b始まりで2進法0始まりで8進法0x始まりで16進法
を表します。
1 2 3 4 5 6 7 8 9 10 | |
で
10
2
8
16
となります。
Pythonと違って8進法で0o10とかするとエラーになります。
error: unable to find numeric literal operator 'operator""o10'
coutで各進法で表示したければ
マニピュレーターを使います。
std::showbaseで0や0xの表示をし、std::octやstd::hexで進法を指定します。
prefixが必要ないならstd::showbaseなしで。(noshowbaseで元に戻せします。)
2進法ではhexやoctの様なマニピュレーターは無いのでからりにbitsetを使い変換し、
必要であれば0bなどは自分で付ける必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
出力は
10
012
0xa
0b1010
12
a
となります。
