FC2ブログ

本を読む

読書やコンピュータなどに関するメモ

ThinkPad+Ubuntuで、サスペンドから戻るとウィンドウがリサイズされる

 去年からThinkPad T470sを使い始め、最初からUbuntu 18.04を入れています。満足しているのですが、1つ問題があって、サスペンド状態からリジュームすると画面の各ウィンドウがリサイズされて画面左上に集まってしまうという症状をくらっていました。

 見た感じとしては、リジュームした直後に画面のHiDPIスケーリングが一度200%ぐらいになって、また元の設定になるような感じです。

 Ubuntuのlaunchpad(イシュートラッカー)にもBug #1726548 “Windows resized after resume”というスレッドが立っていて、同様の症状が次々寄せられているようでした。

 このスレッドで今月、Ask Ubuntuの18.04 - Preventing Window resizing after resume - Ask Ubuntuという質問と回答が紹介されていました。ここで書かれていたのは、GNOMEの自動スケーリング調整をオフにするという方法です。

 設定はgsettingsで確認できます。

$ gsettings get org.gnome.desktop.interface scaling-factor

 結果が「uint32 0」になっていると、GNOMEの自動スケーリングがオンになっています。

 オフにする場合もgsettingsから。

$ gsettings set org.gnome.desktop.interface scaling-factor 1

 私の場合は自動スケーリングを使っていないので、これで問題を回避できました。

Bash 5.0の新機能:namerefの挙動の変更

Qiitaと同時投稿です

Bash 5.0では、namerefの名前解決で非互換の挙動があると言われています。

リリースアナウンスでは次のように書かれています。

There are a few incompatible changes between bash-4.4 and bash-5.0. The changes to how nameref variables are resolved means that some uses of namerefs will behave differently, though I have tried to minimize the compatibility issues.

「bash 5.0のNEWSファイル私訳」にも次のようにあります。

  1. 関数内でnamerefの名前解決のループは、グローバルスコープでの名前の変数 に解決されるようになりました。

そもそもnamerefを知らないと変更点もわからないので、順に説明します。

おさらい①:namerefって?

namarefはBash 4.3から導入された機能です。ksh由来のようで、AIXのマニュアルでは「名前参照」と、Solarisのマニュアルでは「nameref」と表記されているようです。

namerefは、ほかの変数を参照する変数を作る機能です。変数のエイリアス(別名)を付けるときなどに使います。

namerefはdeclare -nlocal -nなど、変数宣言のときに-nオプションを指定して作ります。namerefな変数に、ほかの変数名を文字列として代入すると、エイリアスのように動きます。

$ aaa=3                 # 変数aaaに3を代入
$ declare -n bbb        # namerefな変数bbbを宣言
$ bbb=aaa               # bbbに'aaa'を代入($aaaではない)
$ echo $bbb             # bbbの内容は?
3

おさらい②:namerefの使い道

namerefの使い道として自分が考えたのが、関数から関数に値を渡すときに、変数名を渡す方法です。

たとえば、関数から関数に配列を渡すときには、引数や標準入力ではそのままでは渡せません。そこでダイナミックスコープを使って渡す方法があります。

foo() {
  local ary=('a b' c d)         # 配列変数aryを宣言
  bar
}

bar() {
  echo ${ary[0]}                # fooで宣言された配列変数aryを参照
}

foo                             # 「a b」と表示される

ただbarから暗黙的に変数aryを参照するのがいまいちです。そこで、namerefを使って変数名を渡すことで、barではfooで宣言された変数名の知識が不要になります。

foo() {
  local ary=('a b' c d)
  bar ary               # “ary”という変数名を文字列で引数に
}

bar() {
  local -n var=$1       # nameref変数varに1つめの引数の文字列(変数名)を代入
  echo ${var[0]}
}

foo                      # 「a b」と表示される

namerefの挙動の違い

さて、Bash 5.0で挙動が変わったのはどのあたりでしょうか。

「関数内でのnamerefの名前解決のループ(A nameref name resolution loop in a function)」ということなので、namerefで循環参照を作ってみます。

aaa=7                           # グローバル変数aaa

foo() {
  local -n aaa                  # ローカルなnameref変数aaa
  aaa=aaa                       # aaaからaaa自身を参照
  echo $aaa
}

foo

これをBash 4.4で実行すると、循環参照の警告が表示されたあと、echo $aaaでは何も表示されません。

