Tcl – 文字列を解析する -scan-

文字列を、指定した書式に基づいて解析を行い、変換した値を変数に格納するにはscanコマンドを使います。

逆に文字列を出力する際に桁を揃えたり、数値の表示形式を10進から16進に変更するなど、文字列を整形するには、formatコマンドを使います。

この記事は、scanコマンドの使い方を紹介します。

書式

scanコマンドはC言語のsscanf関数と同じように入力文字列を解析します。

[書式]

scan string format ?varName varName …?

[変換指定子の書式]

%?位置指定子??最大フィールド幅??サイズ修飾子??変換文字?

scanコマンドは、第1引数のstringを第2引数で指定したformatの書式に基づいて解析を行い、変換した値をvarNameへ格納します。戻り値は変換が行われた回数を返します。変換が実行される前に入力文字列の末尾に達すると-1を返します。

varNameを指定しない場合、変換した値をリストで返します。この場合、変換が実行される前に入力文字列の末尾に達すると空の文字列を返します。

formatで指定する変換指定子(%で始まる書式)は、formatコマンドと同様の方法で指定します。変換指定子は、「%」の後に最大4つのフィールドを含みます。

  1. 位置指定子
    (または変換された値を変数に代入せずに破棄することを示す「*」)
  2. 最大フィールド幅の数値
  3. サイズ修飾子
  4. 変換文字

最後の変換文字を除いて、これらのフィールドはすべてオプションです。フィールドの指定は上記の順序で指定します。

指定できる変換指定子

変換文字 意味
d 入力文字列は10進数の整数でなければなりません。読み込まれた値を符号付き10進数に変換して変数に格納します。
o 入力文字列は8進数の整数でなければなりません。読み込まれた値を符号付き10進数に変換して変数に格納します。
x,X 入力文字列は16進数の整数でなければなりません。読み込まれた値を符号付き10進数に変換して変数に格納します。
b 入力文字列は2進数の整数でなければなりません。読み込まれた値を符号付き10進数に変換して変数に格納します。※Tcl8.6で追加されました。
u 入力文字列は10進数の整数でなければなりません。読み込まれた値を符号なし10進数に変換して変数に格納します。
i 入力文字列は10進数、8進数、16進数の整数でなければなりません。(8進数の場合は先頭に0、16進数の場合は先頭に0xを付けた値)
読み込まれた値を符号付き10進数に変換して変数に格納します。
c 1つの文字が読み込まれUnicode値を整数値として変数に格納します。先頭の空白はスキップされません。したがって入力文字列中の空白は空白文字になります。またフィールド幅の指定はできません。
s 入力文字列を文字列として解析します。入力文字列中の次の空白文字までを1つの文字列として変数に格納します。
e,f,g,E,G 入力文字列は浮動小数点数でなければなりません。読み込まれた値を浮動小数点値として変数に格納します。※e,f,g E,Gの意味につては文字列の整形(書式) -formatコマンド-を参照してください。
例えは、fはxx.yyy、eはxx.yyye±zzの形式です。
[chars] charsは1つ以上の文字を指定します。入力文字列がcharsに一致すると一致する文字列を変数に格納します。charsの最初の文字が「]」である場合、右角括弧ではなく文字の一部として扱われます。charsが「a-b」の形式の場合、aからbの間のすべての文字を表します。charsの最初の文字または最後の文字が「-」の場合、範囲を示すのではなく文字の一部として扱われます。
[^chars] 入力文字列がcharsに一致しない文字列を変数に格納します。それ以外は、[chars]と同じ。
n 入力文字列からスキャンされた文字の総数が変数に格納されます。
サイズ
修飾子
意味
h サイズ修飾子を指定しない場合と同じです。
l,L 格納される整数範囲は64ビットになります。 ( exprコマンドのwide()関数によって生成されたものと同じ範囲 )
ll 格納される整数範囲は無制限になります。

サイズ修飾子を指定しない場合、整数値はexprコマンドのint()関数によって生成されたものと同じ範囲(少なくとも32ビットの範囲 )になります。int()関数で生成される範囲は、tcl_platform(wordSize)で調べることができます。

使い方

書式と変換指定子の表を見ただけでは、使い方のイメージがつかめないと思われるので、変換指定子をどのように指定すると、どのような結果になるのか、いくつか例を紹介します。

書式に基づいて文字列を分離する

[使用例1]

# 時、分、秒を分離
% scan "07:30.50" "%d:%d.%d" hh mm ss
3
% puts "hh=$hh, mm=$mm, ss=$ss"
hh=7, mm=30, ss=50

# RGBをそれぞれ10進数の値に変換
% set rgb "#ff0055"
#ff0055
% scan $rgb "#%2x%2x%2x" red green blue
3
% puts "red=$red green=$green blue=$blue"
red=255 green=0 blue=85

