iMovie で動画編集の私的テクニック

アニメーションチャートの作り方

PowerPoint を使う.

単一スライド内でアニメーションを凝ることもできるが,複数スライドで「画面切り替え」で構成していくのがシンプルで良い.

ポイントは,スライド間で動かす図形の ID を一致させること.これを守っていれば,移動や拡大縮小だけでなく,正方形を円にすることもできる.

「自動的に切り替え」を使って,設定した秒でスライドが切り替わるようにするが,一般に,変形切り替えで時間を使うので,それにプラスして何秒か待ってしまうと流れが悪くなる.(よって「自動的に切り替え」を  00.00 に設定するのが良い)

キャプションの追加

これも PowerPoint を使う.

作戦は,画面全体の大きさの透過 PNG を作成して,iMovie でオーバーレイするというもの.キャプションの数だけスライドを作成することになる.画面全体の大きさの透過 PNG を作成するには,まず塗りつぶしなしの長方形をスライドぴったりの大きさで作成する.さらに,図形「星とリボン」の「横巻き」あたりを使用してキャプションをスライド中の所望の位置に配置する.

次に,長方形とキャプション図形を両方選択した状態で右クリックして,「図として保存」を選ぶと,画面全体の大きさの透過 PNG が作成できる.

この PNG を iMovie にドラッグ&ドロップするが,デフォルトでは KeeBurn エフェクトがかかってしまうので,これを「サイズ調整してクロップ」にすると動いたりサイズが変わったりしないキャプションになる.さらに,フェードを 0.5 秒ぐらいで設定すると自然な感じにキャプションが登場・消滅してくれる.

再び ChatGPT

is there any way to convert jsonpath to json?

Yes, there are libraries and tools that can be used to convert JSONPath to JSON. One popular library for this purpose is the “jsonpath-ng” library in Python, which can be used to parse JSONPath expressions and return the corresponding JSON data. Additionally, some online tools such as “JSONPath Evaluator” can also be used to convert JSONPath to JSON.

I’d like the one for JavaScript. Any?

Yes, there are libraries for JavaScript that can be used to convert JSONPath to JSON. One popular library for this purpose is the “jsonpath” library, which can be used to parse JSONPath expressions and return the corresponding JSON data.

You can install it via npm:

Copy codenpm install jsonpath

And use it like this:

Copy codeconst jsonpath = require("jsonpath"); const json = {...}; const path = "$.store.book[*].author"; const result = jsonpath.query(json, path);console.log(result);

Another option is “jsonpath-plus” which is a fork of jsonpath that supports a more powerful query language and more options.

You can install it via npm:

Copy codenpm install jsonpath-plus

and use it like this:

Copy codeconst jsonpath = require("jsonpath-plus"); const json = {...}; const path = "$..author"; const result = jsonpath({json, path});console.log(result);

Both of these libraries are actively maintained and widely used in the JavaScript community.

It looks like the ones for JavaScript only converts json to jsonpath. What I need is jsonpath to json.

I apologize for the confusion. You are correct that the libraries that I previously mentioned, such as “jsonpath” and “jsonpath-plus”, are primarily used for extracting data from JSON using JSONPath expressions, rather than converting JSONPath to JSON.

A library that can convert JSONPath to JSON is “jsonpath-object-transform” it is a javascript library that can take a json object and a jsonpath string and return a new json object that only contains the properties that match the jsonpath string.

You can install it via npm:

Copy codenpm install jsonpath-object-transform

And use it like this:

Copy codeconst jsonpathObjectTransform = require("jsonpath-object-transform");const json = {...}; const path = "$.store.book[*].author"; const result = jsonpathObjectTransform(json, path); console.log(result);

Keep in mind that this library is not as popular as jsonpath and jsonpath-plus and depending on your use case other libraries or methods may be more suitable.

wow. sounds cool. thanks!

You’re welcome! I’m glad I could help. Do let me know if you have any other questions.

ChatGPT

https://news.yahoo.co.jp/articles/f18a81c57f556a69d75ff5e0528e07a2f496b582

今更ながら試してみた.

[オレ]
how to install apache 2.4.54, php 7.4.33 and mysql 5.7.33 using RPM/dnf into my centos8 stream?

