拡張 POSIX シェルスクリプト Advent Calendar 2013、10日目の記事です。 そろそろネタが尽きそうです。 スクリプトの添削依頼や疑問・質問、やって欲しいネタをお待ちしております。 記事に反映できるかどうかは内容や私の実力次第ですが…。
今日はコマンドパイプラインを実行したときの変数のスコープの問題について紹介します。 「あー、パイプラインとサブシェルの問題だね。知っているよ!」という方も、 最後までお付き合いいただけると新しい発見があるかもしれません。
まずシェルスクリプトのお題として 「メンバー情報が空でないグループの数とグループ名の一覧をカンマ区切りで表示するスクリプトの作成」 を考えてみます。 実装のポイントは次の通りです。
- グループ情報は
getent group
でgroup
(5) 形式の情報が得られる。 (getent
コマンドがない OS をご利用の方はcat /etc/group
で代用してください) group
(5) の 1レコード (1行) はグループ名:パスワードハッシュ:GID:メンバー
のようなコロン:
区切りのテキストである。- 「メンバー情報が空でないグループ」とはグループレコード中の 「メンバー」の項目が空でないことを条件とする。
私の手元の環境でメンバーが空でない(メンバーがいる)グループを確認したところ、 18グループが該当しました。以下、スクリプトの実行例はこの状態を前提とします。
$ getent group |grep -v ':$'
dialout:x:20:fumiyas
cdrom:x:24:fumiyas
…省略…
$ getent group |grep -v ':$' |wc -l
18
bash によるよくある間違った実装例
シェルスクリプトに不慣れな人が実装するとこんな感じの実装になるでしょうか。 典型的な間違った実装です。
このスクリプトを実行してみます。
$ bash ./nomember-buggy.bash
0
NOT FOUND
期待通りに動いていないようです。何故でしょうか。 それは、POSIX やそれ以前の sh、そして bash では、 コマンドパイプライン中のすべてのコマンドがサブシェル内で実行されるためです。 サブシェル内ではメインシェルのすべての変数を継承しますが、 サブシェル内で設定・変更・削除した変数はメインシェルに反映されません。 よって、パイプライン以降のメインシェルで処理結果を参照することはできません。
キーワード「パイプ サブシェル」でググるとこの現象(と回避方法)が見つかるように、 シェルスクリプトの問題の中でも比較的よく語られている問題です。
bash による正しい実装例 (その1)
複数のコマンドの組み合せによる処理と結果をパイプラインのように、
かつパイプラインを避けて実装するには、
パイプラインの前段の処理をプロセス置換 <(コマンド)
に置き換え、
パイプラインの最後段の処理をメインシェルで実行し、
プロセス置換の結果をリダイレクトで受け取る方法が一般的です。
このスクリプトは期待通りに動作します。
$ bash ./nomember-good1.bash
18
dialout,cdrom,floppy,audio,dip,video,plugdev,…省略…
正しく動くのはいいですが、パイプラインより美しさに欠けるように見えます。 どうでしょうか。
ksh, zsh による正しい実装例
ksh と zsh はどうでしょうか。正しい実装例を示します。
間違い探しをしてもらうまでもなく、最初の bash 向けの間違った実装例との違いは
shebang が #!/bin/bash
から #!/bin/ksh
に変わっただけです。
ksh と zsh であれば、このスクリプトは期待通りに動作します。
$ ksh ./nomember-good.ksh
18
dialout,cdrom,floppy,audio,dip,video,plugdev,…省略…
$ zsh ./nomember-good.ksh
18
dialout,cdrom,floppy,audio,dip,video,plugdev,…省略…
最初の間違った例のスクリプトも期待通りに動作します。 (ksh, zsh で直接実行しているので当たり前)
$ ksh ./nomember-buggy.bash
18
dialout,cdrom,floppy,audio,dip,video,plugdev,…省略…
$ zsh ./nomember-buggy.bash
18
dialout,cdrom,floppy,audio,dip,video,plugdev,…省略…
これは何故でしょうか。 ksh と zsh では、コマンドパイプラインの最後段がメインシェルで実行されるためです。 パイプラインで複数コマンドの組み合せ処理が自然に記述できて、 大変素晴しいですね。私が bash より ksh, zsh が好きな理由の一つです。
bash による正しい実装例 (その2)
bash ファンのみなさん、お待たせいたしました! 実は bash でも ksh, zsh と同じ挙動にすることができます!! ただし bash 4.2 以降限定です。どうぞ!!!!
最初に新しい bash オプション lastpipe を有効にします。これだけ。 これで bash でも美しいパイプラインが書けますね。
ところで、12月25日はクリスマスな上に、
OSS 界隈で地味に活躍されているふみやすさんの誕生日ですね!
っ http://www.amazon.co.jp/registry/wishlist/27M7TV8CEEF6G?sort=priority
逆に、あなたの書いた OSS や Blog や Advent Calendar が気に入ったら何か送りたく なってしまうかもしれないので、プロフィールや Web サイトに あなたの Amazon 欲しいものリストの URL を貼っておいてくださいね!
私が勤める OSSTech っていう某弊社で社員募集しているようです。 人材紹介会社を介さなければ、入社後に 20万円のボーナス! 「ふみやすっていう人に紹介された」と言ってもらえると私にもボーナス!! → https://www.osstech.co.jp/recruit/
よろしければ、これまで参加した/参加予定のほかの Advent Calendar もどうぞ。
- Ansible Advent Calendar 2023
- シェル芸 Advent Calendar 2023
- 闇の魔術に対する防衛術 Advent Calendar 2023
- Ansible Advent Calendar 2023
- Ansible Advent Calendar 2020
- DNS温泉 Advent Calendar 2019
- OSSTech Advent Calendar 2019
- Ansible Advent Calendar 2018
- OSSTech Advent Calendar 2018
- Debian/Ubuntu Advent Calendar 2017
- Linux Advent Calendar 2017
- Shell Script Advent Calendar 2017
- Shell Script Advent Calendar 2016
- OpenLDAP と仲間たち Advent Calendar 2015
- Postfix Advent Calendar 2014
- 拡張 POSIX シェルスクリプト Advent Calendar 2013