rcmdnk's blog

CEREMONY [Analog]

久々に新しく知ったGNU/BSDの違い。

printfでのパディング

シェルでprintfというコマンドを使うとechoではできないフォーマットを指定したりすることも出来ます。

その中で、フォーマットの中で%dが数字、%sが文字を表していて後ろから引数として渡してそこに埋められます。

1
2
$ printf "X %s X %d X\n" abc 123
X abc X 123 X

これらのsとかdとかの記号の前に数字を入れるとそれの幅だけを予約し、空いた分は空白として出力します。

1
2
$ printf "X%5sX\n" abc
X  abcX

マイナスを入れると後ろを空ける。

1
2
$ printf "X%-5sX\n" abc
Xabc  X

予約した分よりも入力の文字が多い場合にはその文字列分だけ場所をとります。

1
2
$ printf "X%5sX\n" abcdef
XabcdefX

この辺の空白埋めは%dでも同じです。 GNU/BSDでも少なくともここに書いてあることは同じようになります。

printfでの整数のゼロパディング

%dに対して、パディング記法で数字の前に0を置くと0で埋めることができます。

1
2
$ printf "X%05dX\n" 111
X00111X

もし、数字がマイナスの場合、マイナスはゼロの左側に出ます。

1
2
$ printf "X%05dX\n" -111
X-0111X

マイナスの分も1つ文字を消化した上で。

この0.に変えても同じようにゼロ埋めになります。

一方、記法の方で0の前にマイナスを付けると 単に右側の空白埋めになります。

1
2
$ printf "X%-05dX\n" 111
X111  X

ここは単なる空白の場合と異なりますが、ここでは5の前の0-のオプションが 重複して、-の方が優先された、という状態です。

数字のゼロ埋めなので右に埋めると数字が根本的に変わるのでそのようなことは出来ないようになっているようです。

オプションの重複、という意味では、 実際、これを逆にしても

1
2
$ printf "X%0-5dX\n" 111
X111  X

同じ出力。

また、0の代わりに.を書いてもゼロ埋めになります。

1
2
$ printf "X%.5dX\n" 111
X00111X

これは.<N>精度を表す記法で、 5桁の精度ということなのですが、 整数の場合には単にその桁分の左ゼロ埋めで表記されます。 入力桁数の方が大きい場合には単にそのまま出力されます。

少数の%fを使うと、

1
2
3
4
$ printf "X%.5fX\n" 123.456
X123.45600X
$ printf "X%.5fX\n" 123.456789123
X123.45679X

こんな感じで小数点以下が5桁になるようにされているのがわかります。 下の方は最後の桁はその下の桁を四捨五入して繰り上げで8から9に変換されています。

なのでこちらはパディングとは違うものですが、 整数に対して行うとゼロパディングと同じようになっているだけです。

右梅にしようとして-を前につけても精度の方が優先されて変わりません。

1
2
$ printf "X%-.5dX\n" 111
X00111X

ただし、.より後ろにつけると、ここでもGNU/BSDで違っていて

GNU:

1
2
$ printf "X%.-5dX\n" 111
X%.0-5ldX

BSD:

1
2
$ printf "X%.-5dX\n" 111
X111X

のような感じで、BSDは単に精度もパディングも無視されたような状態で、 一方でGNUの方は%記法が無視され、かつ-5の部分が0-5lに変換されています。 (多分何かしら考えれば分かりそうですがパッと考えどうしてこうなるか良くわからない。)

いずれにしろこの辺は通常用途では無いと思うのでおいておきます。

printfでの文字列のゼロパディング

今回の本題、%sに対してのゼロパディング。

そもそもゼロで埋めるのは数字だから左埋めしても意味がわかるものの、 通常の文字だと意味がよくわからなくなることもあるかとは思います。

そんなわけでこれも通常用途とはあまり言えないかもしれませんが、 プログラムの中では空白では無く何かしらの文字で埋きたい場合や、 何かしら別の文字列で埋める場合に一旦0にして置換したほうが 空白よりもやりやすいこともあるかも。