[ChatGPT]
To install Apache 2.4.54, PHP 7.4.33, and MySQL 5.7.33 using RPM/dnf on CentOS 8 Stream, you can use the following commands:
• Install Apache:
sudo dnf install httpd-2.4.54
• Install PHP:
sudo dnf install php-7.4.33
• Install MySQL:
sudo dnf install mysql-community-server-5.7.33
Once the installation is complete, you can start the Apache, PHP, and MySQL services by running:
sudo systemctl start httpd
sudo systemctl start php-fpm
sudo systemctl start mysqld

You can check the status of the services by running:
sudo systemctl status httpd
sudo systemctl status php-fpm
sudo systemctl status mysqld

You should also configure your server to start the Apache, PHP, and MySQL services automatically at boot time, by running
sudo systemctl enable httpd
sudo systemctl enable php-fpm
sudo systemctl enable mysqld

Note: You may need to install additional dependencies and configure your server accordingly to fully run the three services.

[オレ]
as for apache, the command failed with “Error: Unable to find a match: httpd-2.4.54”. what can I do?

[ChatGPT]
The error message “Error: Unable to find a match: httpd-2.4.54” indicates that the package httpd-2.4.54 is not available in the default repositories for your version of CentOS. This could be due to a number of reasons, such as the package not being compatible with your version of CentOS, or it not being available in the repositories you have configured on your system.
Here are a few things you can try:
1 Check the version of Apache that is available in the default repositories for your version of CentOS by running the command:
sudo dnf info httpd
2 You can also check if there is any other version of Apache that is compatible with PHP 7.4.33 and MySQL 5.7.33, and use that version instead of 2.4.54.
3 You can try to install Apache from the CentOS SCL repository, which provides newer versions of software for older versions of CentOS, by running the command:
sudo dnf install centos-release-scl
sudo dnf install httpd24-httpd

4 You can also install Apache from a third-party repository, like EPEL (Extra Packages for Enterprise Linux), by running the command:
sudo dnf install epel-release
sudo dnf install httpd

5 If none of the above solutions work, you may need to consider upgrading to a newer version of CentOS that supports Apache 2.4.54, PHP 7.4.33, and MySQL 5.7.33.
Be aware that installing software from third party repositories may have security risks and compatibility issues with your current system.

[オレ]
“dnf install centos-release-scl” failed with “Error: Unable to find a match: centos-release-scl”. Could you tell me any alternative method to use CentOS SCL in my centos8 stream?

[ChatGPT]
The error message “Error: Unable to find a match: centos-release-scl” indicates that the package “centos-release-scl” is not available in the default repositories for your version of CentOS 8 Stream.
Here are a few things you can try:
1 Check if there is any other package that provides the Software Collections (SCL) repository for your version of CentOS by running the command:
sudo dnf search centos-release-scl
2 You can also check if there is any other package that provides the Software Collections (SCL) repository for your version of CentOS by running the command:
sudo dnf search scl
3 If you still can’t find the package, you can try to enable the Software Collections (SCL) repository by adding the following line to the file /etc/yum.repos.d/CentOS-SCLo-scl.repo
[centos-sclo-scl-rh]
name=CentOS-8 - SCLo scl
baseurl=https://mirror.centos.org/centos/8/SCLo/x86_64/scl/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial

4 You can also install Apache from a third-party repository, like IUS (Inline with Upstream Stable) by running the command:
sudo dnf install https://rhel8.iuscommunity.org/ius-release.rpm
sudo dnf install httpd24u

5 If none of the above solutions work, you may need to consider upgrading to a newer version of CentOS that supports Apache 2.4.54, PHP 7.4.33, and MySQL 5.7.33.
Be aware that installing software from third party repositories may have security risks and compatibility issues with your current system.
Please double check your settings, and make sure you are using the correct version of CentOS, and that you have the correct repository enabled before installing the packages.

[オレ]
The first responded “No matches found.” It looks like centos-release-scl does NOT exist. To check the second, dnf presented a couple of results, but I couldn’t locate exact “scl”. For the third solution, it looks like mirror.centos.org no longer exists.