$ bash ./n.sh
./n.sh: line 6: warning: aaa: circular name reference

Bash 5.0で実行すると、循環参照の警告が表示されたあと、echo $aaaでグローバル変数aaaの値である「7」が表示されます。

$ ./bash ./n.sh
./n.sh: line 6: warning: aaa: circular name reference
7

結論

そもそも、Bashでnamerefをそれほど使うとは思わないし、その中でも特殊なケースなので、非互換による影響はほとんどないのではないでしょうか。

Bash 5.0の新機能:カーソルの画面行移動

Qiitaと同時投稿です

Bash 5.0のNEWSファイルをさくっと翻訳したエントリー「bash 5.0のNEWSファイル私訳」を先日書きました。

この中のReadline 8.0の新機能として、カーソルの画面行移動機能がありました。

b. キーに割り当てられるコマンドに新しく`next-screen-line' and
   `previous-screen-line'があります。カーソルを画面行のそれぞれ次と前
   の行の同じカラムに移動します。

さっそく試してみましょう。

Bash 5.0のコマンドラインから、Ctrl-↓にnext-screen-lineを割り当てます。

$ bind '"\e[1;5B": next-screen-line'

同様にCtrl-↑にprevious-screen-lineを割り当てます。

$ bind '"\e[1;5A": previous-screen-line'

コマンドラインで、1行が1画面行を超えるように文字を入力します。

$ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

ここでCtrl-↓とCtrl-↑を押すとカーソルが画面行を移動することがわかります。

Bash 5.0の新機能:localvar_inherit

Qiitaと同時投稿です

Bash 5.0のNEWSファイルをさくっと翻訳したエントリー「bash 5.0のNEWSファイル私訳」を先日書きました。

この中に、localvar_inheritという新しいshoptオプションが追加されたことが書かれています。

o. 新しいshoptオプション:localvar_inherit。設定すると、ローカル変数
   は、直前のスコープにある同じ名前の変数の値を継承します。

自分で訳しておいてなんですが、わかりにくいですね。

Bash 5.0のmanpageでは、こう説明されています。

localvar_inherit
        If set, local variables inherit the value and attributes
        of a variable of the same name that exists at a previous
        scope before any new value  is  assigned.   The  nameref
        attribute is not inherited.

やはりわかりにくいですね。

そこで、ちょこっと調べて試してみた結果を、順を追って説明します。

おさらい①:関数

多くのプログラミング言語と同じく、Bashでも関数を定義できます。定義した関数はコマンドとして実行できます。

$ cat hello.sh
hello() {               # helloという関数を定義
    echo hello
}

hello                   # helloを実行
$ bash ./hello.sh
hello

おさらい②:ローカル変数

単純なシェルでは変数はグローバル変数しかありません。しかしBashでは、localコマンドによって関数内のローカル変数を定義できます。

$ cat localvar.sh
foo() {
  local aaa     # aaaというローカル変数を定義
  aaa=5         # aaaに5を代入
}

aaa=3           # aaaに3を代入
foo             # fooを実行
echo $aaa       # その後でaaaの値は?
$ bash ./localvar.sh
3

おさらい③:ローカル変数はダイナミックスコープ

Bashのローカル変数のスコープは、呼び出し先の関数でも有効です。Common Lispのスペシャル変数や、Emacs Lispのデフォルトのダイナミックスコープ変数、Perlのlocalなどに似ています。

次のように、関数fooと、fooから呼び出す関数barを定義します。すると、fooで定義したローカル変数aaaがbarからも参照できます。

$ cat scope.sh
foo() {
  local aaa=3   # aaaというローカル変数を定義して値を3に
  bar
}

bar() {
  echo $aaa     # barでのaaaの値は?
}

foo
$ bash ./scope.sh
3

同じ変数aaaを参照しているので、barで値を変更すると、fooでも変更されています。

$ cat scope2.sh
foo() {
  local aaa=3   # aaaというローカル変数を定義して値を3に
  bar
  echo $aaa     # barを呼び出した後でaaaの値は?
}

bar() {
  aaa=5         # barでaaaに5を代入
}

foo
$ bash ./scope2.sh
5

ようやく本題:localvar_inherit

では、Bash 5.0の新機能であるlocalvar_inheritを見てみましょう。

localvar_inheritをオンにすると、呼び出し元の変数と同じ名前の変数をlocalで定義したときに、呼び出し元の変数の値と属性が コピー されます。