ということでやってみると、

GNU:

1
2
$ printf "X%05sX\n" aaa
X  aaaX

BSD:

1
2
$ printf "X%05sX\n" aaa
X00aaaX

ということで、GNUの場合はゼロではなく空白埋めになり、一方BSDでは文字列に対してでもゼロ埋めになります。

これどんな用途で使いたかったかというと、 文字幅が可変なものに対してその上下に文字列と同じ長さだけの#とか書いてバナー的にしたい時、

1
2
3
4
5
str="abcdefg"
n=${#str}
printf "%0${n}s\n" |tr 0 "#"
printf "%s\n" $str
printf "%0${n}s\n" |tr 0 "#"

こんなことをやってました。 ゼロ埋めした上で何も渡さないのでnの分だけ0が表示されるつもり。 それをtr#に変換して

1
2
3
#######
abcdefg
#######

こんな出力にしたかった。

で、macOSでやってみたところうまくいってOKと思っていたら うまく行ったのでこれでOKかと思いきやGNUなLinux環境でやってみると

1
2
3
   
abcdefg
   

みたいな感じで空行になってました。

これは上で見たように%05sとか文字列に対してゼロ埋めしようとしても GNU版printfだと空白埋めにしてしまうから。

これに対しては

1
2
3
4
5
str="abcdefg"
n=${#str}
printf "%0${n}d\n" |tr 0 "#"
printf "%s\n" $str
printf "%0${n}d\n" |tr 0 "#"

のようにdを使ってやればGNUでもBSDでもバナー表示になります。

Ref

GNU版のマニュアル:

man printfはエスケープシーケンスについての記述が少しありますが、 %d%sに関してはあまりなし。

man中にリンクのある上のマニュアルページにもパディングについてはなし。

man 3 printfを見ると、

1
2
3
4
0      The value should be zero padded.  For d, i, o, u, x, X, a, A, e, E, f, F, g,  and  G  conversions,  the
       converted value is padded on the left with zeros rather than blanks.  If the 0 and - flags both appear,
       the 0 flag is ignored.  If a precision is given with a numeric conversion (d, i, o, u, x, and X), the 0
       flag is ignored.  For other conversions, the behavior is undefined.

とあり、dとかにはゼロ埋めします、と書いてありますが、sは含まれていません。

BSD版のマニュアル:

上のはFreeBSDのprintfのマニュアルですが、macOSのman printfのものも上と同じJuly 1, 2020でmacOS 13.3のものとそこだけ違いますがあとは多分一緒。

ここには

1
2
3
0       A zero `0' character indicating that zero-padding should
        be used rather than blank-padding.  A `-' overrides a `0'
        if both are used;

と、どのタイプに対して、ということは書いておらず、単にゼロ埋めします、と書いてあります。 また、-と同時に使われた場合には0よりも優先して使われる、ともあります。

man 3 printfはFreeBSDの最新版だと2018年版になってましたが、macOSだと2009年で、おそらく 以下のものと同じ

ここ見ると

1
2
3
4
5
`0' (zero)   Zero padding.  For all conversions except n, the con-
             verted value is padded onthe left with zeros rather
             than blanks.  If a precision is given with a numeric
             conversion (d, i,o, u, i, x, and X), the 0 flag is ig-
             nored.

となっていて、こちらも0が付けばどれでもゼロ埋めする、とあります。 加えてここでは.の精度をつけられるタイプでそのflagが付いてる場合にはゼロパディングのフラグは無視される、とあります。

文字列の場合にはそのままゼロパディングされます。

というわけで、マニュアルをちゃんと読むと書いてありました、と。

その他のGNU/BSDの違い

Sponsored Links
Sponsored Links

« ChatGPT同士で議論させる シェルスクリプトでスクリプトの引数をループする方法 »

}