[ChatGPT]
I apologize for the confusion caused by my previous response. It appears that the package “centos-release-scl” is not available for CentOS 8 Stream. The Software Collections (SCL) repository is no longer available for this version of CentOS and it has been replaced by the “Stream” repository.
Regarding the second solution, you may find a package named “rh-*” that can provide the software collections you need, and you can use that package instead of “scl”.
Regarding the third solution, it seems that the mirror.centos.org is no longer valid and the link is broken,
I apologize for the confusion caused by my previous response.
A possible alternative solution is to use the “Alternate” repository, that provides newer versions of software for older versions of CentOS, but it’s not officially supported by Red Hat and you should use it with caution.
You can try installing Apache from the Alternate repository by running the command:
sudo dnf install httpd24
Another alternative is to install Apache from a third-party repository like EPEL (Extra Packages for Enterprise Linux), by running the command:
sudo dnf install epel-release
sudo dnf install httpd

It’s important to keep in mind that installing software from third-party repositories may have security risks and compatibility issues with your current system.
Also please make sure to check the package versions with the other software you are planning to use, such as PHP and MySQL, to ensure compatibility.
Please let me know if there is anything else I can assist you with.

[オレ]
where can I locate php 7.4.33 for centos8 stream?

[ChatGPT]
PHP 7.4.33 is not available in the default repositories for CentOS 8 Stream, you can check the version of PHP that is available in the default repositories for your version of CentOS by running the command:
sudo dnf info php
You can try using the “Alternate” repository, that provides newer versions of software for older versions of CentOS, but it’s not officially supported by Red Hat and you should use it with caution.
You can try installing PHP 7.4.33 from the Alternate repository by running the command:
sudo dnf install php74
Another alternative is to install PHP from a third-party repository like IUS (Inline with Upstream Stable) by running the command:
sudo dnf install https://rhel8.iuscommunity.org/ius-release.rpm
sudo dnf install php74u

It’s important to keep in mind that installing software from third-party repositories may have security risks and compatibility issues with your current system.
Also please make sure to check the package versions with the other software you are planning to use, such as Apache and MySQL, to ensure compatibility.
Please let me know if there is anything else I can assist you with.

[オレ]
I tried IUS but rhel8.iuscommunity.org doesn’t exist.

[ChatGPT]
I apologize for the confusion caused by my previous response. It looks like the repository rhel8.iuscommunity.org is no longer available, it may be that the repository has been discontinued or moved to a different location.
Here are a few things you can try:
1 Check if there is any other package that provides the PHP 7.4.33 package for your version of CentOS by running the command:
sudo dnf search php74
2 You can try installing PHP from another third-party repository like Remi’s RPM repository, which provides newer versions of PHP, by running the Copy code
sudo dnf install http://rpms.remirepo.net/enterprise/8/remi/x86_64/php74-php-7.4.33-1.el8.remi.x86_64.rpm
3 You can also try installing PHP from a package built by a community member, but it’s important to keep in mind that these packages may not be officially supported and may have compatibility issues.
4 If none of the above solutions work, you may need to consider upgrading to a newer version of CentOS that supports PHP 7.4.33.
It’s important to keep in mind that installing software from third-party repositories may have security risks and compatibility issues with your current system.
Also please make sure to check the package versions with the other software you are planning to use, such as Apache and MySQL, to ensure compatibility.
Please let me know if there is anything else I can assist you with.

[オレ]
Cool, I have downloaded http://rpms.remirepo.net/enterprise/8/remi/x86_64/php74-php-7.4.33-1.el8.remi.x86_64.rpm! Finally, where can I locate mysql 5.7.33 RPM for centos8 stream?