次の例では、fooとbarで同じ名前の変数aaaを定義しています。barではaaaの値を代入していないのに、fooでaaaに設定した値が参照されます。

$ cat inherit.sh
shopt -s localvar_inherit       # localvar_inheritをオンに

foo() {
  local aaa=3   # fooでローカル変数aaaを定義
  bar
}

bar() {
  local aaa     # barでもaaaを定義。値は代入しない
  echo $aaa     # aaaの値は?
}

foo
$ bash ./inherit.sh
3

ダイナミックスコープと違い、同じ変数を参照するわけではありません。次のように、barでaaaの値を変更しても、fooのaaaの値は変わりません。

$ cat inherit.sh
shopt -s localvar_inherit

foo() {
  local aaa=3
  bar
  echo $aaa     # barを呼び出した後でaaaの値は?
}

bar() {
  local aaa
  aaa=5         # barでaaaに5を代入
}

foo
$ bash ./inherit.sh
3

用途

さて、これをどういうところで使うと便利でしょうか。

普通の変数の値であれば、引数などで渡せば十分です。ただし、配列は引数などでは渡しづらいので、localvar_inheritを使って呼び出し先にコピーするのが便利そうです。

bash 5.0のNEWSファイル私訳

Qiitaと同時投稿です

bash 5.0(とreadline 8.0)がリリースされたので、NEWSファイル(新機能リスト)からbash 5.0の新機能の部分を訳してみました。

ターミナル用RSSリーダーのnewsbeuterを試す

 「RSSは死んだ」的なことが言われる昨今ですが、私はRSSリーダーを日々使っています。便利だと思うんですけどね。

 数百フィード以上を日常的にチェックするには、クラウド型のRSSリーダーが必須です。ただ、たとえばアドベントカレンダーなどで一時的にフィードをチェックするには、ローカルでシンプルなRSSリーダーを使うほうが小回りが効きます。

 Adventarで2018年のアドベントカレンダーを追いかけるのに、私はnewsbeuterを使ってみました。newsbeuterは、ターミナルのTUI(text user interface)画面で動作します。以下、Ubuntuデスクトップでの例です。

 インストールはaptから。

$ apt install newsbeuter

 まず一度newsbeuterを起動すると、エラーで終了します。このとき、設定ディレクトリ「~/.newsbeuter」が作られます。

$ newsbeuter
XDG: configuration directory '/home/emasaka/.config/newsbeuter' not accessible, using '/home/emasaka/.newsbeuter' instead.
Starting newsbeuter 2.9...
Loading configuration...done.
Opening cache...done.
Loading URLs from /home/emasaka/.newsbeuter/urls...done.
Error: no URLs configured. Please fill the file /home/emasaka/.newsbeuter/urls with RSS feed URLs or import an OPML file.

newsbeuter 2.9
usage: newsbeuter [-i <file>|-e] [-u <urlfile>] [-c <cachefile>] [-x <command> ...] [-h]
        -e              export OPML feed to stdout
        -r              refresh feeds on start
        -i <file>       import OPML file
        -u <urlfile>    read RSS feed URLs from <urlfile>
        -c <cachefile>  use <cachefile> as cache file
        -C <configfile> read configuration from <configfile>
        -X              clean up cache thoroughly
        -x <command>... execute list of commands
        -o              activate offline mode (only applies to The Old Reader synchronization mode)
        -q              quiet startup
        -v              get version information
        -l <loglevel>   write a log with a certain loglevel (valid values: 1 to 6)
        -d <logfile>    use <logfile> as output log file
        -E <file>       export list of read articles to <file>
        -I <file>       import list of read articles from <file>
        -h              this help

 購読するフィードは、「~/.newsbeuter/urls」というテキストファイルを新規作成して書き込みます。たとえばこんな感じで。

https://adventar.org/calendars/3599.rss
https://adventar.org/calendars/2877.rss
https://adventar.org/calendars/2940.rss

 ついでに設定を変更しておきます。設定は「~/.newsbeuter/config」というテキストファイルです。ここでは、メニュー画面で「j」「k」キーでも操作できるようにします(デフォルトでは「↓」「↑」キー)。

bind-key j next
bind-key k prev

 ここで改めてnewsbeuterを起動します。

$ newsbeuter

 すると、フィード一覧がTUIで表示されます。

フィード一覧

 「R」(「Shift」+「r」)キーで、フィードが更新され、新着がチェックされます。