# 変数を指定しない場合
% scan "07:30.50" "%d:%d.%d"
7 30 50

書式に基づいて分離・変換した値を第3引数で指定した変数に格納します。 戻り値は変換が行われた回数を返します。 変数を指定しない場合は、分離した値をリストで返します。

[使用例2]

% scan "07:30.50" "%d:%d.%d" hh mm
different numbers of variable names and field specifiers

% scan "07:30.50" "%d:%d.%d" hh mm ss dd
variable is not assigned by any conversion specifiers

変換指定子と変数の数を合わせないとエラーになります。

変換文字の使用例

[使用例1] 整数

# %uは符号なし10進数に変換します。
% scan "-1" "%d"
-1
% scan "-1" "%u"
4294967295

# %oは8進数を符号付き10進数に変換します。
% scan"755" "%o"
493

# %iは10進数、8進数、16進数を符号なし10進数に変換します。
% scan "10 010 0x10" "%i%i%i"
10 8 16

[使用例2] 浮動小数点数

# 浮動小数点数
% scan "3.666 3.666E-7" "%f %e"
3.666 3.666e-7

[使用例3] 文字

# 空白、a、b、c、あ、各文字をUnicodeの10進数の値に変換します。
% scan " abcあ" "%c%c%c%c%c" space a b c hiragana
5
% puts "空白=$space a=$a b=$b c=$c あ=$hiragana"
空白=32 a=97 b=98 c=99 あ=12354

[使用例4] chars, s, n

# chars
% scan "abcABC" {%[a-z]%[A-Z]}
abc ABC
% scan "abcABC" {%[^A-Z]%[^a-z]}
abc ABC

# スキャンされた文字の総数
% scan "abc" "%s %n"
abc 3

[使用例5] 変換指定子の「b」はTcl8.6で追加されました。

# 2進数を10進数に変換
% scan "0111" "%b"
7
% scan "1111" "%b"
15
% scan "10000000" "%b"
128
% scan "11111111" "%b"
255
% scan "11111111111111111111111111111111" "%b"
-1
% scan "11111111111111111111111111111110" "%b"
-2
% scan "11111111111111111111111111111000" "%b"
-8
% scan "11111111111111111111111111110001" "%b"
-15
% scan "1111111111111111111111111111111111111111111111111111111111111111" "%lb"
-1

最大フィールド幅の使用例

[使用例1] 整数

# すべての入力文字列が対象
% scan "12345" "%d"
12345

# 指定した幅を解析します。
% scan "12345" "%3d%2d"
123 45
% scan "12345" "%3d"
123

[使用例2] 文字列

# 文字列中の次の空白文字までを1つの文字列として解析します。
% scan "ABC abc" "%s"
ABC
% scan "ABC abc" "%s%s"
ABC abc

# 指定した幅を解析します。
% scan "ABC abc" "%s%2s"
ABC ab
% scan "ABC abc" "%5s%2s"
ABC ab
% scan "ABC abc" "%2s%2s%2s"
AB C ab

位置指定子の使用例

[使用例]

# 位置指定子
% scan "ABC abc" "%2$3s%1$2s" ABC abc
can not read "3s": no such variable

% scan "ABC abc" {%2$3s%1$2s} ABC abc
2
% puts "ABC=$ABC abc=$abc"
ABC=ab abc=ABC

「%」の後に10進数と「$」が続く書式(位置指定子)を使用する場合、$記号による変数置換がされないように「””」ではなく「{}」で囲む必要があります。

また、「%」に続く数字が格納する変数の位置になります。例の場合は、1つ目の変数はABC、2つ目の変数はabcです。

サイズ修飾子の使用例

[使用例]

% set tcl_platform(wordSize)
4

# 32ビットの整数範囲を符号付き10進数に変換
% scan "ffffffff" "%x"
-1
# 64ビットの整数範囲を符号付き10進数に変換
% scan "ffffffff" "%lx"
4294967295

# 64ビットの整数範囲を符号付き10進数に変換
% scan "ffffffffffffffff" "%lx"
-1
# llを指定すると整数範囲は無制限になります。
% scan "ffffffffffffffff" "%llx"
18446744073709551615

# 70ビットの整数(無制限)
% scan "ffffffffffffffffff" "%llx"
4722366482869645213695

その他の使用例

[使用例] day_of_the_week.tcl

#!/bin/sh
# the next line restarts using tclsh \
exec tclsh "$0" "$@"

# 入力した日付の曜日を調べる。

puts "日付を入力してください。(yyyy-mm-ddまたはyyyy/mm/dd)"
set date [gets stdin]

