日々之迷歩

世の中わからんことだらけ

ITが複雑で難しくなっていく様に翻弄される日々です。微力ながら共著させていただいた「シェル・ワンライナー160本ノック」をよろしくお願い申し上げます。

対話モードか否か?

10月末に行われた第19回シェル芸勉強会の問題をふと思い出すことがありました。

最初の問題が、端末で実行した場合とシェルスクリプトに書いて実行した場合で動きを変える、という内容でした。出題者上田さんの解答例では、aliasが効くかどうかで判定していました。

問題と解答は、出題者上田さんが公開されているページQ1をご覧ください。

b.ueda.tech

この問題の意味は、シェルの状態が対話(インタラクティブ)モードかどうか?の判定をどうするかということだと思います。

ところでFreeBSDのデフォルトログインシェルはtcshです。FreeBSDのサーバで作業をしていた時に、ふとシェルの設定ファイルを確認してみました。デフォルトで提供される設定ファイルが下記のようになっています。

> tail -n +24 .cshrc

if ($?prompt) then【←シェル変数promptが定義されているか?】
  # An interactive shell -- set some stuff up
  #set prompt = "%N@%m:%~ %# "
  #set promptchars = "%#"
  set prompt = '\n%n@%M:%~\n%# '

  set filec
  set history = 1000
  set savehist = (1000 merge)
  set autolist = ambiguous
  # Use history to aid expansion
  set autoexpand
  set autorehash
  set mail = (/var/mail/$USER)
  if ( $?tcsh ) then
    bindkey "^W" backward-delete-word
    bindkey -k up history-search-backward
    bindkey -k down history-search-forward
  endif
endif

プロンプトが設定されたシェル変数promptが定義されているか?を判定するif文があります。 ここでシェルの状態が対話(インタラクティブ)モードなのか?どうか判定しているようです。

実際に確認してみました。シェルはtcshです。 スクリプト(非対話モード)は、シェルの-cオプションを使ってコマンド文字列を指定する方法を使いました。

## 対話モード
> echo $prompt
\n%n@%M:%~\n%#

## 非対話モード
> tcsh -c 'echo $prompt'
prompt: Undefined variable.

sh、bash、zshのプロンプトはPS1というシェル変数で設定します。 tcshと同様に、対話モードか非対話モードで変化するか、bashを使って実際に確認してみました。

## 対話モード
$ echo $PS1
\n\u@\h:\w\n\$

## 非対話モード
$ bash -c 'echo $PS1'
【出力無し】

非対話モードでは出力がありませんが、PS1が未定義なのかどうかが確認出来ません。 そこで未定義の変数を参照したらエラーが発生するset -uを使って確認しました。

## 対話モード
$ set -u; echo $PS1; set +u
\n\u@\h:\w\n\$

## 非対話モード
$ bash -c 'set -u; echo $PS1'
bash: PS1: 未割り当ての変数です

つまり、非対話モードの場合はシェル変数PS1が未定義のようです。

シェル変数PS1の文字列が未定義(空文字)かで判定する解答はこちらです。

$ [ -n "$PS1" ] && echo 1ppm || echo 40ppm
1ppm

$ bash -c '[ -n "$PS1" ] && echo 1ppm || echo 40ppm'
40ppm