フィードが更新された

 フィードのタイトルが取得されて表示されました。

 さきほど設定した「j」「k」キーにより、対象フィードを移動できます。「Enter」キーで、対象フィードに入り、エントリー一覧が表示されます。

エントリー一覧

 ここの操作もフィード一覧と同様です。未読のエントリーには「N」が付いて太字になっています。ここから「Enter」でエントリーの内容が表示されます。

エントリーが表示

 エントリーのリンク先は、「o」キーで表示できます。呼び出されるWebブラウザーは、UbuntuやDebianのデフォルトではsensible-browser(デフォルトのWebブラウザーを開くシェルスクリプト)、本家ソースのデフォルトではlynxです。このWebブラウザーも「~/.newsbeuter/config」で設定できます。

 エントリー表示から元に戻るのは「q」キーです。エントリー一覧やフィード一覧からも「q」キーで戻れます。

 以上、単なる“動かしてみましたエントリー”でした。

Rubyでカナのソート

 これは「ライティングや編集にまつわるあれこれ Advent Calendar 2018」の12月12日の記事です。いままでブログにちょろちょろ書いていたことを、アドベントカレンダー向けにまとめます。

 さて、専門書などの巻末には、多くの場合、索引が設けられています。TeXなど、本文中の指定した箇所から自動的に索引を作る機能があるツールもあります。そうした機能がない場合は、本文から単語とページ番号(ノンブル)を抜き出したデータを用意して索引を作ります。

 索引は、アルファベット順や読み(カナ)順にソートされます。このソート方法については出版社各社や編集者各自でそれぞれ対応していると思います。ここでは、私の方法について書きます。まあ地味なテーマです。

カナのソートは何が問題か

 カナのソートというと、一見、文字コード順にソートすればいいように思えます。たとえば、こんな場合です。

  • うみ
  • かわ
  • やま

 しかし、長音(音引き)が入る場合はどういう順序になるでしょうか。

  • あーてぃすと
  • あいでんてぃてぃ

 あるいは濁音や促音などが入ったら、どのような順序でしょうか。

  • いが
  • いかだ
  • たった
  • たつた
  • だった

実はJISの規格がある

 こうしたカナ(などを含む日本語文字列)のソート順には、JIS X 4061「日本語文字列照合順番」という規格が存在します。

 ルールをざっくりいうと、先頭から1文字ずつ比較していきます。文字の比較としては、まずその文字の文字クラスを比較します。文字クラスが同じなら基底文字列を比較します。そして、基底文字列で比較しても最後まで同じだったら、照合属性を含めて再び先頭から比較します。

 と言われても、独自の用語が並んでいてわかりませんね。この用語をそれぞれ説明します。

 文字クラスとは、文字の種類です。JIS X 4061では次表のように決められています。番号が少ないほうが順番が先です。

番号 文字クラス
1 スペース
2 記述記号
3 括弧記号
4 学術記号
5 一般記号
6 単位記号
7 アラビア数字
8 欧字記号
9 ラテンアルファベット
10 仮名
11 漢字
12 げた記号

 次に、基底文字とは、文字の余分な属性を除いた文字です。アルファベットなら、大文字小文字を揃えます。カナであれば、ひらがなかカタカナか、小書き文字(発音や拗音)、濁音・半濁音を揃えます。つまり、「a」と「A」、「あ」と「ア」、「つ」と「っ」、「は」と「ば」と「ぱ」が同じになります。

 この基底文字の扱いでは、長音の扱いも決まっていて、直前の母音として扱われます。つまり「カー」は「カア」と、「オー」は「オオ」として扱われます。「んー」は直前の母音がないので「んん」となります。「んー」は専門書の索引にはあまり出ないと思いますが。

 基底文字で最後の文字まで比較してまったく同じであれば、基底文字にするときに除いた属性を照合して再び先頭から比較します。濁音半濁音については「清音文字 < 濁音文字<半濁音文字」、小書き文字や大文字小文字であれば「長音記号 < 小文字 < 大文字」となります。まあだいたいUnicodeやJISの文字コード順です。

 これらを適用すると、次のような順序になります。これは単純に文字コードでソートした順序とは異なります。

  • あーてぃすと < あいでんてぃてぃ
  • いが < いかだ