# 入力形式のチェック
if {[scan $date {%4d-%2d-%2d} y m d] == 3} {
    set sec [clock scan $date -format {%Y-%m-%d}]
} elseif {[scan $date {%4d/%2d/%2d} y m d] == 3} {
    set sec [clock scan $date -format {%Y/%m/%d}]
} else {
    puts "入力形式が違います。"
    exit
}

array set week {
    Sun 日 Mon 月 Tue 火 Wed 水 Thu 木 Fri 金 Sat 土
}
set day [clock format $sec -format {%a}]

puts "${y}年${m}月${d}日は$week($day)曜日です。"

[実行例]

$ ./day_of_the_week.tcl
日付を入力してください。(yyyy-mm-ddまたはyyyy/mm/dd)
2019-03-15
2019年3月15日は金曜日です。

$ ./day_of_the_week.tcl
日付を入力してください。(yyyy-mm-ddまたはyyyy/mm/dd)
2019/03/15
2019年3月15日は金曜日です。

$ ./day_of_the_week.tcl
日付を入力してください。(yyyy-mm-ddまたはyyyy/mm/dd)
2019.03.15
入力形式が違います。

上の例は、scanコマンドの変換回数をチェックすることにより、入力した日付の形式が間違っていないか確認しています。

clockコマンドは日時に関連する操作を行います。clock scanはは入力された日付を解析して秒単位で表現した時刻に変換して返します。clock formatは秒単位で表現された時刻を書式に基づいて整形します。

上の例のclockコマンドの書式のキーワードは以下の意味があります。

 %Y 4桁の年の数字(2019など)
 %m 月の数字(01-12)
 %d 日の数字(01-31)
 %a 省略形式の曜日の名前(Sun,Monなど)

clockコマンドの使い方については別途紹介しようと思います。

位置指定子について -XPG3とは-

本家マニュアルのscanコマンドの解説を参照するとXPG3位置指定子(XPG3 position specifier)という記述が登場します。「XPG3とは何か?」よく分からなったので調べてみました。

一言でいうと、XPG3とは「X/Open Portability Guidelinesのバージョン3」のことです。

1984年、いくつかのUNIXの製造企業が集まって、オープン標準の確立と振興を目的としてX/Open Company, Ltd.という団体を設立しました。X/Openでは、コマンドやシステムコールなどの仕様を新規で作成するのではなく既存の標準をまとめて統一する作業を行いました。この統一した仕様を X/Open Portability Guide (XPG) という名前で公表しました。

X/Openは、Open Software Foundation (OSF) と合併を行い、現在では、The Open Groupという名称に変わっています。

X/Open - Wikipedia

ここで話を元に戻して「XPG3位置指定子」とは、変換指定子の1つ目のフィールドは、「XPG3に準拠した位置指定子」という意味であると理解しているのですが、原文は以下のようになっています。

A conversion specifier contains up to four fields after the %: a XPG3 position specifier (or a * to indicate the converted value is to be discarded instead of assigned to any variable);

http://www.tcl.tk/man/tcl8.6/TclCmd/scan.htm

Google翻訳で訳すと以下にようになります。

変換指定子は、%の後に最大4つのフィールドを含みます。XPG3位置指定子(または、変換された値が変数に代入される代わりに破棄されることを示すための*)。

「XPG3位置指定子」、原文では「XPG3 position specifier」と聞いてもピンと来ないですよね。英語圏の人は、この英文で理解できているのだろうか?

formatコマンドの解説の中にヒントになる説明がありました。

原文
This follows the XPG3 conventions for positional specifiers.


翻訳
これは位置指定子に関するXPG3の規則に従います。

http://www.tcl.tk/man/tcl8.6/TclCmd/format.htm

個人的には、scanコマンドの使い方を理解するうえで、XPG3という記述は、混乱を招くので不要ではないかと思う。

または、以下のような文章にすべきではないかと思った。

英文
a positional specifiers, This follows the XPG3 conventions. (or a * to indicate the converted value is to be discarded instead of assigned to any variable);

翻訳すると
位置指定子。これはXPG3の規約に従います。 (または、変換された値が変数に代入されるのではなく破棄されることを示すための*)。


scanコマンドと逆の働きをするコマンドとしてformatコマンドがあります。 formatコマンドは文字列を出力する際に桁を揃えたり、数値の表示形式を10進から16進に変更するなど、文字列を整形する際に使います。formatコマンドの使い方はこの記事で紹介しています。
Tcl - 文字列を整形(書式化)する -format-
この記事は、変換指定子の書式の各項目(フラグ、最小フィールド(幅)、精度、サイズ修飾子、変換文字)に指定できる値と意味、formatコマンドの使い方を、いくつか例を使ってを紹介しています。

コメント

タイトルとURLをコピーしました