[ChatGPT]
MySQL 5.7.33 is not available in the default repositories for CentOS 8 Stream, you can check the version of MySQL that is available in the default repositories for your version of CentOS by running the command:
sudo dnf info mysql-community-server
You can try using the “Alternate” repository, that provides newer versions of software for older versions of CentOS, but it’s not officially supported by Red Hat and you should use it with caution.
You can try installing MySQL 5.7.33 from the Alternate repository by running the command:
sudo dnf install mysql57
Another alternative is to install MySQL from a third-party repository like Percona, which provides newer versions of MySQL that are compatible with your version of CentOS 8.
You can download the package from the official website of Percona (https://www.percona.com/downloads/Percona-Server-5.7/) and install it with rpm command:
sudo rpm -ivh Percona-Server-5.7.33-rel34.1-1.el8.x86_64.rpm
It’s important to keep in mind that installing software from third-party repositories may have security risks and compatibility issues with your current system.
Also please make sure to check the package versions with the other software you are planning to use, such as Apache and PHP, to ensure compatibility.
Please let me know if there is anything else I can assist you with.

[オレ]
I came up with “Raven” repository. Would you tell me “Raven” is reliable or not?

[ChatGPT]
I am not familiar with “Raven” repository specifically, so I can’t say for certain if it’s reliable or not. In general, when using third-party repositories it’s important to research the reputation and reliability of the source before installing packages from it.
Here are a few things you can do to check the reliability of a repository:
1 Look for reviews or testimonials from other users who have used the repository.
2 Check the repository’s website for information about its maintenance, security and update policies.
3 Check if the repository is backed by a reputable organization or company.
4 Check if the repository is actively maintained and updated.
5 Check if the repository has a public bug tracker, and if there are any known issues with the packages provided.
It’s important to keep in mind that installing software from third-party repositories may have security risks and compatibility issues with your current system.
You should also check the package versions with the other software you are planning to use, such as Apache and PHP, to ensure compatibility.
Please let me know if there is anything else I can assist you with.

CentOS7 に Emacs 26.3 をインストールして ShellCheck を Emacs 内でオンザフライ実行する

CentOS7 で利用可能な emacs パッケージは標準 RPM では 24.3.任意のバージョンをインストールしたいので,ここでは 26.3 をソースからビルドする(emacs で flycheck パッケージを使用するが,flycheck 自体は 24.3 以降で利用はできる).

開発ツールのインストール

$ sudo yum -y groupinstall "Development Tools"
$ sudo yum -y install gnutls-devel ncurses-devel

ソースの入手

$ curl -LO https://ftpmirror.gnu.org/emacs/emacs-26.3.tar.gz

ソースの展開

$ zcat emacs-26.3.tar.gz | tar xf -

make/make install

ここでは Emacs はターミナル内でのみ使用することにし,X-Window 上の GUI は使用しないことにするので --with-x=no を指定する.

$ cd emacs-26.3
$ configure --with-x=no --without-pop
$ make
$ sudo make install

インストール完了

$ which emacs
/usr/local/bin/emacs
$ emacs --version
GNU Emacs 26.3
Copyright (C) 2019 Free Software Foundation, Inc.
GNU Emacs comes with ABSOLUTELY NO WARRANTY.
You may redistribute copies of GNU Emacs
under the terms of the GNU General Public License.
For more information about these matters, see the file named COPYING.

ShellCheck をインストール

$ sudo yum -y install ShellCheck-0.8.0-1.x86_64.rpm

Emacs の設定

emacs 初回起動時にホームディレクトリに .emacs.d が作成される.init.el は手動作成する.

$ emacs # 起動して何もせず終了する
$ cat <<EOF > ~/.emacs.d/init.el
(require 'package)

(add-to-list 'package-archives
             '("MELPA Stable" . "https://stable.melpa.org/packages/") t)
(package-initialize)
EOF

このあと emacs をまた起動し,M-x package-install[RET] し,Install package:flycheck[RET] を入力する.これで flycheck が ~/.emacs.d/ にダウンロードされ,init.el に必要な設定が追記される.

最後に emacs をまた終了し,以下のように sh-mode で flycheck が自動起動するよう設定を .emacs.d/init.el に追記する.

echo "(add-hook 'sh-mode-hook 'flycheck-mode)" >> ~/.emacs.d/init.el

これで次回 emacs を起動してシェルスクリプトを作成または編集するときには,バッファ内コンテンツに対して ShellCheck が稼働して,引っかかった部分を太字や下線でハイライトして見せてくれるようになる.(https://www.flycheck.org/en/latest/languages.html にある通り,デフォルトで sh-shellcheck が入っているので,flycheck に ShellCheck 用の設定を追加で施す必要はない)

CentOS7 での ShellCheck ビルドメモ

CentOS7 の場合 EPEL に ShellCheck は存在するけれども,バージョンが 0.3.8 と古い(※ pkgs.org によれば,RHEL9 ならば最新の 0.8.0 の RPM が EPEL にあるようだ)ので,ソースからビルドする.

ShellCheck のソースは, https://github.com/koalaman/shellcheck から入手できる.ShellCheck のコンパイルには Haskell/Cabal も必要なので,まずそれをインストールする.なお,README.md によると 2GB RAM でコンパイルできるとあるが,6GB ないと成功しなかった(swap 容量の設定は変えず,単純に VM の RAM 容量を増やして解決した).

Cabal のインストール

$ sudo yum -y install gcc gmp gmp-devel make ncurses xz perl zlib-devel
$ curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
$ cabal install --constraint="lukko -ofd-locking" cabal-install

ShellCheck のソース入手+コンパイル

$ sudo yum -y install git
$ git clone https://github.com/koalaman/shellcheck.git
$ cd shellcheck/
$ cabal install

以上で ShellCheck がコンパイルされて,~/.cabal/bin/shellcheck にインストールされる.--version オプションをつけて実行するとこんな具合だ.

$ shellcheck --version
ShellCheck - shell script analysis tool
version: 0.8.0
license: GNU General Public License, version 3
website: https://www.shellcheck.net
[snakai@task2140 shellcheck]$ shellcheck --help
Usage: shellcheck [OPTIONS...] FILES...
  -a                  --check-sourced            Include warnings from sourced files
  -C[WHEN]            --color[=WHEN]             Use color (auto, always, never)
  -i CODE1,CODE2..    --include=CODE1,CODE2..    Consider only given types of warnings
  -e CODE1,CODE2..    --exclude=CODE1,CODE2..    Exclude types of warnings
  -f FORMAT           --format=FORMAT            Output format (checkstyle, diff, gcc, json, json1, quiet, tty)
                      --list-optional            List checks disabled by default
                      --norc                     Don't look for .shellcheckrc files
  -o check1,check2..  --enable=check1,check2..   List of optional checks to enable (or 'all')
  -P SOURCEPATHS      --source-path=SOURCEPATHS  Specify path when looking for sourced files ("SCRIPTDIR" for script's dir)
  -s SHELLNAME        --shell=SHELLNAME          Specify dialect (sh, bash, dash, ksh)
  -S SEVERITY         --severity=SEVERITY        Minimum severity of errors to consider (error, warning, info, style)
  -V                  --version                  Print version information
  -W NUM              --wiki-link-count=NUM      The number of wiki links to show, when applicable
  -x                  --external-sources         Allow 'source' outside of FILES
                      --help                     Show this usage summary and exit

man の生成

man ページを pandoc で作成することができる.pandoc は以下で導入可能.

$ curl -LO https://github.com/jgm/pandoc/releases/download/2.19.2/pandoc-2.19.2-linux-amd64.tar.gz
$ zcat pandoc-2.19.2-linux-amd64.tar.gz | tar xvf -
$ sudo cp pandoc-2.19.2/bin/* /usr/local/bin
$ sudo cp pandoc-2.19.2/share/man/man1/pandoc* /usr/local/share/man/man1
$ which pandoc
/usr/local/bin/pandoc

pandoc が使えるようになったら,以下で ShellCheck の man ページが生成される.(/usr/local/share/man/man1 へのインストールも行っている)

$ cd shellcheck/
$ pandoc -s -f markdown-smart -t man shellcheck.1.md -o shellcheck.1
$ sudo cp shellcheck.1 /usr/local/share/man/man1
$ man shellcheck

rpm 作成

rpm 作成用パッケージを導入する.

$ yum -y install rpmdevtools
$ rpmdev-setuptree

SPEC ファイルを作成する.

$ cat <<EOF > ~/rpmbuild/SPECS/ShellCheck.spec
Name: ShellCheck
Version: 0.8.0
Release: 1
Summary: ShellCheck - A shell script static analysis tool

License: GPLv3+
URL: https://www.shellcheck.net/
Source0: %{name}-%{version}.tar.gz

%description
ShellCheck is a GPLv3 tool that gives warnings and suggestions for bash/sh shell scripts.
The goals of ShellCheck are

* To point out and clarify typical beginner's syntax issues that cause a shell to give cryptic error messages.

* To point out and clarify typical intermediate level semantic problems that cause a shell to behave strangely and counter-intuitively.

* To point out subtle caveats, corner cases and pitfalls that may cause an advanced user's otherwise working script to fail under future circumstances.

%prep

%setup

%build

%define  debug_package %{nil}

%install
mkdir -p $RPM_BUILD_ROOT/usr/local/bin
mkdir -p $RPM_BUILD_ROOT/usr/local/man/man1
install -m 755 shellcheck $RPM_BUILD_ROOT/usr/local/bin
install -m 644 shellcheck.1 $RPM_BUILD_ROOT/usr/local/man/man1

%files
/usr/local/bin/shellcheck
/usr/local/man/man1/shellcheck.1

%changelog
* <DOW> <Mon> <Day> <Year> <Packager Name> <<Packager Email>>
- Initial release
EOF

バイナリ tar.gz の準備と rpmbuild 実行.

$ mkdir ShellCheck-0.8.0
$ cp ~/.cabal/bin/shellcheck/shellcheck ShellCheck-0.8.0/
$ cp shellcheck/shellcheck.1 ShellCheck-0.8.0/
$ tar czf ShellCheck-0.8.0.tar.gz ShellCheck-0.8.0
$ cp ShellCheck-0.8.0.tar.gz ~/rpmbuild/SOURCES/
$ cd ~
$ rpmbuild -bb ~/rpmbuild/SPECS/ShellCheck.spec

以上で,未署名 RPM ~/rpmbuild/RPMS/x86_64/ShellCheck-0.8.0-1.x86_64.rpm が作成される.必要に応じて GPG 署名する.

while と PIPESTATUS

# while read -r _r; do true; done < /etc/passwd
# echo $?
0
# while read -r _r; do true; done < /etc/passwd
# echo ${PIPESTATUS[@]}
1
# while read -r _r; do true; done < /etc/passwd1
-bash: /etc/passwd1: そのようなファイルやディレクトリはありません
# echo $?
1
# while read -r _r; do true; done < /etc/passwd1
-bash: /etc/passwd1: そのようなファイルやディレクトリはありません
# echo ${PIPESTATUS[@]}
0

フラットファイル + シェルスクリプト「で」トランザクション

ファイルシステム「の」トランザクションではなく,データリポジトリにフラットファイルを使用して,ビジネスアプリケーションをシェルスクリプトで組むときにどうやって「トランザクション」を実現するかという話.

トランザクションは「データ(のセット)を(業務上)整合性のとれた一貫性のある状態に保ちながら更新すること」と言って良いと思う.これを実現するために,「コミット」や「ロールバック」の手段を整備することが必要になる.

SQL の場合,こんな感じにできるらしい.

BEGIN;

UPDATE accounts SET balance = balance + 10000 WHERE name = 'Jiro';

UPDATE accounts SET balance = balance - 10000 WHERE name = 'Taro';

COMMIT;

二つ目の UPDATE 文で,balance に「0または自然数」制約みたいなものが付いていて,なおかつ Taro の口座に 10,000円入っていなかった場合は,二つ目の UPDATE は失敗する.このとき DBMS は勝手に BEGIN の時の状態にデータを戻してくれる – Jiro の口座への 10,000円追加を無かったことにしてくれる(ロールバック).

これに加えて上記の SQL コードには陽に現れていないが,DBMS ではこのトランザクションが開始されると他のプログラムから Taro や Jiro の口座データが更新することはできない仕組みになっている(排他制御).同等のことを,フラットファイル + シェルスクリプトでどれだけシステマティックに実現できるかがテーマである.

排他制御をどう実現するか

ファイルアクセスの排他制御というと「ファイルロック」だが,UNIX では「アドバイザリロック」を使用するのが一般的だ.「マンダトリ(強制)ロック」がサポートされる OS もあるようだ(この辺のことはこちらを参照)が,ここではアドバイザリロックを前提にする.アドバイザリロックというのはあくまで「アドバイザリ」なので,カーネルがプログラムのファイルアクセスを禁止しているわけではない.従って,排他が問題になるプログラム群は,共通の排他制御のルールに従う(簡単に言うと,ファイルアクセスの際にいちいちロックの有無を確認→ロックする→アクセスする→ロックを解除する)ことが要求される.

アドバイザリロックを実現するにも何通りかのやり方がある.字面上いちばん名が通っているのが flock(2) である.ただ flock(2) は C プログラマ向けに用意された API であり,シェルスクリプトから利用するにはなんらかのパッケージを介在させる必要がある.Linux の場合は flock(1) コマンドが用意されているが,他の UNIX OS では一般に利用できない.また flock(1) も flock(2) も NFS に対応していないという仕様上の制限もある.

そういったことから,歴史的に「ロックファイル」の方式がよく使われる.これは,一つのファイルシステム上にパスが同じファイルは複数存在できないという仕様を利用しており,「ファイルAをロックする」ということを「(ファイルAに対応した)ロックファイルaを作成する」ということに読み替えてアドバイザリロックを擬似的に実現するものである.「ロックファイルaの作成を試みたが,できなかった」ということは「ファイルAは他のプログラムで使用されている」ということなので,プログラムはファイルAへのアクセスをいったん諦めて,時間をおいて再挑戦するなり,エラー終了するなりという挙動をするようプログラマがロジックを組む責任がある.

ところで「ファイルロック」を実現するのに「ロックファイル」を使用する,というのが用語上混乱をきたすことがあるかもしれない(自分がそうだったので).「セマフォファイル」という呼称にするとその心配がなくなるかもしれないが,本来セマフォは共有資源にアクセスできるプログラムの数を制限するためのものなので,排他制御の文脈で使うと(共有資源同時アクセス数 1 とすれば排他制御にもなるが)それはそれで混乱するかもしれない.

コミットをどう実現するか

フラットファイル + シェルスクリプトにおいて,データの更新方法はいくつかある.ここではフラットファイルがレコード(行)xフィールド(列)の二次元形式を取っていると想定すると,

レコード追記型更新

new_record="$(<処理>)"
printf "%s\n" "$new_record">> <フラットファイル>

非追記型(ランダム更新 – レコード挿入,削除,フィールド値更新)

<処理> < <フラットファイル> > <一時ファイル>
mv <一時ファイル> <フラットファイル>

こんなところだろう.ビジネスアプリケーションにおいては,レコード追記型更新で全てを済ませられる場合は少ないだろうから,ここは非追記型を想定する.非追記型はレコード追記型にも使用することができる.また,非追記型の更新は「更新試行」→「確定」というトランザクションのコミットの動きそのものである.あとは,トランザクションに関係する複数ファイルを「全て mv で更新する・しない」,言い方を変えると,複数ファイルのうち一部だけ mv で更新されてしまわないようにする制御を加えれば,トランザクションのコミットは実現可能ということになる.

なお mv による更新の魅力は,一時ファイルと本番ファイルが同一ファイルシステムに格納されている場合,それらを置き換えるにあたって I/O が発生しないことである(ファイルシステムメタデータの更新のため微量の I/O は発生するが,データを複製するための I/O は発生しない).この場合,一時ファイルによるフラットファイルの置き換えは一瞬で済ませることができる.

ロールバックをどう実現するか

コミットの考察で,非追記型更新を前提にすると述べた.この場合,ロールバックとは何かというと「mv で一時ファイルをフラットファイルに置き換えないままプログラムを終了すること」で良かろう.ただし単一ファイルの更新ならそれで良いのだが,複数ファイルの更新を行うトランザクションでは少々他の考慮も必要である.

2つのファイルAとBを更新するトランザクションがあったとする.AとBにロックを施して排他を宣言し,ファイルAとファイルBをそれぞれ更新していったん一時ファイルAと一時ファイルBを作成までできた(ここまででエラーが発生した場合は,一時ファイルAもBもない,や,一時ファイルAだけ生成されているという状況だから,そのままプログラムを終了すればファイルAとBの内容は整合性がとれたままである).

問題はこの後で,次は

  • 一時ファイルAをファイルA に mv
  • 一時ファイルBをファイルB に mv

するわけだが,前者が成功して後者が失敗した場合は,ファイルAを更新前の状態に戻さなければならない.ここが工夫のしどころとなる.

ファイルAの更新前の状態を保存するためにまず思いつくのは,トランザクション開始時に cp でバックアップを取っておくことである.

cp ファイルA ファイルA.org

そうして,一時ファイルBをファイルBに mv する操作が失敗したら,

mv ファイルA.org ファイルA

これでロールバックができた,ということになる(この操作が失敗したらどうするかというのは考慮事項だが,それはアプリケーションの要件によって変わってくるのでここでは議論しない.ロールバックが失敗したという通知をさせ,またはログを吐かせ,いったんシステムを停止する – ユーザーからのアクセスを遮断して管理者だけが操作可能にする – というのが定石だとは思う)

問題は,cp によるバックアップでデータ I/O が発生してしまうため,トランザクション開始時のオーバーヘッドが大きいということである.ファイルが数GBあった場合,トランザクション開始操作に数10秒を要することも想定される(RAM を大量に搭載していてファイルシステムキャッシュが潤沢に使えている場合は一瞬で完了する可能性はある – が毎回それは期待できない).

これを解決するのがハードリンクである.大学で共用計算機の SunOS を利用する際に「リンク」は基本「シンボリックリンク」を使え -「ハードリンク」は使ってはならぬと教え込まれたので,その理由もよく考えないまま(今から考えると,複数のファイルシステムにホームディレクトリが収容されていたからとかそういうことなんでは,と思うが)ハードリンクには馴染みのないままこれまで過ごしてきたが,cp の代替手段としてこのハードリンクが使える.

ln ファイルA ファイルA.org

これで「ファイルA」のデータは「ファイルA.org」という名前でも参照できるようになった.内部ではファイルシステム上で同じデータに違うラベルが追加された,ということなので,データ I/O は発生しない.この操作をおこなった後,「一時ファイルA」を「ファイルA」に mv して更新すると,「ファイルA」を指すと更新されたデータが得られるが,「ファイルA.org」を指すと更新前のデータが得られる.

従って,この後「一時ファイルB」を「ファイルB」に mv して更新する操作が失敗した場合,mv ファイルA.org ファイルA でファイルAを元の内容に戻すことが可能である.

定式(決め事)化

トランザクションでのデータ更新,コミット,ロールバックをどう実現するかの骨子は以上である.あとは以上の事柄をプログラマにできるだけ簡単になおかつ確実に実行してもらうための仕組み(決め事)作りである.

現在のところ,以下のような決め事とし,それをサポートする関数 INIT(), BEGIN(), COMMIT(), ROLLBACK(), END() を実装している(関数やファイル名の字面は実際とは異なる)

  • INIT() でトランザクションIDを設定して,本トランザクションで一時ファイル等を置く一時ディレクトリを作成
  • files-to-protect(トランザクション中,他のプログラムからのアクセスを遮断しなければいけないファイル群)を定義
  • files-to-update (トランザクション中更新するファイルと,更新一時ファイル名)を定義
  • BEGIN() で排他ファイルロックとハードリンク作成によるバックアップを実施
  • トランザクション処理はサブシェル内に記述し,エラーが発生すれば non-zero exit させる
  • non-zero exit (異常終了)した場合は ROLLBACK() する(ただし,この場合の ROLLBACK() は実質ほぼ何もしない – バックアップハードリンクを消すだけである)
  • zero exit (正常終了)した場合は COMMIT() するが.ここで失敗すると ROLLBACK() する
  • END() で排他ファイルロックを解除,バックアップハードリンク消去,トランザクション用一時ディレクトリを削除する
# トランザクションの初期化
INIT || exit 1

# このトランザクション中,排他アクセスが必要なファイルをリスト
cat <<EOF > files-to-protect || exit 1
ledger-Taro
ledger-Jiro
EOF

# このトランザクション中,更新するファイルとその一時ファイル名をリスト
cat <<EOF > files-to-update || exit 1
ledger-Taro    ledger-Taro.new
ledger-Jiro    ledger-Jiro.new
EOF

# トランザクション開始
BEGIN || exit 1

dt="$(date "+%Y/%m/%d %H:%M")" || exit 1
name=フリコミ
amount=10000

# トランザクション処理(事前に名前を決めておいた一時ファイルに更新データを出力する)
(
  # Taro の通帳更新
  printf "%s\t%s\t%d\n" "$dt" "$name" "-$amount" > /tmp/transaction || exit 1
  cat ledger-Taro /tmp/transaction > ledger-Taro.new || exit 1

  # Jiro の通帳更新
  printf "%s\t%s\t%d\n" "$dt" "$name" "+$amount" > /tmp/transaction || exit 1
  cat ledger-Jiro /tmp/transaction > ledger-Jiro.new || exit 1
)
es=$?

if [ "$es" -ne 0 ] ; then
  # トランザクション処理が失敗した場合 ROLLBACK() が一時ファイルを消去する
  ROLLBACK || exit 1
else
  # トランザクション処理が成功した場合 COMMIT() が一時ファイルを本番ファイルに mv する
  COMMIT || {
    # コミットに失敗した場合 ROLLBACK() がバックアップハードリンクから本番ファイルをリストアする
    ROLLBACK || exit 1
  }
fi

# トランザクション終了(END() がファイルロック解除,バックアップハードリンク消去を行う)
END || exit 1