RubyでCLDR

 たとえば、Excelでかな文字列をソートすると、JIS X 4061に従うようです。私も前職で10年ちょっと前は、索引のソートにExcelを使っていました。

 ここでは、Excelでなくテキストファイルとバッチ処理による例を、Rubyでやってみます。

 Unicodeコンソーシアムでは、CLDR(Common Locale Data Repository)プロジェクトで、ロケール固有の情報をまとめた、一種のデータベース(XML)を定めてています。CLDRには、数字の桁区切り(カンマかピリオドかなど)や、重さや長さの単位、通貨の単位、月日や日時の表記、数字を言葉で書く方法などが含まれます。

 このCLDRの中に文字列の照合順序も含まれています。

 CLDRをRubyから扱うには、twitter_cldrというライブラリがあります。twitter_cldrはRubyGemsにあるので、gemコマンドやbundleコマンドでインストールできます。

$ gem install twitter_cldr

 twitter_cldrはCLDRのさまざまな機能を備えています。日本語の文字列をJIS X 4061にしたがってソートするには、Array(配列)からLocalizedArrayを作ってソートし、またArrayに戻します。

require 'twitter_cldr'

["あいでんてぃてぃ", "あーてぃすと"].localize(:ja).sort.to_a
#=> ["あーてぃすと", "あいでんてぃてぃ"]

 ただし、表記と読みが組になったデータをソートする機能は、そのままではありません。たとえば、次のようにします。

words = [
  { original: '春', yomi: 'ハル' },
  { original: '夏', yomi: 'ナツ' },
  { original: '秋', yomi: 'アキ' },
  { original: '冬', yomi: 'フユ' },
]

def sort_kana_cldr_by(enum)
  enum.sort_by do |x|
    TwitterCldr::Collation::Collator.new(:ja).get_sort_key(yield(x))
  end
end

sort_kana_cldr_by(words) {|x| x[:yomi]}
# => [{:original=>"秋", :yomi=>"アキ"}, {:original=>"夏", :yomi=>"ナツ"},
#     {:original=>"春", :yomi=>"ハル"}, {:original=>"冬", :yomi=>"フユ"}]

 ただ、twitter-cldr-rbは多機能なぶん、サイズも比較的大きめですし、やはり比較的大きめのライブラリいくつかに依存しています。

sort_kana_jisx4061を作った

 カナのソートに特化したRubyライブラリとしては、sort_kana_jisx4061というのもあります。私が作りました。

 もともとtwitter_cldrを知らずに手元で作っていた索引作成補助スクリプトから、カナのソート機能を切り出してライブラリ化したものです。コードは80行ぐらいの簡単なものです。

 sort_kana_jisx4061も、gemコマンドやbundleコマンドでインストールできます。

$ gem install sort_kana_jisx4061

 使い方はこんな感じです。

require 'sort_kana_jisx4061'

words = [
  { original: '春', yomi: 'ハル' },
  { original: '夏', yomi: 'ナツ' },
  { original: '秋', yomi: 'アキ' },
  { original: '冬', yomi: 'フユ' },
]

sort_kana_jisx4061_by(words) {|x| x[:yomi] }
# => [{:original=>"秋", :yomi=>"アキ"}, {:original=>"夏", :yomi=>"ナツ"},
#     {:original=>"春", :yomi=>"ハル"}, {:original=>"冬", :yomi=>"フユ"}]

 もともと索引作成用なので、読み文字列を含むデータをソートできるようにしています。また、カナや英数字に特化していて、漢字などはソート対象に入ることを想定していません。

 JIS X 4061とちょっと挙動が違うところとしては、単位記号を特別扱いせず一般の記号として扱っている点があります。これは、たとえば“$”はコンピュータ関連では、Unix系OSのプロンプトや、PerlやPHPの変数記号、Haskellの演算子などに使われるので、特別扱いするのはどうかなと思ったからです。でも規格に合わせたほうがいいんですかねえ。

 試しに手持ちの本物の書籍索引データ(“$”などは含まない)を元に、sort_kana_jisx4061とtwitter_cldrとでソートをかけてみたところ、まったく同じ結果になりました。この範囲ではsort_kana_jisx4061に問題はないようです。

 ということで、私の用途にはtwitter_cldrよりsort_kana_jisx4061のほうが使いやすそうです。

 | HOME |  »

Categories

Recent Entries

Recent Comments

Recent Trackbacks

Appendix

emasaka

emasaka

フリーター。
連絡先はこのへん

Monthly


FC2Ad